graphwise 1.8.1 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/{adjacency-map-D-Ul7V1r.js → adjacency-map-BtKzcuJq.js} +1 -1
  2. package/dist/{adjacency-map-D-Ul7V1r.js.map → adjacency-map-BtKzcuJq.js.map} +1 -1
  3. package/dist/{adjacency-map-B6wPtmaq.cjs → adjacency-map-JqBnMNkF.cjs} +1 -1
  4. package/dist/{adjacency-map-B6wPtmaq.cjs.map → adjacency-map-JqBnMNkF.cjs.map} +1 -1
  5. package/dist/async/index.cjs +2 -2
  6. package/dist/async/index.js +2 -2
  7. package/dist/expansion/index.cjs +1 -1
  8. package/dist/expansion/index.js +1 -1
  9. package/dist/{expansion-sldRognt.js → expansion-ClDhlMK8.js} +4 -4
  10. package/dist/{expansion-sldRognt.js.map → expansion-ClDhlMK8.js.map} +1 -1
  11. package/dist/{expansion-FkmEYlrQ.cjs → expansion-DaTroIyv.cjs} +4 -4
  12. package/dist/{expansion-FkmEYlrQ.cjs.map → expansion-DaTroIyv.cjs.map} +1 -1
  13. package/dist/extraction/index.cjs +1 -1
  14. package/dist/extraction/index.js +1 -1
  15. package/dist/gpu/csr.d.ts +29 -30
  16. package/dist/gpu/csr.d.ts.map +1 -1
  17. package/dist/gpu/dispatch.d.ts +31 -0
  18. package/dist/gpu/dispatch.d.ts.map +1 -0
  19. package/dist/gpu/dispatch.unit.test.d.ts +5 -0
  20. package/dist/gpu/dispatch.unit.test.d.ts.map +1 -0
  21. package/dist/gpu/index.cjs +15 -410
  22. package/dist/gpu/index.d.ts +3 -1
  23. package/dist/gpu/index.d.ts.map +1 -1
  24. package/dist/gpu/index.js +2 -400
  25. package/dist/gpu/kernels/bfs/kernel.d.ts +59 -0
  26. package/dist/gpu/kernels/bfs/kernel.d.ts.map +1 -0
  27. package/dist/gpu/kernels/bfs/logic.d.ts +47 -0
  28. package/dist/gpu/kernels/bfs/logic.d.ts.map +1 -0
  29. package/dist/gpu/kernels/bfs/logic.unit.test.d.ts +2 -0
  30. package/dist/gpu/kernels/bfs/logic.unit.test.d.ts.map +1 -0
  31. package/dist/gpu/kernels/degree-histogram/kernel.d.ts +32 -0
  32. package/dist/gpu/kernels/degree-histogram/kernel.d.ts.map +1 -0
  33. package/dist/gpu/kernels/degree-histogram/logic.d.ts +45 -0
  34. package/dist/gpu/kernels/degree-histogram/logic.d.ts.map +1 -0
  35. package/dist/gpu/kernels/degree-histogram/logic.unit.test.d.ts +2 -0
  36. package/dist/gpu/kernels/degree-histogram/logic.unit.test.d.ts.map +1 -0
  37. package/dist/gpu/kernels/jaccard/kernel.d.ts +40 -0
  38. package/dist/gpu/kernels/jaccard/kernel.d.ts.map +1 -0
  39. package/dist/gpu/kernels/jaccard/logic.d.ts +43 -0
  40. package/dist/gpu/kernels/jaccard/logic.d.ts.map +1 -0
  41. package/dist/gpu/kernels/jaccard/logic.unit.test.d.ts +2 -0
  42. package/dist/gpu/kernels/jaccard/logic.unit.test.d.ts.map +1 -0
  43. package/dist/gpu/kernels/pagerank/kernel.d.ts +44 -0
  44. package/dist/gpu/kernels/pagerank/kernel.d.ts.map +1 -0
  45. package/dist/gpu/kernels/pagerank/logic.d.ts +50 -0
  46. package/dist/gpu/kernels/pagerank/logic.d.ts.map +1 -0
  47. package/dist/gpu/kernels/pagerank/logic.unit.test.d.ts +2 -0
  48. package/dist/gpu/kernels/pagerank/logic.unit.test.d.ts.map +1 -0
  49. package/dist/gpu/kernels/spmv/kernel.d.ts +43 -0
  50. package/dist/gpu/kernels/spmv/kernel.d.ts.map +1 -0
  51. package/dist/gpu/kernels/spmv/logic.d.ts +31 -0
  52. package/dist/gpu/kernels/spmv/logic.d.ts.map +1 -0
  53. package/dist/gpu/kernels/spmv/logic.unit.test.d.ts +2 -0
  54. package/dist/gpu/kernels/spmv/logic.unit.test.d.ts.map +1 -0
  55. package/dist/gpu/operations.d.ts +76 -0
  56. package/dist/gpu/operations.d.ts.map +1 -0
  57. package/dist/gpu/operations.unit.test.d.ts +5 -0
  58. package/dist/gpu/operations.unit.test.d.ts.map +1 -0
  59. package/dist/gpu/root.d.ts +53 -0
  60. package/dist/gpu/root.d.ts.map +1 -0
  61. package/dist/gpu/root.unit.test.d.ts +2 -0
  62. package/dist/gpu/root.unit.test.d.ts.map +1 -0
  63. package/dist/gpu/types.d.ts +3 -8
  64. package/dist/gpu/types.d.ts.map +1 -1
  65. package/dist/gpu-CHiCN0wa.js +16945 -0
  66. package/dist/gpu-CHiCN0wa.js.map +1 -0
  67. package/dist/gpu-Y6owRVMi.cjs +17028 -0
  68. package/dist/gpu-Y6owRVMi.cjs.map +1 -0
  69. package/dist/graph/index.cjs +1 -1
  70. package/dist/graph/index.js +1 -1
  71. package/dist/index/index.cjs +18 -15
  72. package/dist/index/index.js +10 -10
  73. package/dist/{jaccard-Yddrtt5D.js → jaccard-3rCdilwm.js} +2 -2
  74. package/dist/{jaccard-Yddrtt5D.js.map → jaccard-3rCdilwm.js.map} +1 -1
  75. package/dist/{jaccard-Bmd1IEFO.cjs → jaccard-Bys9_dGW.cjs} +2 -2
  76. package/dist/{jaccard-Bmd1IEFO.cjs.map → jaccard-Bys9_dGW.cjs.map} +1 -1
  77. package/dist/{kmeans-D3yX5QFs.cjs → kmeans-B8x9D1kt.cjs} +1 -1
  78. package/dist/{kmeans-D3yX5QFs.cjs.map → kmeans-B8x9D1kt.cjs.map} +1 -1
  79. package/dist/{kmeans-DVCe61Me.js → kmeans-DKkL9rAN.js} +1 -1
  80. package/dist/{kmeans-DVCe61Me.js.map → kmeans-DKkL9rAN.js.map} +1 -1
  81. package/dist/{ops-4nmI-pwk.cjs → ops-djAsQQSh.cjs} +2 -2
  82. package/dist/{ops-4nmI-pwk.cjs.map → ops-djAsQQSh.cjs.map} +1 -1
  83. package/dist/{ops-Zsu4ecEG.js → ops-upIi6JIi.js} +2 -2
  84. package/dist/{ops-Zsu4ecEG.js.map → ops-upIi6JIi.js.map} +1 -1
  85. package/dist/{priority-queue-ChVLoG6T.cjs → priority-queue-BIiD1L0k.cjs} +1 -1
  86. package/dist/{priority-queue-ChVLoG6T.cjs.map → priority-queue-BIiD1L0k.cjs.map} +1 -1
  87. package/dist/{priority-queue-DqCuFTR8.js → priority-queue-CFDd5cBg.js} +1 -1
  88. package/dist/{priority-queue-DqCuFTR8.js.map → priority-queue-CFDd5cBg.js.map} +1 -1
  89. package/dist/ranking/index.cjs +2 -2
  90. package/dist/ranking/index.js +2 -2
  91. package/dist/ranking/mi/index.cjs +2 -2
  92. package/dist/ranking/mi/index.js +2 -2
  93. package/dist/{ranking-mUm9rV-C.js → ranking-3ez5m67U.js} +2 -2
  94. package/dist/{ranking-mUm9rV-C.js.map → ranking-3ez5m67U.js.map} +1 -1
  95. package/dist/{ranking-riRrEVAR.cjs → ranking-DVvajgUZ.cjs} +2 -2
  96. package/dist/{ranking-riRrEVAR.cjs.map → ranking-DVvajgUZ.cjs.map} +1 -1
  97. package/dist/seeds/index.cjs +1 -1
  98. package/dist/seeds/index.js +1 -1
  99. package/dist/structures/index.cjs +1 -1
  100. package/dist/structures/index.js +1 -1
  101. package/dist/utils/index.cjs +1 -1
  102. package/dist/utils/index.js +1 -1
  103. package/dist/{utils-CcIrKAEb.js → utils-BodeE2Mo.js} +1 -1
  104. package/dist/{utils-CcIrKAEb.js.map → utils-BodeE2Mo.js.map} +1 -1
  105. package/dist/{utils-CpyzmzIF.cjs → utils-CDtCcsyF.cjs} +1 -1
  106. package/dist/{utils-CpyzmzIF.cjs.map → utils-CDtCcsyF.cjs.map} +1 -1
  107. package/package.json +3 -1
  108. package/dist/gpu/context.d.ts +0 -118
  109. package/dist/gpu/context.d.ts.map +0 -1
  110. package/dist/gpu/context.unit.test.d.ts +0 -2
  111. package/dist/gpu/context.unit.test.d.ts.map +0 -1
  112. package/dist/gpu/index.cjs.map +0 -1
  113. package/dist/gpu/index.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"expansion-sldRognt.js","names":[],"sources":["../src/expansion/base-helpers.ts","../src/expansion/base-core.ts","../src/expansion/base.ts","../src/expansion/dome.ts","../src/expansion/hae.ts","../src/expansion/edge.ts","../src/expansion/pipe.ts","../src/expansion/priority-helpers.ts","../src/expansion/sage.ts","../src/expansion/reach.ts","../src/expansion/maze.ts","../src/expansion/tide.ts","../src/expansion/lace.ts","../src/expansion/warp.ts","../src/expansion/fuse.ts","../src/expansion/sift.ts","../src/expansion/flux.ts","../src/expansion/standard-bfs.ts","../src/expansion/frontier-balanced.ts","../src/expansion/random-priority.ts","../src/expansion/dfs-priority.ts","../src/expansion/k-hop.ts","../src/expansion/random-walk.ts"],"sourcesContent":["/**\n * Pure helper functions for the BASE expansion engine.\n *\n * These functions are extracted from base.ts so they can be shared between\n * the synchronous `base()` entry point and the `baseCore` generator used by\n * both sync and async runners.\n *\n * All functions here are pure — they make no direct graph calls.\n */\n\nimport type { NodeId } from \"../graph\";\nimport type {\n\tSeed,\n\tExpansionPath,\n\tExpansionResult,\n\tExpansionStats,\n} from \"./types\";\n\n/**\n * Internal queue entry for frontier expansion.\n */\nexport interface QueueEntry {\n\tnodeId: NodeId;\n\tfrontierIndex: number;\n\tpredecessor: NodeId | null;\n}\n\n/**\n * Limits structure used by continueExpansion.\n */\nexport interface ExpansionLimits {\n\treadonly maxIterations: number;\n\treadonly maxNodes: number;\n\treadonly maxPaths: number;\n}\n\n/**\n * Check whether expansion should continue given current progress.\n *\n * Returns shouldContinue=false as soon as any configured limit is reached,\n * along with the appropriate termination reason.\n *\n * @param iterations - Number of iterations completed so far\n * @param nodesVisited - Number of distinct nodes visited so far\n * @param pathsFound - Number of paths discovered so far\n * @param limits - Configured expansion limits (0 = unlimited)\n * @returns Whether to continue and the termination reason if stopping\n */\nexport function continueExpansion(\n\titerations: number,\n\tnodesVisited: number,\n\tpathsFound: number,\n\tlimits: ExpansionLimits,\n): { shouldContinue: boolean; termination: ExpansionStats[\"termination\"] } {\n\tif (limits.maxIterations > 0 && iterations >= limits.maxIterations) {\n\t\treturn { shouldContinue: false, termination: \"limit\" };\n\t}\n\tif (limits.maxNodes > 0 && nodesVisited >= limits.maxNodes) {\n\t\treturn { shouldContinue: false, termination: \"limit\" };\n\t}\n\tif (limits.maxPaths > 0 && pathsFound >= limits.maxPaths) {\n\t\treturn { shouldContinue: false, termination: \"limit\" };\n\t}\n\treturn { shouldContinue: true, termination: \"exhausted\" };\n}\n\n/**\n * Reconstruct path from collision point.\n *\n * Traces backwards through the predecessor maps of both frontiers from the\n * collision node, then concatenates the two halves to form the full path.\n *\n * @param collisionNode - The node where the two frontiers met\n * @param frontierA - Index of the first frontier\n * @param frontierB - Index of the second frontier\n * @param predecessors - Predecessor maps, one per frontier\n * @param seeds - Seed nodes, one per frontier\n * @returns The reconstructed path, or null if seeds are missing\n */\nexport function reconstructPath(\n\tcollisionNode: NodeId,\n\tfrontierA: number,\n\tfrontierB: number,\n\tpredecessors: readonly Map<NodeId, NodeId | null>[],\n\tseeds: readonly Seed[],\n): ExpansionPath | null {\n\tconst pathA: NodeId[] = [collisionNode];\n\tconst predA = predecessors[frontierA];\n\tif (predA !== undefined) {\n\t\tlet node: NodeId | null | undefined = collisionNode;\n\t\tlet next: NodeId | null | undefined = predA.get(node);\n\t\twhile (next !== null && next !== undefined) {\n\t\t\tnode = next;\n\t\t\tpathA.unshift(node);\n\t\t\tnext = predA.get(node);\n\t\t}\n\t}\n\n\tconst pathB: NodeId[] = [];\n\tconst predB = predecessors[frontierB];\n\tif (predB !== undefined) {\n\t\tlet node: NodeId | null | undefined = collisionNode;\n\t\tlet next: NodeId | null | undefined = predB.get(node);\n\t\twhile (next !== null && next !== undefined) {\n\t\t\tnode = next;\n\t\t\tpathB.push(node);\n\t\t\tnext = predB.get(node);\n\t\t}\n\t}\n\n\tconst fullPath = [...pathA, ...pathB];\n\n\tconst seedA = seeds[frontierA];\n\tconst seedB = seeds[frontierB];\n\n\tif (seedA === undefined || seedB === undefined) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tfromSeed: seedA,\n\t\ttoSeed: seedB,\n\t\tnodes: fullPath,\n\t};\n}\n\n/**\n * Create an empty expansion result for early termination (e.g. no seeds given).\n *\n * @param algorithm - Name of the algorithm producing this result\n * @param startTime - performance.now() timestamp taken before the algorithm began\n * @returns An ExpansionResult with zero paths and zero stats\n */\nexport function emptyResult(\n\talgorithm: string,\n\tstartTime: number,\n): ExpansionResult {\n\treturn {\n\t\tpaths: [],\n\t\tsampledNodes: new Set(),\n\t\tsampledEdges: new Set(),\n\t\tvisitedPerFrontier: [],\n\t\tstats: {\n\t\t\titerations: 0,\n\t\t\tnodesVisited: 0,\n\t\t\tedgesTraversed: 0,\n\t\t\tpathsFound: 0,\n\t\t\tdurationMs: performance.now() - startTime,\n\t\t\talgorithm,\n\t\t\ttermination: \"exhausted\",\n\t\t},\n\t};\n}\n","/**\n * BASE generator core.\n *\n * Contains the expansion loop as a generator function that yields GraphOp\n * objects instead of calling the graph directly. This allows the same logic\n * to be driven by either `runSync` (for in-process graphs) or `runAsync`\n * (for remote/lazy graphs).\n *\n * Used by `base()` (via `runSync`) and `baseAsync()` (via `runAsync`).\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport { PriorityQueue } from \"../structures/priority-queue\";\nimport type { GraphOp, GraphOpResponse } from \"../async/protocol\";\nimport { opNeighbours, opDegree } from \"../async/ops\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionPath,\n\tExpansionStats,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport {\n\ttype QueueEntry,\n\ttype ExpansionLimits,\n\tcontinueExpansion,\n\treconstructPath,\n\temptyResult,\n} from \"./base-helpers\";\n\n/**\n * Default priority function — degree-ordered (DOME).\n *\n * Lower degree = higher priority, so sparse nodes are explored before hubs.\n */\nfunction degreePriority<N extends NodeData, E extends EdgeData>(\n\t_nodeId: NodeId,\n\tcontext: PriorityContext<N, E>,\n): number {\n\treturn context.degree;\n}\n\n/**\n * Generator core of the BASE expansion algorithm.\n *\n * Yields GraphOp objects to request graph data, allowing the caller to\n * provide a sync or async runner. The optional `graphRef` parameter is\n * required when the priority function accesses `context.graph` — it is\n * populated in sync mode by `base()`. In async mode (Phase 4+), a proxy\n * graph may be supplied instead.\n *\n * @param graphMeta - Immutable graph metadata (directed, nodeCount, edgeCount)\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration (priority, limits, debug)\n * @param graphRef - Optional real graph reference for context.graph in priority functions\n * @returns An ExpansionResult with all discovered paths and statistics\n */\nexport function* baseCore<N extends NodeData, E extends EdgeData>(\n\tgraphMeta: {\n\t\treadonly directed: boolean;\n\t\treadonly nodeCount: number;\n\t\treadonly edgeCount: number;\n\t},\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n\tgraphRef?: ReadableGraph<N, E>,\n): Generator<GraphOp, ExpansionResult, GraphOpResponse<N, E>> {\n\tconst startTime = performance.now();\n\n\tconst {\n\t\tmaxNodes = 0,\n\t\tmaxIterations = 0,\n\t\tmaxPaths = 0,\n\t\tpriority = degreePriority,\n\t\tdebug = false,\n\t} = config ?? {};\n\n\tif (seeds.length === 0) {\n\t\treturn emptyResult(\"base\", startTime);\n\t}\n\n\t// Initialise frontiers — one per seed\n\tconst numFrontiers = seeds.length;\n\tconst allVisited = new Set<NodeId>();\n\tconst combinedVisited = new Map<NodeId, number>();\n\tconst visitedByFrontier: Map<NodeId, number>[] = [];\n\tconst predecessors: Map<NodeId, NodeId | null>[] = [];\n\tconst queues: PriorityQueue<QueueEntry>[] = [];\n\n\tfor (let i = 0; i < numFrontiers; i++) {\n\t\tvisitedByFrontier.push(new Map());\n\t\tpredecessors.push(new Map());\n\t\tqueues.push(new PriorityQueue<QueueEntry>());\n\n\t\tconst seed = seeds[i];\n\t\tif (seed === undefined) continue;\n\n\t\tconst seedNode = seed.id;\n\t\t// Note: seed is NOT marked as visited here — it will be marked when processed\n\t\t// like any other node, so it can be properly expanded.\n\t\tpredecessors[i]?.set(seedNode, null);\n\t\tcombinedVisited.set(seedNode, i);\n\t\tallVisited.add(seedNode);\n\n\t\t// Yield to get the seed's degree for priority context\n\t\tconst seedDegree = yield* opDegree<N, E>(seedNode);\n\n\t\tconst context = buildPriorityContext<N, E>(\n\t\t\tseedNode,\n\t\t\ti,\n\t\t\tcombinedVisited,\n\t\t\tallVisited,\n\t\t\t[],\n\t\t\t0,\n\t\t\tseedDegree,\n\t\t\tgraphRef,\n\t\t);\n\n\t\tconst seedPriority = priority(seedNode, context);\n\t\tqueues[i]?.push(\n\t\t\t{\n\t\t\t\tnodeId: seedNode,\n\t\t\t\tfrontierIndex: i,\n\t\t\t\tpredecessor: null,\n\t\t\t},\n\t\t\tseedPriority,\n\t\t);\n\t}\n\n\tconst sampledEdgeMap = new Map<NodeId, Set<NodeId>>();\n\tconst discoveredPaths: ExpansionPath[] = [];\n\tlet iterations = 0;\n\tlet edgesTraversed = 0;\n\tlet termination: ExpansionStats[\"termination\"] = \"exhausted\";\n\n\tconst limits: ExpansionLimits = { maxIterations, maxNodes, maxPaths };\n\n\t// Main expansion loop — limits are checked at the start of each iteration\n\tfor (;;) {\n\t\tconst check = continueExpansion(\n\t\t\titerations,\n\t\t\tallVisited.size,\n\t\t\tdiscoveredPaths.length,\n\t\t\tlimits,\n\t\t);\n\t\tif (!check.shouldContinue) {\n\t\t\ttermination = check.termination;\n\t\t\tbreak;\n\t\t}\n\n\t\t// Find the frontier with the globally lowest-priority entry\n\t\tlet lowestPriority = Number.POSITIVE_INFINITY;\n\t\tlet activeFrontier = -1;\n\n\t\tfor (let i = 0; i < numFrontiers; i++) {\n\t\t\tconst queue = queues[i];\n\t\t\tif (queue !== undefined && !queue.isEmpty()) {\n\t\t\t\tconst peek = queue.peek();\n\t\t\t\tif (peek !== undefined && peek.priority < lowestPriority) {\n\t\t\t\t\tlowestPriority = peek.priority;\n\t\t\t\t\tactiveFrontier = i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// All queues empty — expansion exhausted\n\t\tif (activeFrontier < 0) {\n\t\t\ttermination = \"exhausted\";\n\t\t\tbreak;\n\t\t}\n\n\t\tconst queue = queues[activeFrontier];\n\t\tif (queue === undefined) break;\n\n\t\tconst entry = queue.pop();\n\t\tif (entry === undefined) break;\n\n\t\tconst { nodeId, predecessor } = entry.item;\n\n\t\t// Skip if already visited by this frontier\n\t\tconst frontierVisited = visitedByFrontier[activeFrontier];\n\t\tif (frontierVisited === undefined || frontierVisited.has(nodeId)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Mark visited\n\t\tfrontierVisited.set(nodeId, activeFrontier);\n\t\tcombinedVisited.set(nodeId, activeFrontier);\n\t\tif (predecessor !== null) {\n\t\t\tconst predMap = predecessors[activeFrontier];\n\t\t\tif (predMap !== undefined) {\n\t\t\t\tpredMap.set(nodeId, predecessor);\n\t\t\t}\n\t\t}\n\t\tallVisited.add(nodeId);\n\n\t\tif (debug) {\n\t\t\tconsole.log(\n\t\t\t\t`[BASE] Iteration ${String(iterations)}: Frontier ${String(activeFrontier)} visiting ${nodeId}`,\n\t\t\t);\n\t\t}\n\n\t\t// Check for collision with other frontiers\n\t\tfor (let otherFrontier = 0; otherFrontier < numFrontiers; otherFrontier++) {\n\t\t\tif (otherFrontier === activeFrontier) continue;\n\n\t\t\tconst otherVisited = visitedByFrontier[otherFrontier];\n\t\t\tif (otherVisited === undefined) continue;\n\n\t\t\tif (otherVisited.has(nodeId)) {\n\t\t\t\t// Collision! Reconstruct the path between the two frontiers.\n\t\t\t\tconst path = reconstructPath(\n\t\t\t\t\tnodeId,\n\t\t\t\t\tactiveFrontier,\n\t\t\t\t\totherFrontier,\n\t\t\t\t\tpredecessors,\n\t\t\t\t\tseeds,\n\t\t\t\t);\n\t\t\t\tif (path !== null) {\n\t\t\t\t\tdiscoveredPaths.push(path);\n\t\t\t\t\tif (debug) {\n\t\t\t\t\t\tconsole.log(`[BASE] Path found: ${path.nodes.join(\" -> \")}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Expand neighbours — yield to the runner to retrieve them\n\t\tconst neighbours = yield* opNeighbours<N, E>(nodeId);\n\t\tfor (const neighbour of neighbours) {\n\t\t\tedgesTraversed++;\n\n\t\t\t// Track the sampled edge (normalised so source < target)\n\t\t\tconst [s, t] =\n\t\t\t\tnodeId < neighbour ? [nodeId, neighbour] : [neighbour, nodeId];\n\t\t\tlet targets = sampledEdgeMap.get(s);\n\t\t\tif (targets === undefined) {\n\t\t\t\ttargets = new Set();\n\t\t\t\tsampledEdgeMap.set(s, targets);\n\t\t\t}\n\t\t\ttargets.add(t);\n\n\t\t\t// Skip if already visited by this frontier\n\t\t\tconst fv = visitedByFrontier[activeFrontier];\n\t\t\tif (fv === undefined || fv.has(neighbour)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Yield to get the neighbour's degree for priority context\n\t\t\tconst neighbourDegree = yield* opDegree<N, E>(neighbour);\n\n\t\t\tconst context = buildPriorityContext<N, E>(\n\t\t\t\tneighbour,\n\t\t\t\tactiveFrontier,\n\t\t\t\tcombinedVisited,\n\t\t\t\tallVisited,\n\t\t\t\tdiscoveredPaths,\n\t\t\t\titerations + 1,\n\t\t\t\tneighbourDegree,\n\t\t\t\tgraphRef,\n\t\t\t);\n\n\t\t\tconst neighbourPriority = priority(neighbour, context);\n\n\t\t\tqueue.push(\n\t\t\t\t{\n\t\t\t\t\tnodeId: neighbour,\n\t\t\t\t\tfrontierIndex: activeFrontier,\n\t\t\t\t\tpredecessor: nodeId,\n\t\t\t\t},\n\t\t\t\tneighbourPriority,\n\t\t\t);\n\t\t}\n\n\t\titerations++;\n\t}\n\n\tconst endTime = performance.now();\n\tconst visitedPerFrontier = visitedByFrontier.map((m) => new Set(m.keys()));\n\n\t// Convert sampled edges to readonly tuples\n\tconst edgeTuples = new Set<readonly [NodeId, NodeId]>();\n\tfor (const [source, targets] of sampledEdgeMap) {\n\t\tfor (const target of targets) {\n\t\t\tedgeTuples.add([source, target] as const);\n\t\t}\n\t}\n\n\treturn {\n\t\tpaths: discoveredPaths,\n\t\tsampledNodes: allVisited,\n\t\tsampledEdges: edgeTuples,\n\t\tvisitedPerFrontier,\n\t\tstats: {\n\t\t\titerations,\n\t\t\tnodesVisited: allVisited.size,\n\t\t\tedgesTraversed,\n\t\t\tpathsFound: discoveredPaths.length,\n\t\t\tdurationMs: endTime - startTime,\n\t\t\talgorithm: \"base\",\n\t\t\ttermination,\n\t\t},\n\t};\n}\n\n/**\n * Create a sentinel ReadableGraph that throws if any member is accessed.\n *\n * Used in async mode when no graphRef is provided. Gives a clear error\n * message rather than silently returning incorrect results if a priority\n * function attempts to access `context.graph` before Phase 4b introduces\n * a real async proxy.\n */\nfunction makeNoGraphSentinel<\n\tN extends NodeData,\n\tE extends EdgeData,\n>(): ReadableGraph<N, E> {\n\tconst msg =\n\t\t\"Priority function accessed context.graph in async mode without a graph proxy. \" +\n\t\t\"Pass a graphRef or use a priority function that does not access context.graph.\";\n\tconst fail = (): never => {\n\t\tthrow new Error(msg);\n\t};\n\treturn {\n\t\tget directed(): boolean {\n\t\t\treturn fail();\n\t\t},\n\t\tget nodeCount(): number {\n\t\t\treturn fail();\n\t\t},\n\t\tget edgeCount(): number {\n\t\t\treturn fail();\n\t\t},\n\t\thasNode: fail,\n\t\tgetNode: fail,\n\t\tnodeIds: fail,\n\t\tneighbours: fail,\n\t\tdegree: fail,\n\t\tgetEdge: fail,\n\t\tedges: fail,\n\t};\n}\n\n/**\n * Build a PriorityContext for a node using a pre-fetched degree.\n *\n * When `graphRef` is provided (sync mode), it is used as `context.graph` so\n * priority functions can access the graph directly. When it is absent (async\n * mode), a Proxy is used in its place that throws a clear error if any\n * property is accessed — this prevents silent failures until Phase 4b\n * introduces a real async proxy graph.\n */\nfunction buildPriorityContext<N extends NodeData, E extends EdgeData>(\n\t_nodeId: NodeId,\n\tfrontierIndex: number,\n\tcombinedVisited: ReadonlyMap<NodeId, number>,\n\tallVisited: ReadonlySet<NodeId>,\n\tdiscoveredPaths: readonly ExpansionPath[],\n\titeration: number,\n\tdegree: number,\n\tgraphRef: ReadableGraph<N, E> | undefined,\n): PriorityContext<N, E> {\n\t// Resolve the graph reference. In async mode without a proxy, we construct\n\t// a sentinel that satisfies the ReadableGraph<N, E> interface structurally\n\t// but throws a clear error if any method is called. Phase 4b will replace\n\t// this with a real async proxy graph.\n\tconst graph: ReadableGraph<N, E> = graphRef ?? makeNoGraphSentinel<N, E>();\n\n\treturn {\n\t\tgraph,\n\t\tdegree,\n\t\tfrontierIndex,\n\t\tvisitedByFrontier: combinedVisited,\n\t\tallVisited,\n\t\tdiscoveredPaths,\n\t\titeration,\n\t};\n}\n","/**\n * BASE (Bidirectional Adaptive Seed Expansion) entry points.\n *\n * Provides synchronous `base()` and asynchronous `baseAsync()` entry points.\n * Both delegate to the shared `baseCore` generator, which yields GraphOp\n * objects instead of calling the graph directly.\n *\n * - `base()` drives the generator via `runSync` (zero overhead vs the old impl)\n * - `baseAsync()` drives the generator via `runAsync` (supports cancellation\n * and progress callbacks)\n *\n * Key properties:\n * 1. Priority-ordered exploration — global min-priority across all frontiers\n * 2. Frontier collision detection — path recorded when frontiers meet\n * 3. Implicit termination — halts when all queues empty\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport { runSync, runAsync } from \"../async/runners\";\nimport type { AsyncRunnerOptions, YieldStrategy } from \"../async/types\";\nimport type { ProgressStats } from \"../async/protocol\";\nimport { baseCore } from \"./base-core\";\nimport type { Seed, ExpansionResult, ExpansionConfig } from \"./types\";\n\n/**\n * Configuration for the async BASE expansion algorithm.\n *\n * Extends ExpansionConfig with async runner options (cancellation, progress,\n * yield strategy).\n */\nexport interface AsyncExpansionConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>\n\textends ExpansionConfig<N, E>, AsyncRunnerOptions {}\n\n/**\n * Run BASE expansion synchronously.\n *\n * Delegates to baseCore + runSync. Behaviour is identical to the previous\n * direct implementation — all existing callers are unaffected.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function base<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\tconst gen = baseCore<N, E>(\n\t\t{\n\t\t\tdirected: graph.directed,\n\t\t\tnodeCount: graph.nodeCount,\n\t\t\tedgeCount: graph.edgeCount,\n\t\t},\n\t\tseeds,\n\t\tconfig,\n\t\tgraph,\n\t);\n\treturn runSync(gen, graph);\n}\n\n/**\n * Run BASE expansion asynchronously.\n *\n * Delegates to baseCore + runAsync. Supports:\n * - Cancellation via AbortSignal (config.signal)\n * - Progress callbacks (config.onProgress)\n * - Custom cooperative yield strategies (config.yieldStrategy)\n *\n * Note: priority functions that access `context.graph` are not supported in\n * async mode without a graph proxy (Phase 4b). The default degree-based\n * priority (DOME) does not access context.graph and works correctly.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function baseAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\tconst [nodeCount, edgeCount] = await Promise.all([\n\t\tgraph.nodeCount,\n\t\tgraph.edgeCount,\n\t]);\n\tconst gen = baseCore<N, E>(\n\t\t{ directed: graph.directed, nodeCount, edgeCount },\n\t\tseeds,\n\t\tconfig,\n\t\t// No graphRef in async mode — priority functions that access\n\t\t// context.graph will receive the sentinel and throw. Phase 4b will\n\t\t// inject a real async proxy graph here.\n\t);\n\t// Build runner options conditionally to satisfy exactOptionalPropertyTypes:\n\t// optional properties must be omitted entirely rather than set to undefined.\n\tconst runnerOptions: {\n\t\tsignal?: AbortSignal;\n\t\tonProgress?: (stats: ProgressStats) => void | Promise<void>;\n\t\tyieldStrategy?: YieldStrategy;\n\t} = {};\n\tif (config?.signal !== undefined) {\n\t\trunnerOptions.signal = config.signal;\n\t}\n\tif (config?.onProgress !== undefined) {\n\t\trunnerOptions.onProgress = config.onProgress;\n\t}\n\tif (config?.yieldStrategy !== undefined) {\n\t\trunnerOptions.yieldStrategy = config.yieldStrategy;\n\t}\n\treturn runAsync(gen, graph, runnerOptions);\n}\n","/**\n * DOME (Degree-Ordered Multi-Expansion) algorithm.\n *\n * Simplest BASE variant: priority = node degree.\n * Lower degree nodes are expanded first (can be reversed via config).\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base, baseAsync } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\n\n/**\n * DOME priority: lower degree is expanded first.\n */\nfunction domePriority<N extends NodeData, E extends EdgeData>(\n\t_nodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\treturn context.degree;\n}\n\n/**\n * DOME high-degree priority: negate degree to prioritise high-degree nodes.\n */\nfunction domeHighDegreePriority<N extends NodeData, E extends EdgeData>(\n\t_nodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\treturn -context.degree;\n}\n\n/**\n * Run DOME expansion (degree-ordered).\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function dome<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: domePriority,\n\t});\n}\n\n/**\n * Run DOME expansion asynchronously (degree-ordered).\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function domeAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn baseAsync(graph, seeds, { ...config, priority: domePriority });\n}\n\n/**\n * DOME with reverse priority (high degree first).\n */\nexport function domeHighDegree<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: domeHighDegreePriority,\n\t});\n}\n\n/**\n * Run DOME high-degree expansion asynchronously (high degree first).\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function domeHighDegreeAsync<\n\tN extends NodeData,\n\tE extends EdgeData,\n>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn baseAsync(graph, seeds, {\n\t\t...config,\n\t\tpriority: domeHighDegreePriority,\n\t});\n}\n","/**\n * HAE (Heterogeneity-Aware Expansion) algorithm.\n *\n * Generalises EDGE with user-supplied type mapper for flexible type extraction.\n * Priority function: π(v) = (1 / (H_local(v) + ε)) × log(deg(v) + 1)\n *\n * where H_local(v) = Shannon entropy of the neighbour type distribution\n * computed using the provided typeMapper function.\n *\n * Allows custom type extraction beyond node.type.\n * Degenerates to DOME on homogeneous graphs (all types the same).\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { localTypeEntropy } from \"../utils/entropy\";\n\nconst EPSILON = 1e-10;\n\n/**\n * Configuration for HAE, extending base ExpansionConfig.\n */\nexport interface HAEConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> extends ExpansionConfig<N, E> {\n\t/** Function to extract type from a node (default: node.type) */\n\treadonly typeMapper?: (node: N) => string;\n}\n\n/**\n * Default type mapper - uses node.type property.\n */\nfunction defaultTypeMapper(node: NodeData): string {\n\treturn node.type ?? \"default\";\n}\n\n/**\n * Create a priority function using the given type mapper.\n */\nfunction createHAEPriority<N extends NodeData, E extends EdgeData>(\n\ttypeMapper: (node: N) => string,\n) {\n\treturn function haePriority(\n\t\tnodeId: string,\n\t\tcontext: PriorityContext<N, E>,\n\t): number {\n\t\tconst graph = context.graph;\n\t\tconst neighbours = graph.neighbours(nodeId);\n\n\t\t// Collect neighbour types using the custom mapper\n\t\tconst neighbourTypes: string[] = [];\n\t\tfor (const neighbour of neighbours) {\n\t\t\tconst node = graph.getNode(neighbour);\n\t\t\tif (node !== undefined) {\n\t\t\t\tneighbourTypes.push(typeMapper(node));\n\t\t\t}\n\t\t}\n\n\t\t// Compute local type entropy\n\t\tconst entropy = localTypeEntropy(neighbourTypes);\n\n\t\t// Priority = 1 / (entropy + ε) * log(degree + 1)\n\t\treturn (1 / (entropy + EPSILON)) * Math.log(context.degree + 1);\n\t};\n}\n\n/**\n * Run HAE expansion (Heterogeneity-Aware Expansion).\n *\n * Discovers paths by prioritising nodes with diverse neighbour types,\n * using a custom type mapper for flexible type extraction.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - HAE configuration with optional typeMapper\n * @returns Expansion result with discovered paths\n */\nexport function hae<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: HAEConfig<N, E>,\n): ExpansionResult {\n\tconst typeMapper = config?.typeMapper ?? defaultTypeMapper;\n\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: createHAEPriority<N, E>(typeMapper),\n\t});\n}\n\n/**\n * Run HAE expansion asynchronously.\n *\n * Note: the HAE priority function accesses `context.graph` to retrieve\n * neighbour types. Full async equivalence requires PriorityContext\n * refactoring (Phase 4b deferred). This export establishes the async API\n * surface; use with a `wrapAsync`-wrapped sync graph for testing.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - HAE configuration combined with async runner options\n * @returns Promise resolving to the expansion result\n */\nexport async function haeAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: HAEConfig<N, E> & AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\tconst typeMapper = config?.typeMapper ?? defaultTypeMapper;\n\n\treturn baseAsync(graph, seeds, {\n\t\t...config,\n\t\tpriority: createHAEPriority<N, E>(typeMapper),\n\t});\n}\n","/**\n * EDGE (Entropy-Driven Graph Expansion) algorithm.\n *\n * Discovers paths by prioritising nodes with diverse neighbour types.\n * Priority function: π(v) = (1 / (H_local(v) + ε)) × log(deg(v) + 1)\n *\n * where H_local(v) = Shannon entropy of the neighbour type distribution.\n *\n * High entropy (diverse types) → lower priority → expanded sooner.\n * Low entropy (homogeneous types) → higher priority → deferred.\n *\n * Delegates to HAE with the default `node.type` mapper.\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type { Seed, ExpansionResult, ExpansionConfig } from \"./types\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { hae, haeAsync } from \"./hae\";\n\n/** Default type mapper: reads `node.type`, falling back to \"default\". */\nconst defaultTypeMapper = (n: NodeData): string =>\n\ttypeof n.type === \"string\" ? n.type : \"default\";\n\n/**\n * Run EDGE expansion (Entropy-Driven Graph Expansion).\n *\n * Discovers paths by prioritising nodes with diverse neighbour types,\n * deferring nodes with homogeneous neighbourhoods.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function edge<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn hae(graph, seeds, {\n\t\t...config,\n\t\ttypeMapper: defaultTypeMapper,\n\t});\n}\n\n/**\n * Run EDGE expansion asynchronously.\n *\n * Delegates to `haeAsync` with the default `node.type` mapper.\n *\n * Note: the HAE priority function accesses `context.graph` to retrieve\n * neighbour types. Full async equivalence requires PriorityContext\n * refactoring (Phase 4b deferred). This export establishes the async API\n * surface; use with a `wrapAsync`-wrapped sync graph for testing.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function edgeAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn haeAsync(graph, seeds, {\n\t\t...config,\n\t\ttypeMapper: defaultTypeMapper,\n\t});\n}\n","/**\n * PIPE (Path-Potential Informed Priority Expansion) algorithm.\n *\n * Discovers paths by prioritising nodes that bridge multiple frontiers.\n * Priority function: π(v) = deg(v) / (1 + path_potential(v))\n *\n * where path_potential(v) = count of v's neighbours already visited by OTHER seed frontiers.\n *\n * High path potential (many bridges to other frontiers) → lower priority → expanded sooner.\n * Low path potential (few bridges) → higher priority → deferred.\n *\n * This incentivises discovery of paths that connect multiple frontiers.\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\n\n/**\n * Priority function using path potential.\n * Lower values = higher priority (expanded first).\n *\n * Path potential measures how many of a node's neighbours have been\n * visited by OTHER frontiers (not the current frontier).\n */\nfunction pipePriority<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\tconst graph = context.graph;\n\tconst neighbours = graph.neighbours(nodeId);\n\n\t// Count how many neighbours have been visited by OTHER frontiers\n\tlet pathPotential = 0;\n\tfor (const neighbour of neighbours) {\n\t\tconst visitedBy = context.visitedByFrontier.get(neighbour);\n\t\tif (visitedBy !== undefined && visitedBy !== context.frontierIndex) {\n\t\t\tpathPotential++;\n\t\t}\n\t}\n\n\t// Priority = degree / (1 + path_potential)\n\t// High path potential → lower priority (expanded sooner)\n\treturn context.degree / (1 + pathPotential);\n}\n\n/**\n * Run PIPE expansion (Path-Potential Informed Priority Expansion).\n *\n * Discovers paths by prioritising nodes that bridge multiple frontiers,\n * identifying connecting points between seed regions.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function pipe<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: pipePriority,\n\t});\n}\n\n/**\n * Run PIPE expansion asynchronously.\n *\n * Note: the PIPE priority function accesses `context.graph` to retrieve\n * neighbour lists. Full async equivalence requires PriorityContext\n * refactoring (Phase 4b deferred). This export establishes the async API\n * surface; use with a `wrapAsync`-wrapped sync graph for testing.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function pipeAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn baseAsync(graph, seeds, {\n\t\t...config,\n\t\tpriority: pipePriority,\n\t});\n}\n","/**\n * Shared helper functions for expansion algorithm priority computations.\n *\n * These utilities encapsulate repeated computation patterns across\n * multiple expansion algorithms.\n *\n * @module expansion/priority-helpers\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { PriorityContext, ExpansionPath } from \"./types\";\n\n/**\n * Compute the average mutual information between a node and all visited\n * nodes in the same frontier.\n *\n * Returns a value in [0, 1] — higher means the node is more similar\n * (on average) to already-visited same-frontier nodes.\n *\n * @param graph - Source graph\n * @param nodeId - Node being prioritised\n * @param context - Current priority context\n * @param mi - MI function to use for pairwise scoring\n * @returns Average MI score, or 0 if no same-frontier visited nodes exist\n */\nexport function avgFrontierMI<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tnodeId: NodeId,\n\tcontext: PriorityContext<N, E>,\n\tmi: (graph: ReadableGraph<N, E>, source: string, target: string) => number,\n): number {\n\tconst { frontierIndex, visitedByFrontier } = context;\n\n\tlet total = 0;\n\tlet count = 0;\n\n\tfor (const [visitedId, idx] of visitedByFrontier) {\n\t\tif (idx === frontierIndex && visitedId !== nodeId) {\n\t\t\ttotal += mi(graph, visitedId, nodeId);\n\t\t\tcount++;\n\t\t}\n\t}\n\n\treturn count > 0 ? total / count : 0;\n}\n\n/**\n * Count the number of a node's neighbours that have been visited by\n * frontiers other than the node's own frontier.\n *\n * A higher count indicates this node is likely to bridge two frontiers,\n * making it a strong candidate for path completion.\n *\n * @param graph - Source graph\n * @param nodeId - Node being evaluated\n * @param context - Current priority context\n * @returns Number of neighbours visited by other frontiers\n */\nexport function countCrossFrontierNeighbours<\n\tN extends NodeData,\n\tE extends EdgeData,\n>(\n\tgraph: ReadableGraph<N, E>,\n\tnodeId: NodeId,\n\tcontext: PriorityContext<N, E>,\n): number {\n\tconst { frontierIndex, visitedByFrontier } = context;\n\tconst nodeNeighbours = new Set(graph.neighbours(nodeId));\n\n\tlet count = 0;\n\tfor (const [visitedId, idx] of visitedByFrontier) {\n\t\tif (idx !== frontierIndex && nodeNeighbours.has(visitedId)) {\n\t\t\tcount++;\n\t\t}\n\t}\n\n\treturn count;\n}\n\n/**\n * Incrementally update salience counts for paths discovered since the\n * last update.\n *\n * Iterates only over paths from `fromIndex` onwards, avoiding redundant\n * re-processing of already-counted paths.\n *\n * @param salienceCounts - Mutable map of node ID to salience count (mutated in place)\n * @param paths - Full list of discovered paths\n * @param fromIndex - Index to start counting from (exclusive of earlier paths)\n * @returns The new `fromIndex` value (i.e. `paths.length` after update)\n */\nexport function updateSalienceCounts(\n\tsalienceCounts: Map<NodeId, number>,\n\tpaths: readonly ExpansionPath[],\n\tfromIndex: number,\n): number {\n\tfor (let i = fromIndex; i < paths.length; i++) {\n\t\tconst path = paths[i];\n\t\tif (path !== undefined) {\n\t\t\tfor (const node of path.nodes) {\n\t\t\t\tsalienceCounts.set(node, (salienceCounts.get(node) ?? 0) + 1);\n\t\t\t}\n\t\t}\n\t}\n\treturn paths.length;\n}\n","/**\n * SAGE (Salience-Accumulation Guided Expansion) algorithm.\n *\n * Two-phase expansion algorithm that tracks how often nodes appear\n * in discovered paths and uses this salience to guide expansion.\n *\n * Phase 1 (before first path): priority = log(degree + 1)\n * Phase 2 (after first path): priority = -(salience(v) × 1000 - degree)\n *\n * where salience(v) = count of discovered paths containing v\n *\n * In phase 2, nodes that appear frequently in paths are deprioritised,\n * encouraging exploration of fresh frontier regions.\n *\n * @module expansion/sage\n */\n\nimport type { NodeData, EdgeData, ReadableGraph, NodeId } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { updateSalienceCounts } from \"./priority-helpers\";\n\n/**\n * Run SAGE expansion algorithm.\n *\n * Salience-aware multi-frontier expansion with two phases:\n * - Phase 1: Degree-based priority (early exploration)\n * - Phase 2: Salience feedback (path-aware frontier steering)\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function sage<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\t// Closure state: encapsulate phase tracking and salience counts per call\n\tconst salienceCounts = new Map<NodeId, number>();\n\tlet inPhase2 = false;\n\tlet lastPathCount = 0;\n\n\t/**\n\t * SAGE priority function with phase transition logic.\n\t */\n\tfunction sagePriority(\n\t\tnodeId: NodeId,\n\t\tcontext: PriorityContext<N, E>,\n\t): number {\n\t\tconst pathCount = context.discoveredPaths.length;\n\n\t\t// Detect phase transition: first path discovered\n\t\tif (pathCount > 0 && !inPhase2) {\n\t\t\tinPhase2 = true;\n\t\t\t// No scan needed — the incremental update below handles it\n\t\t}\n\n\t\t// Update salience counts for newly discovered paths\n\t\tif (pathCount > lastPathCount) {\n\t\t\tlastPathCount = updateSalienceCounts(\n\t\t\t\tsalienceCounts,\n\t\t\t\tcontext.discoveredPaths,\n\t\t\t\tlastPathCount,\n\t\t\t);\n\t\t}\n\n\t\t// Phase 1: Degree-based priority before first path\n\t\tif (!inPhase2) {\n\t\t\treturn Math.log(context.degree + 1);\n\t\t}\n\n\t\t// Phase 2: Salience-guided priority\n\t\t// Nodes with high salience are deprioritised, fresh nodes get lower priority\n\t\tconst salience = salienceCounts.get(nodeId) ?? 0;\n\t\treturn -(salience * 1000 - context.degree);\n\t}\n\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: sagePriority,\n\t});\n}\n\n/**\n * Run SAGE expansion asynchronously.\n *\n * Creates fresh closure state (salienceCounts, phase tracking) for this\n * invocation. The SAGE priority function does not access `context.graph`\n * directly, so it is safe to use in async mode via `baseAsync`.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function sageAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\t// Fresh closure state — independent of any concurrent sync invocations\n\tconst salienceCounts = new Map<NodeId, number>();\n\tlet inPhase2 = false;\n\tlet lastPathCount = 0;\n\n\tfunction sagePriority(\n\t\tnodeId: NodeId,\n\t\tcontext: PriorityContext<N, E>,\n\t): number {\n\t\tconst pathCount = context.discoveredPaths.length;\n\n\t\tif (pathCount > 0 && !inPhase2) {\n\t\t\tinPhase2 = true;\n\t\t}\n\n\t\tif (pathCount > lastPathCount) {\n\t\t\tlastPathCount = updateSalienceCounts(\n\t\t\t\tsalienceCounts,\n\t\t\t\tcontext.discoveredPaths,\n\t\t\t\tlastPathCount,\n\t\t\t);\n\t\t}\n\n\t\tif (!inPhase2) {\n\t\t\treturn Math.log(context.degree + 1);\n\t\t}\n\n\t\tconst salience = salienceCounts.get(nodeId) ?? 0;\n\t\treturn -(salience * 1000 - context.degree);\n\t}\n\n\treturn baseAsync(graph, seeds, { ...config, priority: sagePriority });\n}\n","/**\n * REACH (Retrospective Expansion with Adaptive Convergence Heuristic) algorithm.\n *\n * Two-phase expansion algorithm that computes mean Jaccard similarity\n * between candidate nodes and discovered path endpoints, using this\n * mutual information estimate to guide expansion.\n *\n * Phase 1 (before first path): priority = log(degree + 1)\n * Phase 2 (after first path): priority = log(degree + 1) × (1 - MI_hat(v))\n *\n * where MI_hat(v) = mean Jaccard(N(v), N(endpoint)) over all discovered\n * path endpoints (source and target of each path).\n *\n * In phase 2, nodes with high neighbourhood similarity to path endpoints\n * are deprioritised, encouraging discovery of structurally dissimilar paths.\n *\n * @module expansion/reach\n */\n\nimport type { NodeData, EdgeData, ReadableGraph, NodeId } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { jaccard } from \"../ranking/mi/jaccard\";\n\n/**\n * Run REACH expansion algorithm.\n *\n * Mutual information-aware multi-frontier expansion with two phases:\n * - Phase 1: Degree-based priority (early exploration)\n * - Phase 2: Structural similarity feedback (MI-guided frontier steering)\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function reach<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\t// Closure state: encapsulate phase tracking\n\tlet inPhase2 = false;\n\n\t// Closure state: cache Jaccard scores by (source, target) key\n\t// Symmetric property ensures consistent ordering\n\tconst jaccardCache = new Map<string, number>();\n\n\t/**\n\t * Compute Jaccard similarity with caching.\n\t *\n\t * Exploits symmetry of Jaccard (J(A,B) = J(B,A)) to reduce\n\t * duplicate computations when the same pair appears in multiple\n\t * discovered paths. Key format ensures consistent ordering.\n\t */\n\tfunction cachedJaccard(source: NodeId, target: NodeId): number {\n\t\t// Symmetric key: consistent ordering ensures cache hits\n\t\tconst key =\n\t\t\tsource < target ? `${source}::${target}` : `${target}::${source}`;\n\n\t\tlet score = jaccardCache.get(key);\n\t\tif (score === undefined) {\n\t\t\tscore = jaccard(graph, source, target);\n\t\t\tjaccardCache.set(key, score);\n\t\t}\n\n\t\treturn score;\n\t}\n\n\t/**\n\t * REACH priority function with MI estimation.\n\t */\n\tfunction reachPriority(\n\t\tnodeId: NodeId,\n\t\tcontext: PriorityContext<N, E>,\n\t): number {\n\t\tconst pathCount = context.discoveredPaths.length;\n\n\t\t// Detect phase transition: first path discovered\n\t\tif (pathCount > 0 && !inPhase2) {\n\t\t\tinPhase2 = true;\n\t\t}\n\n\t\t// Phase 1: Degree-based priority before first path\n\t\tif (!inPhase2) {\n\t\t\treturn Math.log(context.degree + 1);\n\t\t}\n\n\t\t// Phase 2: Compute MI_hat(v) = mean Jaccard to all discovered path endpoints\n\t\t// Collect all endpoint nodes from discovered paths\n\t\tlet totalMI = 0;\n\t\tlet endpointCount = 0;\n\n\t\tfor (const path of context.discoveredPaths) {\n\t\t\tconst fromNodeId = path.fromSeed.id;\n\t\t\tconst toNodeId = path.toSeed.id;\n\n\t\t\t// Compute Jaccard similarity between candidate node and each endpoint\n\t\t\t// Using cached variant to avoid recomputing identical pairs\n\t\t\ttotalMI += cachedJaccard(nodeId, fromNodeId);\n\t\t\ttotalMI += cachedJaccard(nodeId, toNodeId);\n\t\t\tendpointCount += 2;\n\t\t}\n\n\t\tconst miHat = endpointCount > 0 ? totalMI / endpointCount : 0;\n\n\t\t// Phase 2 priority: degree-weighted by dissimilarity\n\t\t// Lower MI → lower priority value → expanded first\n\t\treturn Math.log(context.degree + 1) * (1 - miHat);\n\t}\n\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: reachPriority,\n\t});\n}\n\n/**\n * Run REACH expansion asynchronously.\n *\n * Creates fresh closure state (phase tracking, Jaccard cache) for this\n * invocation. The REACH priority function uses `jaccard(context.graph, ...)`\n * in Phase 2; in async mode `context.graph` is the sentinel and will throw.\n * Full async equivalence requires PriorityContext refactoring (Phase 4b\n * deferred). This export establishes the async API surface.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function reachAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\t// Fresh closure state — independent of any concurrent sync invocations\n\tlet inPhase2 = false;\n\n\t// Note: in Phase 2, jaccard(context.graph, ...) is called. In async mode,\n\t// context.graph is the sentinel and will throw. Phase 4b will resolve this.\n\tfunction reachPriority(\n\t\tnodeId: NodeId,\n\t\tcontext: PriorityContext<N, E>,\n\t): number {\n\t\tconst pathCount = context.discoveredPaths.length;\n\n\t\tif (pathCount > 0 && !inPhase2) {\n\t\t\tinPhase2 = true;\n\t\t}\n\n\t\tif (!inPhase2) {\n\t\t\treturn Math.log(context.degree + 1);\n\t\t}\n\n\t\tlet totalMI = 0;\n\t\tlet endpointCount = 0;\n\n\t\tfor (const path of context.discoveredPaths) {\n\t\t\t// context.graph is the sentinel in async mode — throws on access\n\t\t\ttotalMI += jaccard(context.graph, nodeId, path.fromSeed.id);\n\t\t\ttotalMI += jaccard(context.graph, nodeId, path.toSeed.id);\n\t\t\tendpointCount += 2;\n\t\t}\n\n\t\tconst miHat = endpointCount > 0 ? totalMI / endpointCount : 0;\n\t\treturn Math.log(context.degree + 1) * (1 - miHat);\n\t}\n\n\treturn baseAsync(graph, seeds, { ...config, priority: reachPriority });\n}\n","/**\n * MAZE (Multi-frontier Adaptive Zone) algorithm.\n *\n * Three-phase expansion algorithm combining path potential (PIPE) and\n * salience feedback (SAGE) with adaptive termination criteria.\n *\n * Phase 1 (before M paths found): π(v) = deg(v) / (1 + path_potential(v))\n * where path_potential(v) = count of neighbours visited by other frontiers\n *\n * Phase 2 (after M paths, salience feedback):\n * π(v) = [deg/(1+path_potential)] × [1/(1+λ×salience(v))]\n * where salience(v) = count of discovered paths containing v, λ = 1000\n *\n * Phase 3: Adaptive termination when combination of:\n * - Path count plateau (no new paths in recent iterations)\n * - Salience distribution stabilisation\n * - Frontier diversity convergence\n *\n * Simplified implementation: Phase 1 uses path potential, phase 2 adds\n * salience weighting, implicit phase 3 via collision detection.\n *\n * @module expansion/maze\n */\n\nimport type { NodeData, EdgeData, ReadableGraph, NodeId } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { updateSalienceCounts } from \"./priority-helpers\";\n\n/** Default threshold for switching to phase 2 (after M paths) */\nconst DEFAULT_PHASE2_THRESHOLD = 1;\n\n/** Salience weighting factor */\nconst SALIENCE_WEIGHT = 1000;\n\n/**\n * Run MAZE expansion algorithm.\n *\n * Multi-phase expansion combining path potential and salience with\n * adaptive frontier steering.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function maze<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\t// Closure state: encapsulate phase tracking and salience counts\n\tconst salienceCounts = new Map<NodeId, number>();\n\tlet inPhase2 = false;\n\tlet lastPathCount = 0;\n\n\t/**\n\t * MAZE priority function with path potential and salience feedback.\n\t */\n\tfunction mazePriority(\n\t\tnodeId: NodeId,\n\t\tcontext: PriorityContext<N, E>,\n\t): number {\n\t\tconst pathCount = context.discoveredPaths.length;\n\n\t\t// Detect phase transition: threshold of paths reached\n\t\tif (pathCount >= DEFAULT_PHASE2_THRESHOLD && !inPhase2) {\n\t\t\tinPhase2 = true;\n\t\t\t// Initialise salience counts from all existing paths\n\t\t\tupdateSalienceCounts(salienceCounts, context.discoveredPaths, 0);\n\t\t}\n\n\t\t// Incrementally update salience counts for newly discovered paths in phase 2\n\t\tif (inPhase2 && pathCount > lastPathCount) {\n\t\t\tlastPathCount = updateSalienceCounts(\n\t\t\t\tsalienceCounts,\n\t\t\t\tcontext.discoveredPaths,\n\t\t\t\tlastPathCount,\n\t\t\t);\n\t\t}\n\n\t\t// Compute path potential: neighbours visited by other frontiers\n\t\t// This is a bridge score indicating how likely this node is on important paths\n\t\tconst nodeNeighbours = graph.neighbours(nodeId);\n\t\tlet pathPotential = 0;\n\n\t\tfor (const neighbour of nodeNeighbours) {\n\t\t\tconst visitedBy = context.visitedByFrontier.get(neighbour);\n\t\t\tif (visitedBy !== undefined && visitedBy !== context.frontierIndex) {\n\t\t\t\tpathPotential++;\n\t\t\t}\n\t\t}\n\n\t\t// Phase 1: Path potential-based priority\n\t\t// Lower degree and high path potential = high priority (expanded first)\n\t\tif (!inPhase2) {\n\t\t\treturn context.degree / (1 + pathPotential);\n\t\t}\n\n\t\t// Phase 2: Salience-weighted path potential\n\t\t// Nodes on existing paths (high salience) are deprioritised\n\t\tconst salience = salienceCounts.get(nodeId) ?? 0;\n\t\tconst basePriority = context.degree / (1 + pathPotential);\n\t\tconst salienceFactor = 1 / (1 + SALIENCE_WEIGHT * salience);\n\n\t\treturn basePriority * salienceFactor;\n\t}\n\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: mazePriority,\n\t});\n}\n\n/**\n * Run MAZE expansion asynchronously.\n *\n * Creates fresh closure state (salienceCounts, phase tracking) for this\n * invocation. The MAZE priority function accesses `context.graph` to\n * retrieve neighbour lists for path potential computation. Full async\n * equivalence requires PriorityContext refactoring (Phase 4b deferred).\n * This export establishes the async API surface.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function mazeAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\t// Fresh closure state — independent of any concurrent sync invocations\n\tconst salienceCounts = new Map<NodeId, number>();\n\tlet inPhase2 = false;\n\tlet lastPathCount = 0;\n\n\tfunction mazePriority(\n\t\tnodeId: NodeId,\n\t\tcontext: PriorityContext<N, E>,\n\t): number {\n\t\tconst pathCount = context.discoveredPaths.length;\n\n\t\tif (pathCount >= DEFAULT_PHASE2_THRESHOLD && !inPhase2) {\n\t\t\tinPhase2 = true;\n\t\t\tupdateSalienceCounts(salienceCounts, context.discoveredPaths, 0);\n\t\t}\n\n\t\tif (inPhase2 && pathCount > lastPathCount) {\n\t\t\tlastPathCount = updateSalienceCounts(\n\t\t\t\tsalienceCounts,\n\t\t\t\tcontext.discoveredPaths,\n\t\t\t\tlastPathCount,\n\t\t\t);\n\t\t}\n\n\t\t// context.graph is the sentinel in pure async mode — Phase 4b will resolve this\n\t\tconst nodeNeighbours = context.graph.neighbours(nodeId);\n\t\tlet pathPotential = 0;\n\n\t\tfor (const neighbour of nodeNeighbours) {\n\t\t\tconst visitedBy = context.visitedByFrontier.get(neighbour);\n\t\t\tif (visitedBy !== undefined && visitedBy !== context.frontierIndex) {\n\t\t\t\tpathPotential++;\n\t\t\t}\n\t\t}\n\n\t\tif (!inPhase2) {\n\t\t\treturn context.degree / (1 + pathPotential);\n\t\t}\n\n\t\tconst salience = salienceCounts.get(nodeId) ?? 0;\n\t\tconst basePriority = context.degree / (1 + pathPotential);\n\t\tconst salienceFactor = 1 / (1 + SALIENCE_WEIGHT * salience);\n\n\t\treturn basePriority * salienceFactor;\n\t}\n\n\treturn baseAsync(graph, seeds, { ...config, priority: mazePriority });\n}\n","/**\n * TIDE (Type-Integrated Degree Estimation) algorithm.\n *\n * Prioritises exploration by edge degree rather than node degree.\n * Expands edges with lower combined endpoint degrees first.\n *\n * Useful for finding paths through sparse regions of the graph,\n * avoiding dense clusters.\n *\n * @module expansion/tide\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\n\n/**\n * TIDE priority function.\n *\n * Priority = degree(source) + degree(target)\n * Lower values = higher priority (explored first)\n */\nfunction tidePriority<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\t// Sum of source degree and neighbour degrees\n\tconst graph = context.graph;\n\tlet totalDegree = context.degree;\n\n\tfor (const neighbour of graph.neighbours(nodeId)) {\n\t\ttotalDegree += graph.degree(neighbour);\n\t}\n\n\treturn totalDegree;\n}\n\n/**\n * Run TIDE expansion algorithm.\n *\n * Expands from seeds prioritising low-degree edges first.\n * Useful for avoiding hubs and exploring sparse regions.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function tide<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: tidePriority,\n\t});\n}\n\n/**\n * Run TIDE expansion asynchronously.\n *\n * Note: the TIDE priority function accesses `context.graph` to retrieve\n * neighbour lists and per-neighbour degrees. Full async equivalence\n * requires PriorityContext refactoring (Phase 4b deferred). This export\n * establishes the async API surface.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function tideAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn baseAsync(graph, seeds, {\n\t\t...config,\n\t\tpriority: tidePriority,\n\t});\n}\n","/**\n * LACE (Local Association Context Expansion) algorithm.\n *\n * Prioritises exploration by mutual information scores.\n * Expands high-MI edges first, favouring paths with strong associations.\n *\n * Requires MI function configuration.\n *\n * @module expansion/lace\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { jaccard } from \"../ranking/mi/jaccard\";\nimport { avgFrontierMI } from \"./priority-helpers\";\n\n/**\n * Configuration for LACE expansion.\n */\nexport interface LACEConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> extends ExpansionConfig<N, E> {\n\t/** MI function for computing edge priorities (default: jaccard) */\n\treadonly mi?: (\n\t\tgraph: ReadableGraph<N, E>,\n\t\tsource: string,\n\t\ttarget: string,\n\t) => number;\n}\n\n/**\n * LACE priority function.\n *\n * Priority = 1 - avgMI(node, same-frontier visited nodes)\n * Higher average MI = lower priority value = explored first\n */\nfunction lacePriority<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n\tmi: (graph: ReadableGraph<N, E>, source: string, target: string) => number,\n): number {\n\tconst avgMi = avgFrontierMI(context.graph, nodeId, context, mi);\n\t// Invert so higher MI = lower priority value = expanded first\n\treturn 1 - avgMi;\n}\n\n/**\n * Run LACE expansion algorithm.\n *\n * Expands from seeds prioritising high-MI edges.\n * Useful for finding paths with strong semantic associations.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration with MI function\n * @returns Expansion result with discovered paths\n */\nexport function lace<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: LACEConfig<N, E>,\n): ExpansionResult {\n\tconst { mi = jaccard, ...restConfig } = config ?? {};\n\n\tconst priority = (nodeId: string, context: PriorityContext<N, E>): number =>\n\t\tlacePriority(nodeId, context, mi);\n\n\treturn base(graph, seeds, {\n\t\t...restConfig,\n\t\tpriority,\n\t});\n}\n\n/**\n * Run LACE expansion asynchronously.\n *\n * Note: the LACE priority function accesses `context.graph` via\n * `avgFrontierMI`. Full async equivalence requires PriorityContext\n * refactoring (Phase 4b deferred). This export establishes the async\n * API surface.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - LACE configuration combined with async runner options\n * @returns Promise resolving to the expansion result\n */\nexport async function laceAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: LACEConfig<N, E> & AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\tconst { mi = jaccard, ...restConfig } = config ?? {};\n\n\tconst priority = (nodeId: string, context: PriorityContext<N, E>): number =>\n\t\tlacePriority(nodeId, context, mi);\n\n\treturn baseAsync(graph, seeds, { ...restConfig, priority });\n}\n","/**\n * PIPE (Path Importance Priority Expansion) algorithm.\n *\n * Prioritises nodes that are more likely to be on important paths.\n * Uses betweenness-like estimation based on neighbourhood overlap.\n *\n * Useful for finding paths through \"bridge\" nodes.\n *\n * @module expansion/warp\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { countCrossFrontierNeighbours } from \"./priority-helpers\";\n\n/**\n * WARP priority function.\n *\n * Priority = 1 / (1 + bridge_score)\n * Bridge score = cross-frontier neighbour count plus bonus for nodes\n * already on discovered paths.\n * Higher bridge score = more likely to complete paths = explored first.\n */\nfunction warpPriority<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\t// Count neighbours visited by other frontiers\n\tlet bridgeScore = countCrossFrontierNeighbours(\n\t\tcontext.graph,\n\t\tnodeId,\n\t\tcontext,\n\t);\n\n\t// Additional bonus for nodes already present on discovered paths\n\tfor (const path of context.discoveredPaths) {\n\t\tif (path.nodes.includes(nodeId)) {\n\t\t\tbridgeScore += 2;\n\t\t}\n\t}\n\n\t// Invert: higher bridge score = lower priority value = expanded first\n\treturn 1 / (1 + bridgeScore);\n}\n\n/**\n * Run WARP expansion algorithm.\n *\n * Expands from seeds prioritising bridge nodes.\n * Useful for finding paths through structurally important nodes.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function warp<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: warpPriority,\n\t});\n}\n\n/**\n * Run WARP expansion asynchronously.\n *\n * Note: the WARP priority function accesses `context.graph` via\n * `countCrossFrontierNeighbours`. Full async equivalence requires\n * PriorityContext refactoring (Phase 4b deferred). This export\n * establishes the async API surface.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function warpAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn baseAsync(graph, seeds, {\n\t\t...config,\n\t\tpriority: warpPriority,\n\t});\n}\n","/**\n * FUSE (Forward Unified Semantic Exploration-Aware Graph Expansion) algorithm.\n *\n * Two-phase expansion:\n * 1. Initial DOME-style expansion to discover candidate paths\n * 2. Re-prioritise based on path salience scores\n *\n * Combines structural exploration with semantic ranking.\n *\n * @module expansion/fuse\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { jaccard } from \"../ranking/mi/jaccard\";\nimport { avgFrontierMI } from \"./priority-helpers\";\n\n/**\n * Configuration for FUSE expansion.\n */\nexport interface FUSEConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> extends ExpansionConfig<N, E> {\n\t/** MI function for salience computation (default: jaccard) */\n\treadonly mi?: (\n\t\tgraph: ReadableGraph<N, E>,\n\t\tsource: string,\n\t\ttarget: string,\n\t) => number;\n\t/** Weight for salience component (0-1, default: 0.5) */\n\treadonly salienceWeight?: number;\n}\n\n/**\n * @deprecated Use {@link FUSEConfig} instead.\n */\nexport type SAGEConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> = FUSEConfig<N, E>;\n\n/**\n * FUSE priority function.\n *\n * Combines degree with average frontier MI as a salience proxy:\n * Priority = (1 - w) * degree + w * (1 - avgMI)\n * Lower values = higher priority; high salience lowers priority\n */\nfunction fusePriority<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n\tmi: (graph: ReadableGraph<N, E>, source: string, target: string) => number,\n\tsalienceWeight: number,\n): number {\n\tconst avgSalience = avgFrontierMI(context.graph, nodeId, context, mi);\n\n\t// Combine degree with salience — lower priority value = expanded first\n\tconst degreeComponent = (1 - salienceWeight) * context.degree;\n\tconst salienceComponent = salienceWeight * (1 - avgSalience);\n\n\treturn degreeComponent + salienceComponent;\n}\n\n/**\n * Run FUSE expansion algorithm.\n *\n * Combines structural exploration with semantic salience.\n * Useful for finding paths that are both short and semantically meaningful.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration with MI function\n * @returns Expansion result with discovered paths\n */\nexport function fuse<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: FUSEConfig<N, E>,\n): ExpansionResult {\n\tconst { mi = jaccard, salienceWeight = 0.5, ...restConfig } = config ?? {};\n\n\tconst priority = (nodeId: string, context: PriorityContext<N, E>): number =>\n\t\tfusePriority(nodeId, context, mi, salienceWeight);\n\n\treturn base(graph, seeds, {\n\t\t...restConfig,\n\t\tpriority,\n\t});\n}\n\n/**\n * Run FUSE expansion asynchronously.\n *\n * Note: the FUSE priority function accesses `context.graph` via\n * `avgFrontierMI`. Full async equivalence requires PriorityContext\n * refactoring (Phase 4b deferred). This export establishes the async\n * API surface.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - FUSE configuration combined with async runner options\n * @returns Promise resolving to the expansion result\n */\nexport async function fuseAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: FUSEConfig<N, E> & AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\tconst { mi = jaccard, salienceWeight = 0.5, ...restConfig } = config ?? {};\n\n\tconst priority = (nodeId: string, context: PriorityContext<N, E>): number =>\n\t\tfusePriority(nodeId, context, mi, salienceWeight);\n\n\treturn baseAsync(graph, seeds, { ...restConfig, priority });\n}\n","/**\n * REACH (Rank-Enhanced Adaptive Collision Hash) algorithm.\n *\n * Two-phase expansion:\n * 1. Phase 1: Degree-ordered expansion to collect MI statistics\n * 2. Phase 2: MI-guided expansion using learned thresholds\n *\n * Adapts to graph structure by learning optimal MI thresholds.\n *\n * @module expansion/sift\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { jaccard } from \"../ranking/mi/jaccard\";\nimport { avgFrontierMI } from \"./priority-helpers\";\n\n/**\n * Configuration for REACH expansion.\n */\nexport interface REACHConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> extends ExpansionConfig<N, E> {\n\t/** MI function for salience computation (default: jaccard) */\n\treadonly mi?: (\n\t\tgraph: ReadableGraph<N, E>,\n\t\tsource: string,\n\t\ttarget: string,\n\t) => number;\n\t/** MI percentile threshold for phase 2 (default: 0.25) */\n\treadonly miThreshold?: number;\n\t/** Maximum nodes for phase 1 sampling (default: 1000) */\n\treadonly phase1MaxNodes?: number;\n}\n\n/**\n * REACH (SIFT) priority function.\n *\n * Prioritises nodes with average frontier MI above the threshold;\n * falls back to degree-based ordering for those below it.\n */\nfunction siftPriority<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n\tmi: (graph: ReadableGraph<N, E>, source: string, target: string) => number,\n\tmiThreshold: number,\n): number {\n\tconst avgMi = avgFrontierMI(context.graph, nodeId, context, mi);\n\n\t// If MI is above threshold, give high priority (low value)\n\t// Otherwise, fall back to degree\n\tif (avgMi >= miThreshold) {\n\t\treturn 1 - avgMi; // High MI = low priority value\n\t} else {\n\t\treturn context.degree + 100; // Low MI = delayed expansion\n\t}\n}\n\n/**\n * Run SIFT expansion algorithm.\n *\n * Two-phase adaptive expansion that learns MI thresholds\n * from initial sampling, then uses them for guided expansion.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function sift<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: REACHConfig<N, E>,\n): ExpansionResult {\n\tconst { mi = jaccard, miThreshold = 0.25, ...restConfig } = config ?? {};\n\n\t// Run guided expansion with MI threshold\n\tconst priority = (nodeId: string, context: PriorityContext<N, E>): number =>\n\t\tsiftPriority(nodeId, context, mi, miThreshold);\n\n\treturn base(graph, seeds, {\n\t\t...restConfig,\n\t\tpriority,\n\t});\n}\n\n/**\n * Run SIFT expansion asynchronously.\n *\n * Note: the SIFT priority function accesses `context.graph` via\n * `avgFrontierMI`. Full async equivalence requires PriorityContext\n * refactoring (Phase 4b deferred). This export establishes the async\n * API surface.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - SIFT (REACHConfig) configuration combined with async runner options\n * @returns Promise resolving to the expansion result\n */\nexport async function siftAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: REACHConfig<N, E> & AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\tconst { mi = jaccard, miThreshold = 0.25, ...restConfig } = config ?? {};\n\n\tconst priority = (nodeId: string, context: PriorityContext<N, E>): number =>\n\t\tsiftPriority(nodeId, context, mi, miThreshold);\n\n\treturn baseAsync(graph, seeds, { ...restConfig, priority });\n}\n","/**\n * FLUX (FlexibleAlgorithm Zone Exploration) algorithm.\n *\n * Switches between different expansion strategies based on\n * local graph density and progress.\n *\n * Strategies:\n * - Sparse regions: DOME (expand hubs)\n * - Dense regions: EDGE (expand through low-degree edges)\n * - Bridge nodes: PIPE (expand bridges)\n *\n * @module expansion/flux\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { countCrossFrontierNeighbours } from \"./priority-helpers\";\n\n/**\n * Configuration for MAZE expansion.\n */\nexport interface MAZEConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> extends ExpansionConfig<N, E> {\n\t/** Density threshold for switching to EDGE mode (default: 0.5) */\n\treadonly densityThreshold?: number;\n\t/** Bridge threshold for switching to PIPE mode (default: 0.3) */\n\treadonly bridgeThreshold?: number;\n}\n\n/**\n * Compute local density around a node.\n */\nfunction localDensity<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tnodeId: string,\n): number {\n\tconst neighbours = Array.from(graph.neighbours(nodeId));\n\tconst degree = neighbours.length;\n\n\tif (degree < 2) {\n\t\treturn 0;\n\t}\n\n\t// Count edges among neighbours\n\tlet edges = 0;\n\tfor (let i = 0; i < neighbours.length; i++) {\n\t\tfor (let j = i + 1; j < neighbours.length; j++) {\n\t\t\tconst ni = neighbours[i];\n\t\t\tconst nj = neighbours[j];\n\t\t\tif (\n\t\t\t\tni !== undefined &&\n\t\t\t\tnj !== undefined &&\n\t\t\t\tgraph.getEdge(ni, nj) !== undefined\n\t\t\t) {\n\t\t\t\tedges++;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst maxEdges = (degree * (degree - 1)) / 2;\n\treturn edges / maxEdges;\n}\n\n/**\n * MAZE adaptive priority function.\n *\n * Switches strategies based on local conditions:\n * - High density + low bridge: EDGE mode\n * - Low density + low bridge: DOME mode\n * - High bridge score: PIPE mode\n */\nfunction fluxPriority<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n\tdensityThreshold: number,\n\tbridgeThreshold: number,\n): number {\n\tconst graph = context.graph;\n\tconst degree = context.degree;\n\n\t// Compute local metrics\n\tconst density = localDensity(graph, nodeId);\n\tconst bridge = countCrossFrontierNeighbours(graph, nodeId, context);\n\n\t// Normalise bridge score by number of frontiers\n\tconst numFrontiers = new Set(context.visitedByFrontier.values()).size;\n\tconst normalisedBridge = numFrontiers > 0 ? bridge / numFrontiers : 0;\n\n\t// Select strategy\n\tif (normalisedBridge >= bridgeThreshold) {\n\t\t// PIPE mode: prioritise bridges\n\t\treturn 1 / (1 + bridge);\n\t} else if (density >= densityThreshold) {\n\t\t// EDGE mode: avoid dense regions, expand through sparse edges\n\t\treturn 1 / (degree + 1); // Low degree → low priority value → expanded first\n\t} else {\n\t\t// DOME mode: expand hubs first\n\t\treturn degree;\n\t}\n}\n\n/**\n * Run FLUX expansion algorithm.\n *\n * Adaptively switches between expansion strategies based on\n * local graph structure. Useful for heterogeneous graphs\n * with varying density.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function flux<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: MAZEConfig<N, E>,\n): ExpansionResult {\n\tconst {\n\t\tdensityThreshold = 0.5,\n\t\tbridgeThreshold = 0.3,\n\t\t...restConfig\n\t} = config ?? {};\n\n\tconst priority = (nodeId: string, context: PriorityContext<N, E>): number =>\n\t\tfluxPriority(nodeId, context, densityThreshold, bridgeThreshold);\n\n\treturn base(graph, seeds, {\n\t\t...restConfig,\n\t\tpriority,\n\t});\n}\n\n/**\n * Run FLUX expansion asynchronously.\n *\n * Note: the FLUX priority function accesses `context.graph` to compute\n * local density and cross-frontier bridge scores. Full async equivalence\n * requires PriorityContext refactoring (Phase 4b deferred). This export\n * establishes the async API surface.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - FLUX (MAZEConfig) configuration combined with async runner options\n * @returns Promise resolving to the expansion result\n */\nexport async function fluxAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: MAZEConfig<N, E> & AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\tconst {\n\t\tdensityThreshold = 0.5,\n\t\tbridgeThreshold = 0.3,\n\t\t...restConfig\n\t} = config ?? {};\n\n\tconst priority = (nodeId: string, context: PriorityContext<N, E>): number =>\n\t\tfluxPriority(nodeId, context, densityThreshold, bridgeThreshold);\n\n\treturn baseAsync(graph, seeds, { ...restConfig, priority });\n}\n","/**\n * Standard BFS (Breadth-First Search) expansion.\n *\n * Simplest baseline: FIFO order based on discovery iteration.\n * All nodes at the same frontier are explored in discovery order.\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base, baseAsync } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\n\n/**\n * BFS priority: discovery iteration order (FIFO).\n */\nfunction bfsPriority<N extends NodeData, E extends EdgeData>(\n\t_nodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\treturn context.iteration;\n}\n\n/**\n * Run standard BFS expansion (FIFO discovery order).\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function standardBfs<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: bfsPriority,\n\t});\n}\n\n/**\n * Run standard BFS expansion asynchronously (FIFO discovery order).\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function standardBfsAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn baseAsync(graph, seeds, { ...config, priority: bfsPriority });\n}\n","/**\n * Frontier-Balanced expansion.\n *\n * Round-robin exploration across frontiers.\n * Each frontier expands one node before the next frontier gets a turn.\n * Ensures fair expansion across all seed frontiers.\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base, baseAsync } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\n\n/**\n * Frontier-balanced priority: frontier index dominates, then discovery iteration.\n * Scales frontier index by 1e9 to ensure round-robin ordering across frontiers.\n */\nfunction balancedPriority<N extends NodeData, E extends EdgeData>(\n\t_nodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\treturn context.frontierIndex * 1e9 + context.iteration;\n}\n\n/**\n * Run frontier-balanced expansion (round-robin across frontiers).\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function frontierBalanced<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: balancedPriority,\n\t});\n}\n\n/**\n * Run frontier-balanced expansion asynchronously (round-robin across frontiers).\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function frontierBalancedAsync<\n\tN extends NodeData,\n\tE extends EdgeData,\n>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn baseAsync(graph, seeds, { ...config, priority: balancedPriority });\n}\n","/**\n * Random-Priority expansion.\n *\n * Baseline exploration with random node priorities.\n * Uses deterministic seeded randomness for reproducibility.\n * Serves as a null hypothesis for algorithm comparison.\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base, baseAsync } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\n\n/**\n * Deterministic seeded random number generator.\n * Uses FNV-1a-like hash for input → [0, 1] output.\n *\n * @param input - String to hash\n * @param seed - Random seed for reproducibility\n * @returns Deterministic random value in [0, 1]\n */\nfunction seededRandom(input: string, seed = 0): number {\n\tlet h = seed;\n\tfor (let i = 0; i < input.length; i++) {\n\t\th = Math.imul(h ^ input.charCodeAt(i), 0x9e3779b9);\n\n\t\th ^= h >>> 16;\n\t}\n\n\treturn (h >>> 0) / 0xffffffff;\n}\n\n/**\n * Configuration for random-priority expansion.\n */\ninterface RandomPriorityConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> extends ExpansionConfig<N, E> {\n\t/** Random seed for deterministic reproducibility */\n\treadonly seed?: number;\n}\n\n/**\n * Async configuration for random-priority expansion.\n */\ninterface AsyncRandomPriorityConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> extends AsyncExpansionConfig<N, E> {\n\t/** Random seed for deterministic reproducibility */\n\treadonly seed?: number;\n}\n\n/**\n * Build a seeded random priority function for a given seed value.\n */\nfunction makeRandomPriorityFn<N extends NodeData, E extends EdgeData>(\n\tseed: number,\n): (nodeId: string, context: PriorityContext<N, E>) => number {\n\treturn (nodeId: string): number => seededRandom(nodeId, seed);\n}\n\n/**\n * Run random-priority expansion (null hypothesis baseline).\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function randomPriority<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: RandomPriorityConfig<N, E>,\n): ExpansionResult {\n\tconst { seed = 0 } = config ?? {};\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: makeRandomPriorityFn<N, E>(seed),\n\t});\n}\n\n/**\n * Run random-priority expansion asynchronously (null hypothesis baseline).\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function randomPriorityAsync<\n\tN extends NodeData,\n\tE extends EdgeData,\n>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncRandomPriorityConfig<N, E>,\n): Promise<ExpansionResult> {\n\tconst { seed = 0 } = config ?? {};\n\treturn baseAsync(graph, seeds, {\n\t\t...config,\n\t\tpriority: makeRandomPriorityFn<N, E>(seed),\n\t});\n}\n","/**\n * DFS-Priority expansion.\n *\n * Baseline exploration using LIFO (last-in, first-out) discovery order,\n * simulating depth-first search via the BASE framework.\n * Uses negative iteration count as priority so the most recently\n * discovered node is always expanded next.\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base, baseAsync } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\n\n/**\n * DFS priority function: negative iteration produces LIFO ordering.\n *\n * Lower priority values are expanded first, so negating the iteration\n * counter ensures the most recently enqueued node is always next.\n */\nexport function dfsPriorityFn<N extends NodeData, E extends EdgeData>(\n\t_nodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\treturn -context.iteration;\n}\n\n/**\n * Run DFS-priority expansion (LIFO discovery order).\n *\n * Uses the BASE framework with a negative-iteration priority function,\n * which causes the most recently discovered node to be expanded first —\n * equivalent to depth-first search behaviour.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function dfsPriority<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: dfsPriorityFn,\n\t});\n}\n\n/**\n * Run DFS-priority expansion asynchronously (LIFO discovery order).\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function dfsPriorityAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn baseAsync(graph, seeds, { ...config, priority: dfsPriorityFn });\n}\n","/**\n * K-Hop expansion.\n *\n * Fixed-depth BFS baseline that explores up to k hops from each seed.\n * Implements explicit depth-limited BFS with frontier collision detection\n * for path discovery between seeds. Each seed expands independently;\n * a path is recorded when a node visited by one seed's frontier is\n * encountered by another seed's frontier.\n *\n * Unlike the BASE framework, depth is tracked explicitly so the k-hop\n * constraint is exact rather than approximate.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionPath,\n\tExpansionStats,\n} from \"./types\";\nimport type { ExpansionConfig } from \"./types\";\n\n/**\n * Configuration for k-hop expansion.\n */\nexport interface KHopConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> extends ExpansionConfig<N, E> {\n\t/**\n\t * Maximum number of hops from any seed node.\n\t * Defaults to 2.\n\t */\n\treadonly k?: number;\n}\n\n/**\n * Run k-hop expansion (fixed-depth BFS).\n *\n * Explores all nodes reachable within exactly k hops of any seed using\n * breadth-first search. Paths between seeds are detected when a node\n * is reached by frontiers from two different seeds.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - K-hop configuration (k defaults to 2)\n * @returns Expansion result with discovered paths\n */\nexport function kHop<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: KHopConfig<N, E>,\n): ExpansionResult {\n\tconst startTime = performance.now();\n\n\tconst { k = 2 } = config ?? {};\n\n\tif (seeds.length === 0) {\n\t\treturn emptyResult(startTime);\n\t}\n\n\t// Per-frontier state: visited nodes and predecessor map for path reconstruction\n\tconst visitedByFrontier: Map<NodeId, NodeId | null>[] = seeds.map(\n\t\t(): Map<NodeId, NodeId | null> => new Map<NodeId, NodeId | null>(),\n\t);\n\t// Global map: node → frontier index that first visited it\n\tconst firstVisitedBy = new Map<NodeId, number>();\n\tconst allVisited = new Set<NodeId>();\n\tconst sampledEdgeMap = new Map<NodeId, Set<NodeId>>();\n\tconst discoveredPaths: ExpansionPath[] = [];\n\n\tlet iterations = 0;\n\tlet edgesTraversed = 0;\n\n\t// Initialise each frontier with its seed node\n\tfor (let i = 0; i < seeds.length; i++) {\n\t\tconst seed = seeds[i];\n\t\tif (seed === undefined) continue;\n\t\tif (!graph.hasNode(seed.id)) continue;\n\n\t\tvisitedByFrontier[i]?.set(seed.id, null);\n\t\tallVisited.add(seed.id);\n\n\t\tif (!firstVisitedBy.has(seed.id)) {\n\t\t\tfirstVisitedBy.set(seed.id, i);\n\t\t} else {\n\t\t\t// Seed collision: two seeds are the same node — record trivial path\n\t\t\tconst otherIdx = firstVisitedBy.get(seed.id) ?? -1;\n\t\t\tif (otherIdx < 0) continue;\n\t\t\tconst fromSeed = seeds[otherIdx];\n\t\t\tconst toSeed = seeds[i];\n\t\t\tif (fromSeed !== undefined && toSeed !== undefined) {\n\t\t\t\tdiscoveredPaths.push({ fromSeed, toSeed, nodes: [seed.id] });\n\t\t\t}\n\t\t}\n\t}\n\n\t// BFS level-by-level for each frontier simultaneously\n\t// Current frontier for each seed: nodes to expand at the next hop depth\n\tlet currentLevel: NodeId[][] = seeds.map((s, i): NodeId[] => {\n\t\tconst frontier = visitedByFrontier[i];\n\t\tif (frontier === undefined) return [];\n\t\treturn frontier.has(s.id) ? [s.id] : [];\n\t});\n\n\tfor (let hop = 0; hop < k; hop++) {\n\t\tconst nextLevel: NodeId[][] = seeds.map(() => []);\n\n\t\tfor (let i = 0; i < seeds.length; i++) {\n\t\t\tconst level = currentLevel[i];\n\t\t\tif (level === undefined) continue;\n\n\t\t\tconst frontierVisited = visitedByFrontier[i];\n\t\t\tif (frontierVisited === undefined) continue;\n\n\t\t\tfor (const nodeId of level) {\n\t\t\t\titerations++;\n\n\t\t\t\tfor (const neighbour of graph.neighbours(nodeId)) {\n\t\t\t\t\tedgesTraversed++;\n\n\t\t\t\t\t// Track sampled edge in canonical order\n\t\t\t\t\tconst [s, t] =\n\t\t\t\t\t\tnodeId < neighbour ? [nodeId, neighbour] : [neighbour, nodeId];\n\t\t\t\t\tlet targets = sampledEdgeMap.get(s);\n\t\t\t\t\tif (targets === undefined) {\n\t\t\t\t\t\ttargets = new Set();\n\t\t\t\t\t\tsampledEdgeMap.set(s, targets);\n\t\t\t\t\t}\n\t\t\t\t\ttargets.add(t);\n\n\t\t\t\t\t// Skip if this frontier has already visited this neighbour\n\t\t\t\t\tif (frontierVisited.has(neighbour)) continue;\n\n\t\t\t\t\tfrontierVisited.set(neighbour, nodeId);\n\t\t\t\t\tallVisited.add(neighbour);\n\t\t\t\t\tnextLevel[i]?.push(neighbour);\n\n\t\t\t\t\t// Path detection: collision with another frontier\n\t\t\t\t\tconst previousFrontier = firstVisitedBy.get(neighbour);\n\t\t\t\t\tif (previousFrontier !== undefined && previousFrontier !== i) {\n\t\t\t\t\t\tconst fromSeed = seeds[previousFrontier];\n\t\t\t\t\t\tconst toSeed = seeds[i];\n\t\t\t\t\t\tif (fromSeed !== undefined && toSeed !== undefined) {\n\t\t\t\t\t\t\tconst path = reconstructPath(\n\t\t\t\t\t\t\t\tneighbour,\n\t\t\t\t\t\t\t\tpreviousFrontier,\n\t\t\t\t\t\t\t\ti,\n\t\t\t\t\t\t\t\tvisitedByFrontier,\n\t\t\t\t\t\t\t\tseeds,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (path !== null) {\n\t\t\t\t\t\t\t\tconst alreadyFound = discoveredPaths.some(\n\t\t\t\t\t\t\t\t\t(p) =>\n\t\t\t\t\t\t\t\t\t\t(p.fromSeed.id === fromSeed.id &&\n\t\t\t\t\t\t\t\t\t\t\tp.toSeed.id === toSeed.id) ||\n\t\t\t\t\t\t\t\t\t\t(p.fromSeed.id === toSeed.id &&\n\t\t\t\t\t\t\t\t\t\t\tp.toSeed.id === fromSeed.id),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tif (!alreadyFound) {\n\t\t\t\t\t\t\t\t\tdiscoveredPaths.push(path);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!firstVisitedBy.has(neighbour)) {\n\t\t\t\t\t\tfirstVisitedBy.set(neighbour, i);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tcurrentLevel = nextLevel;\n\n\t\t// Stop early if all frontiers are exhausted\n\t\tif (currentLevel.every((level) => level.length === 0)) break;\n\t}\n\n\tconst endTime = performance.now();\n\n\t// Convert sampled edge map to set of tuples\n\tconst edgeTuples = new Set<readonly [NodeId, NodeId]>();\n\tfor (const [source, targets] of sampledEdgeMap) {\n\t\tfor (const target of targets) {\n\t\t\tedgeTuples.add([source, target] as const);\n\t\t}\n\t}\n\n\tconst visitedPerFrontier = visitedByFrontier.map((m) => new Set(m.keys()));\n\n\tconst stats: ExpansionStats = {\n\t\titerations,\n\t\tnodesVisited: allVisited.size,\n\t\tedgesTraversed,\n\t\tpathsFound: discoveredPaths.length,\n\t\tdurationMs: endTime - startTime,\n\t\talgorithm: \"k-hop\",\n\t\ttermination: \"exhausted\",\n\t};\n\n\treturn {\n\t\tpaths: discoveredPaths,\n\t\tsampledNodes: allVisited,\n\t\tsampledEdges: edgeTuples,\n\t\tvisitedPerFrontier,\n\t\tstats,\n\t};\n}\n\n/**\n * Reconstruct the path between two colliding frontiers.\n */\nfunction reconstructPath(\n\tcollisionNode: NodeId,\n\tfrontierA: number,\n\tfrontierB: number,\n\tvisitedByFrontier: readonly Map<NodeId, NodeId | null>[],\n\tseeds: readonly Seed[],\n): ExpansionPath | null {\n\tconst seedA = seeds[frontierA];\n\tconst seedB = seeds[frontierB];\n\tif (seedA === undefined || seedB === undefined) return null;\n\n\t// Trace back from collision node through frontier A to its seed\n\tconst pathA: NodeId[] = [collisionNode];\n\tconst predA = visitedByFrontier[frontierA];\n\tif (predA !== undefined) {\n\t\tlet node: NodeId | null | undefined = collisionNode;\n\t\tlet pred: NodeId | null | undefined = predA.get(node);\n\t\twhile (pred !== null && pred !== undefined) {\n\t\t\tpathA.unshift(pred);\n\t\t\tnode = pred;\n\t\t\tpred = predA.get(node);\n\t\t}\n\t}\n\n\t// Trace back from collision node through frontier B to its seed\n\tconst pathB: NodeId[] = [];\n\tconst predB = visitedByFrontier[frontierB];\n\tif (predB !== undefined) {\n\t\tlet node: NodeId | null | undefined = collisionNode;\n\t\tlet pred: NodeId | null | undefined = predB.get(node);\n\t\twhile (pred !== null && pred !== undefined) {\n\t\t\tpathB.push(pred);\n\t\t\tnode = pred;\n\t\t\tpred = predB.get(node);\n\t\t}\n\t}\n\n\treturn {\n\t\tfromSeed: seedA,\n\t\ttoSeed: seedB,\n\t\tnodes: [...pathA, ...pathB],\n\t};\n}\n\n/**\n * Create an empty result for early termination (no seeds).\n */\nfunction emptyResult(startTime: number): ExpansionResult {\n\treturn {\n\t\tpaths: [],\n\t\tsampledNodes: new Set(),\n\t\tsampledEdges: new Set(),\n\t\tvisitedPerFrontier: [],\n\t\tstats: {\n\t\t\titerations: 0,\n\t\t\tnodesVisited: 0,\n\t\t\tedgesTraversed: 0,\n\t\t\tpathsFound: 0,\n\t\t\tdurationMs: performance.now() - startTime,\n\t\t\talgorithm: \"k-hop\",\n\t\t\ttermination: \"exhausted\",\n\t\t},\n\t};\n}\n","/**\n * Random Walk with Restart expansion.\n *\n * Baseline exploration strategy using multiple random walks from each seed.\n * Walks proceed by uniformly sampling a neighbour at each step. With\n * probability `restartProbability` the walk restarts from the originating\n * seed node, simulating Personalised PageRank dynamics.\n *\n * Path detection: when a walk visits a node that was previously reached\n * by a walk from a different seed, an inter-seed path is recorded.\n *\n * This algorithm does NOT use the BASE framework — it constructs an\n * ExpansionResult directly.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionPath,\n\tExpansionStats,\n} from \"./types\";\n\n/**\n * Configuration for random-walk-with-restart expansion.\n */\nexport interface RandomWalkConfig {\n\t/** Probability of restarting a walk from its seed node (default: 0.15). */\n\treadonly restartProbability?: number;\n\t/** Number of walks to perform per seed node (default: 10). */\n\treadonly walks?: number;\n\t/** Maximum steps per walk (default: 20). */\n\treadonly walkLength?: number;\n\t/** Random seed for deterministic reproducibility (default: 0). */\n\treadonly seed?: number;\n}\n\n/**\n * Mulberry32 seeded PRNG — fast, compact, and high-quality for simulation.\n *\n * Returns a closure that yields the next pseudo-random value in [0, 1)\n * on each call.\n *\n * @param seed - 32-bit integer seed\n */\nfunction mulberry32(seed: number): () => number {\n\tlet s = seed;\n\treturn (): number => {\n\t\ts += 0x6d2b79f5;\n\t\tlet t = s;\n\t\tt = Math.imul(t ^ (t >>> 15), t | 1);\n\t\tt ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n\t\treturn ((t ^ (t >>> 14)) >>> 0) / 0x100000000;\n\t};\n}\n\n/**\n * Run random-walk-with-restart expansion.\n *\n * For each seed, performs `walks` independent random walks of up to\n * `walkLength` steps. At each step the walk either restarts (with\n * probability `restartProbability`) or moves to a uniformly sampled\n * neighbour. All visited nodes and traversed edges are collected.\n *\n * Inter-seed paths are detected when a walk reaches a node that was\n * previously reached by a walk originating from a different seed.\n * The recorded path contains only the two seed endpoints rather than\n * the full walk trajectory, consistent with the ExpansionPath contract.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Random walk configuration\n * @returns Expansion result with discovered paths\n */\nexport function randomWalk<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: RandomWalkConfig,\n): ExpansionResult {\n\tconst startTime = performance.now();\n\n\tconst {\n\t\trestartProbability = 0.15,\n\t\twalks = 10,\n\t\twalkLength = 20,\n\t\tseed = 0,\n\t} = config ?? {};\n\n\tif (seeds.length === 0) {\n\t\treturn emptyResult(startTime);\n\t}\n\n\tconst rand = mulberry32(seed);\n\n\t// Map each visited node to the index of the seed whose walk first reached it\n\tconst firstVisitedBySeed = new Map<NodeId, number>();\n\tconst allVisited = new Set<NodeId>();\n\tconst sampledEdgeMap = new Map<NodeId, Set<NodeId>>();\n\n\t// Paths discovered when walks from different seeds collide\n\tconst discoveredPaths: ExpansionPath[] = [];\n\n\tlet iterations = 0;\n\tlet edgesTraversed = 0;\n\n\t// Track which nodes were visited by each frontier for visitedPerFrontier\n\tconst visitedPerFrontier: Set<NodeId>[] = seeds.map(() => new Set<NodeId>());\n\n\tfor (let seedIdx = 0; seedIdx < seeds.length; seedIdx++) {\n\t\tconst seed_ = seeds[seedIdx];\n\t\tif (seed_ === undefined) continue;\n\n\t\tconst seedId = seed_.id;\n\t\tif (!graph.hasNode(seedId)) continue;\n\n\t\t// Mark the seed itself as visited\n\t\tif (!firstVisitedBySeed.has(seedId)) {\n\t\t\tfirstVisitedBySeed.set(seedId, seedIdx);\n\t\t}\n\t\tallVisited.add(seedId);\n\t\tvisitedPerFrontier[seedIdx]?.add(seedId);\n\n\t\tfor (let w = 0; w < walks; w++) {\n\t\t\tlet current = seedId;\n\n\t\t\tfor (let step = 0; step < walkLength; step++) {\n\t\t\t\titerations++;\n\n\t\t\t\t// Restart with configured probability\n\t\t\t\tif (rand() < restartProbability) {\n\t\t\t\t\tcurrent = seedId;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Collect neighbours into an array for random sampling\n\t\t\t\tconst neighbourList: NodeId[] = [];\n\t\t\t\tfor (const nb of graph.neighbours(current)) {\n\t\t\t\t\tneighbourList.push(nb);\n\t\t\t\t}\n\n\t\t\t\tif (neighbourList.length === 0) {\n\t\t\t\t\t// Dead end — restart\n\t\t\t\t\tcurrent = seedId;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Uniform random neighbour selection\n\t\t\t\tconst nextIdx = Math.floor(rand() * neighbourList.length);\n\t\t\t\tconst next = neighbourList[nextIdx];\n\t\t\t\tif (next === undefined) {\n\t\t\t\t\tcurrent = seedId;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tedgesTraversed++;\n\n\t\t\t\t// Record traversed edge (canonical order)\n\t\t\t\tconst [s, t] = current < next ? [current, next] : [next, current];\n\t\t\t\tlet targets = sampledEdgeMap.get(s);\n\t\t\t\tif (targets === undefined) {\n\t\t\t\t\ttargets = new Set();\n\t\t\t\t\tsampledEdgeMap.set(s, targets);\n\t\t\t\t}\n\t\t\t\ttargets.add(t);\n\n\t\t\t\t// Path detection: collision with a walk from a different seed\n\t\t\t\tconst previousSeedIdx = firstVisitedBySeed.get(next);\n\t\t\t\tif (previousSeedIdx !== undefined && previousSeedIdx !== seedIdx) {\n\t\t\t\t\tconst fromSeed = seeds[previousSeedIdx];\n\t\t\t\t\tconst toSeed = seeds[seedIdx];\n\t\t\t\t\tif (fromSeed !== undefined && toSeed !== undefined) {\n\t\t\t\t\t\t// Record a path between the two seed endpoints\n\t\t\t\t\t\tconst path: ExpansionPath = {\n\t\t\t\t\t\t\tfromSeed,\n\t\t\t\t\t\t\ttoSeed,\n\t\t\t\t\t\t\tnodes: [fromSeed.id, next, toSeed.id].filter(\n\t\t\t\t\t\t\t\t// Deduplicate when next happens to be a seed itself\n\t\t\t\t\t\t\t\t(n, i, arr) => arr.indexOf(n) === i,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t};\n\t\t\t\t\t\t// Avoid duplicate seed-pair paths\n\t\t\t\t\t\tconst alreadyFound = discoveredPaths.some(\n\t\t\t\t\t\t\t(p) =>\n\t\t\t\t\t\t\t\t(p.fromSeed.id === fromSeed.id && p.toSeed.id === toSeed.id) ||\n\t\t\t\t\t\t\t\t(p.fromSeed.id === toSeed.id && p.toSeed.id === fromSeed.id),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (!alreadyFound) {\n\t\t\t\t\t\t\tdiscoveredPaths.push(path);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!firstVisitedBySeed.has(next)) {\n\t\t\t\t\tfirstVisitedBySeed.set(next, seedIdx);\n\t\t\t\t}\n\t\t\t\tallVisited.add(next);\n\t\t\t\tvisitedPerFrontier[seedIdx]?.add(next);\n\n\t\t\t\tcurrent = next;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst endTime = performance.now();\n\n\t// Convert sampled edge map to set of tuples\n\tconst edgeTuples = new Set<readonly [NodeId, NodeId]>();\n\tfor (const [source, targets] of sampledEdgeMap) {\n\t\tfor (const target of targets) {\n\t\t\tedgeTuples.add([source, target] as const);\n\t\t}\n\t}\n\n\tconst stats: ExpansionStats = {\n\t\titerations,\n\t\tnodesVisited: allVisited.size,\n\t\tedgesTraversed,\n\t\tpathsFound: discoveredPaths.length,\n\t\tdurationMs: endTime - startTime,\n\t\talgorithm: \"random-walk\",\n\t\ttermination: \"exhausted\",\n\t};\n\n\treturn {\n\t\tpaths: discoveredPaths,\n\t\tsampledNodes: allVisited,\n\t\tsampledEdges: edgeTuples,\n\t\tvisitedPerFrontier,\n\t\tstats,\n\t};\n}\n\n/**\n * Create an empty result for early termination (no seeds).\n */\nfunction emptyResult(startTime: number): ExpansionResult {\n\treturn {\n\t\tpaths: [],\n\t\tsampledNodes: new Set(),\n\t\tsampledEdges: new Set(),\n\t\tvisitedPerFrontier: [],\n\t\tstats: {\n\t\t\titerations: 0,\n\t\t\tnodesVisited: 0,\n\t\t\tedgesTraversed: 0,\n\t\t\tpathsFound: 0,\n\t\t\tdurationMs: performance.now() - startTime,\n\t\t\talgorithm: \"random-walk\",\n\t\t\ttermination: \"exhausted\",\n\t\t},\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"expansion-ClDhlMK8.js","names":[],"sources":["../src/expansion/base-helpers.ts","../src/expansion/base-core.ts","../src/expansion/base.ts","../src/expansion/dome.ts","../src/expansion/hae.ts","../src/expansion/edge.ts","../src/expansion/pipe.ts","../src/expansion/priority-helpers.ts","../src/expansion/sage.ts","../src/expansion/reach.ts","../src/expansion/maze.ts","../src/expansion/tide.ts","../src/expansion/lace.ts","../src/expansion/warp.ts","../src/expansion/fuse.ts","../src/expansion/sift.ts","../src/expansion/flux.ts","../src/expansion/standard-bfs.ts","../src/expansion/frontier-balanced.ts","../src/expansion/random-priority.ts","../src/expansion/dfs-priority.ts","../src/expansion/k-hop.ts","../src/expansion/random-walk.ts"],"sourcesContent":["/**\n * Pure helper functions for the BASE expansion engine.\n *\n * These functions are extracted from base.ts so they can be shared between\n * the synchronous `base()` entry point and the `baseCore` generator used by\n * both sync and async runners.\n *\n * All functions here are pure — they make no direct graph calls.\n */\n\nimport type { NodeId } from \"../graph\";\nimport type {\n\tSeed,\n\tExpansionPath,\n\tExpansionResult,\n\tExpansionStats,\n} from \"./types\";\n\n/**\n * Internal queue entry for frontier expansion.\n */\nexport interface QueueEntry {\n\tnodeId: NodeId;\n\tfrontierIndex: number;\n\tpredecessor: NodeId | null;\n}\n\n/**\n * Limits structure used by continueExpansion.\n */\nexport interface ExpansionLimits {\n\treadonly maxIterations: number;\n\treadonly maxNodes: number;\n\treadonly maxPaths: number;\n}\n\n/**\n * Check whether expansion should continue given current progress.\n *\n * Returns shouldContinue=false as soon as any configured limit is reached,\n * along with the appropriate termination reason.\n *\n * @param iterations - Number of iterations completed so far\n * @param nodesVisited - Number of distinct nodes visited so far\n * @param pathsFound - Number of paths discovered so far\n * @param limits - Configured expansion limits (0 = unlimited)\n * @returns Whether to continue and the termination reason if stopping\n */\nexport function continueExpansion(\n\titerations: number,\n\tnodesVisited: number,\n\tpathsFound: number,\n\tlimits: ExpansionLimits,\n): { shouldContinue: boolean; termination: ExpansionStats[\"termination\"] } {\n\tif (limits.maxIterations > 0 && iterations >= limits.maxIterations) {\n\t\treturn { shouldContinue: false, termination: \"limit\" };\n\t}\n\tif (limits.maxNodes > 0 && nodesVisited >= limits.maxNodes) {\n\t\treturn { shouldContinue: false, termination: \"limit\" };\n\t}\n\tif (limits.maxPaths > 0 && pathsFound >= limits.maxPaths) {\n\t\treturn { shouldContinue: false, termination: \"limit\" };\n\t}\n\treturn { shouldContinue: true, termination: \"exhausted\" };\n}\n\n/**\n * Reconstruct path from collision point.\n *\n * Traces backwards through the predecessor maps of both frontiers from the\n * collision node, then concatenates the two halves to form the full path.\n *\n * @param collisionNode - The node where the two frontiers met\n * @param frontierA - Index of the first frontier\n * @param frontierB - Index of the second frontier\n * @param predecessors - Predecessor maps, one per frontier\n * @param seeds - Seed nodes, one per frontier\n * @returns The reconstructed path, or null if seeds are missing\n */\nexport function reconstructPath(\n\tcollisionNode: NodeId,\n\tfrontierA: number,\n\tfrontierB: number,\n\tpredecessors: readonly Map<NodeId, NodeId | null>[],\n\tseeds: readonly Seed[],\n): ExpansionPath | null {\n\tconst pathA: NodeId[] = [collisionNode];\n\tconst predA = predecessors[frontierA];\n\tif (predA !== undefined) {\n\t\tlet node: NodeId | null | undefined = collisionNode;\n\t\tlet next: NodeId | null | undefined = predA.get(node);\n\t\twhile (next !== null && next !== undefined) {\n\t\t\tnode = next;\n\t\t\tpathA.unshift(node);\n\t\t\tnext = predA.get(node);\n\t\t}\n\t}\n\n\tconst pathB: NodeId[] = [];\n\tconst predB = predecessors[frontierB];\n\tif (predB !== undefined) {\n\t\tlet node: NodeId | null | undefined = collisionNode;\n\t\tlet next: NodeId | null | undefined = predB.get(node);\n\t\twhile (next !== null && next !== undefined) {\n\t\t\tnode = next;\n\t\t\tpathB.push(node);\n\t\t\tnext = predB.get(node);\n\t\t}\n\t}\n\n\tconst fullPath = [...pathA, ...pathB];\n\n\tconst seedA = seeds[frontierA];\n\tconst seedB = seeds[frontierB];\n\n\tif (seedA === undefined || seedB === undefined) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tfromSeed: seedA,\n\t\ttoSeed: seedB,\n\t\tnodes: fullPath,\n\t};\n}\n\n/**\n * Create an empty expansion result for early termination (e.g. no seeds given).\n *\n * @param algorithm - Name of the algorithm producing this result\n * @param startTime - performance.now() timestamp taken before the algorithm began\n * @returns An ExpansionResult with zero paths and zero stats\n */\nexport function emptyResult(\n\talgorithm: string,\n\tstartTime: number,\n): ExpansionResult {\n\treturn {\n\t\tpaths: [],\n\t\tsampledNodes: new Set(),\n\t\tsampledEdges: new Set(),\n\t\tvisitedPerFrontier: [],\n\t\tstats: {\n\t\t\titerations: 0,\n\t\t\tnodesVisited: 0,\n\t\t\tedgesTraversed: 0,\n\t\t\tpathsFound: 0,\n\t\t\tdurationMs: performance.now() - startTime,\n\t\t\talgorithm,\n\t\t\ttermination: \"exhausted\",\n\t\t},\n\t};\n}\n","/**\n * BASE generator core.\n *\n * Contains the expansion loop as a generator function that yields GraphOp\n * objects instead of calling the graph directly. This allows the same logic\n * to be driven by either `runSync` (for in-process graphs) or `runAsync`\n * (for remote/lazy graphs).\n *\n * Used by `base()` (via `runSync`) and `baseAsync()` (via `runAsync`).\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport { PriorityQueue } from \"../structures/priority-queue\";\nimport type { GraphOp, GraphOpResponse } from \"../async/protocol\";\nimport { opNeighbours, opDegree } from \"../async/ops\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionPath,\n\tExpansionStats,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport {\n\ttype QueueEntry,\n\ttype ExpansionLimits,\n\tcontinueExpansion,\n\treconstructPath,\n\temptyResult,\n} from \"./base-helpers\";\n\n/**\n * Default priority function — degree-ordered (DOME).\n *\n * Lower degree = higher priority, so sparse nodes are explored before hubs.\n */\nfunction degreePriority<N extends NodeData, E extends EdgeData>(\n\t_nodeId: NodeId,\n\tcontext: PriorityContext<N, E>,\n): number {\n\treturn context.degree;\n}\n\n/**\n * Generator core of the BASE expansion algorithm.\n *\n * Yields GraphOp objects to request graph data, allowing the caller to\n * provide a sync or async runner. The optional `graphRef` parameter is\n * required when the priority function accesses `context.graph` — it is\n * populated in sync mode by `base()`. In async mode (Phase 4+), a proxy\n * graph may be supplied instead.\n *\n * @param graphMeta - Immutable graph metadata (directed, nodeCount, edgeCount)\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration (priority, limits, debug)\n * @param graphRef - Optional real graph reference for context.graph in priority functions\n * @returns An ExpansionResult with all discovered paths and statistics\n */\nexport function* baseCore<N extends NodeData, E extends EdgeData>(\n\tgraphMeta: {\n\t\treadonly directed: boolean;\n\t\treadonly nodeCount: number;\n\t\treadonly edgeCount: number;\n\t},\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n\tgraphRef?: ReadableGraph<N, E>,\n): Generator<GraphOp, ExpansionResult, GraphOpResponse<N, E>> {\n\tconst startTime = performance.now();\n\n\tconst {\n\t\tmaxNodes = 0,\n\t\tmaxIterations = 0,\n\t\tmaxPaths = 0,\n\t\tpriority = degreePriority,\n\t\tdebug = false,\n\t} = config ?? {};\n\n\tif (seeds.length === 0) {\n\t\treturn emptyResult(\"base\", startTime);\n\t}\n\n\t// Initialise frontiers — one per seed\n\tconst numFrontiers = seeds.length;\n\tconst allVisited = new Set<NodeId>();\n\tconst combinedVisited = new Map<NodeId, number>();\n\tconst visitedByFrontier: Map<NodeId, number>[] = [];\n\tconst predecessors: Map<NodeId, NodeId | null>[] = [];\n\tconst queues: PriorityQueue<QueueEntry>[] = [];\n\n\tfor (let i = 0; i < numFrontiers; i++) {\n\t\tvisitedByFrontier.push(new Map());\n\t\tpredecessors.push(new Map());\n\t\tqueues.push(new PriorityQueue<QueueEntry>());\n\n\t\tconst seed = seeds[i];\n\t\tif (seed === undefined) continue;\n\n\t\tconst seedNode = seed.id;\n\t\t// Note: seed is NOT marked as visited here — it will be marked when processed\n\t\t// like any other node, so it can be properly expanded.\n\t\tpredecessors[i]?.set(seedNode, null);\n\t\tcombinedVisited.set(seedNode, i);\n\t\tallVisited.add(seedNode);\n\n\t\t// Yield to get the seed's degree for priority context\n\t\tconst seedDegree = yield* opDegree<N, E>(seedNode);\n\n\t\tconst context = buildPriorityContext<N, E>(\n\t\t\tseedNode,\n\t\t\ti,\n\t\t\tcombinedVisited,\n\t\t\tallVisited,\n\t\t\t[],\n\t\t\t0,\n\t\t\tseedDegree,\n\t\t\tgraphRef,\n\t\t);\n\n\t\tconst seedPriority = priority(seedNode, context);\n\t\tqueues[i]?.push(\n\t\t\t{\n\t\t\t\tnodeId: seedNode,\n\t\t\t\tfrontierIndex: i,\n\t\t\t\tpredecessor: null,\n\t\t\t},\n\t\t\tseedPriority,\n\t\t);\n\t}\n\n\tconst sampledEdgeMap = new Map<NodeId, Set<NodeId>>();\n\tconst discoveredPaths: ExpansionPath[] = [];\n\tlet iterations = 0;\n\tlet edgesTraversed = 0;\n\tlet termination: ExpansionStats[\"termination\"] = \"exhausted\";\n\n\tconst limits: ExpansionLimits = { maxIterations, maxNodes, maxPaths };\n\n\t// Main expansion loop — limits are checked at the start of each iteration\n\tfor (;;) {\n\t\tconst check = continueExpansion(\n\t\t\titerations,\n\t\t\tallVisited.size,\n\t\t\tdiscoveredPaths.length,\n\t\t\tlimits,\n\t\t);\n\t\tif (!check.shouldContinue) {\n\t\t\ttermination = check.termination;\n\t\t\tbreak;\n\t\t}\n\n\t\t// Find the frontier with the globally lowest-priority entry\n\t\tlet lowestPriority = Number.POSITIVE_INFINITY;\n\t\tlet activeFrontier = -1;\n\n\t\tfor (let i = 0; i < numFrontiers; i++) {\n\t\t\tconst queue = queues[i];\n\t\t\tif (queue !== undefined && !queue.isEmpty()) {\n\t\t\t\tconst peek = queue.peek();\n\t\t\t\tif (peek !== undefined && peek.priority < lowestPriority) {\n\t\t\t\t\tlowestPriority = peek.priority;\n\t\t\t\t\tactiveFrontier = i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// All queues empty — expansion exhausted\n\t\tif (activeFrontier < 0) {\n\t\t\ttermination = \"exhausted\";\n\t\t\tbreak;\n\t\t}\n\n\t\tconst queue = queues[activeFrontier];\n\t\tif (queue === undefined) break;\n\n\t\tconst entry = queue.pop();\n\t\tif (entry === undefined) break;\n\n\t\tconst { nodeId, predecessor } = entry.item;\n\n\t\t// Skip if already visited by this frontier\n\t\tconst frontierVisited = visitedByFrontier[activeFrontier];\n\t\tif (frontierVisited === undefined || frontierVisited.has(nodeId)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Mark visited\n\t\tfrontierVisited.set(nodeId, activeFrontier);\n\t\tcombinedVisited.set(nodeId, activeFrontier);\n\t\tif (predecessor !== null) {\n\t\t\tconst predMap = predecessors[activeFrontier];\n\t\t\tif (predMap !== undefined) {\n\t\t\t\tpredMap.set(nodeId, predecessor);\n\t\t\t}\n\t\t}\n\t\tallVisited.add(nodeId);\n\n\t\tif (debug) {\n\t\t\tconsole.log(\n\t\t\t\t`[BASE] Iteration ${String(iterations)}: Frontier ${String(activeFrontier)} visiting ${nodeId}`,\n\t\t\t);\n\t\t}\n\n\t\t// Check for collision with other frontiers\n\t\tfor (let otherFrontier = 0; otherFrontier < numFrontiers; otherFrontier++) {\n\t\t\tif (otherFrontier === activeFrontier) continue;\n\n\t\t\tconst otherVisited = visitedByFrontier[otherFrontier];\n\t\t\tif (otherVisited === undefined) continue;\n\n\t\t\tif (otherVisited.has(nodeId)) {\n\t\t\t\t// Collision! Reconstruct the path between the two frontiers.\n\t\t\t\tconst path = reconstructPath(\n\t\t\t\t\tnodeId,\n\t\t\t\t\tactiveFrontier,\n\t\t\t\t\totherFrontier,\n\t\t\t\t\tpredecessors,\n\t\t\t\t\tseeds,\n\t\t\t\t);\n\t\t\t\tif (path !== null) {\n\t\t\t\t\tdiscoveredPaths.push(path);\n\t\t\t\t\tif (debug) {\n\t\t\t\t\t\tconsole.log(`[BASE] Path found: ${path.nodes.join(\" -> \")}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Expand neighbours — yield to the runner to retrieve them\n\t\tconst neighbours = yield* opNeighbours<N, E>(nodeId);\n\t\tfor (const neighbour of neighbours) {\n\t\t\tedgesTraversed++;\n\n\t\t\t// Track the sampled edge (normalised so source < target)\n\t\t\tconst [s, t] =\n\t\t\t\tnodeId < neighbour ? [nodeId, neighbour] : [neighbour, nodeId];\n\t\t\tlet targets = sampledEdgeMap.get(s);\n\t\t\tif (targets === undefined) {\n\t\t\t\ttargets = new Set();\n\t\t\t\tsampledEdgeMap.set(s, targets);\n\t\t\t}\n\t\t\ttargets.add(t);\n\n\t\t\t// Skip if already visited by this frontier\n\t\t\tconst fv = visitedByFrontier[activeFrontier];\n\t\t\tif (fv === undefined || fv.has(neighbour)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Yield to get the neighbour's degree for priority context\n\t\t\tconst neighbourDegree = yield* opDegree<N, E>(neighbour);\n\n\t\t\tconst context = buildPriorityContext<N, E>(\n\t\t\t\tneighbour,\n\t\t\t\tactiveFrontier,\n\t\t\t\tcombinedVisited,\n\t\t\t\tallVisited,\n\t\t\t\tdiscoveredPaths,\n\t\t\t\titerations + 1,\n\t\t\t\tneighbourDegree,\n\t\t\t\tgraphRef,\n\t\t\t);\n\n\t\t\tconst neighbourPriority = priority(neighbour, context);\n\n\t\t\tqueue.push(\n\t\t\t\t{\n\t\t\t\t\tnodeId: neighbour,\n\t\t\t\t\tfrontierIndex: activeFrontier,\n\t\t\t\t\tpredecessor: nodeId,\n\t\t\t\t},\n\t\t\t\tneighbourPriority,\n\t\t\t);\n\t\t}\n\n\t\titerations++;\n\t}\n\n\tconst endTime = performance.now();\n\tconst visitedPerFrontier = visitedByFrontier.map((m) => new Set(m.keys()));\n\n\t// Convert sampled edges to readonly tuples\n\tconst edgeTuples = new Set<readonly [NodeId, NodeId]>();\n\tfor (const [source, targets] of sampledEdgeMap) {\n\t\tfor (const target of targets) {\n\t\t\tedgeTuples.add([source, target] as const);\n\t\t}\n\t}\n\n\treturn {\n\t\tpaths: discoveredPaths,\n\t\tsampledNodes: allVisited,\n\t\tsampledEdges: edgeTuples,\n\t\tvisitedPerFrontier,\n\t\tstats: {\n\t\t\titerations,\n\t\t\tnodesVisited: allVisited.size,\n\t\t\tedgesTraversed,\n\t\t\tpathsFound: discoveredPaths.length,\n\t\t\tdurationMs: endTime - startTime,\n\t\t\talgorithm: \"base\",\n\t\t\ttermination,\n\t\t},\n\t};\n}\n\n/**\n * Create a sentinel ReadableGraph that throws if any member is accessed.\n *\n * Used in async mode when no graphRef is provided. Gives a clear error\n * message rather than silently returning incorrect results if a priority\n * function attempts to access `context.graph` before Phase 4b introduces\n * a real async proxy.\n */\nfunction makeNoGraphSentinel<\n\tN extends NodeData,\n\tE extends EdgeData,\n>(): ReadableGraph<N, E> {\n\tconst msg =\n\t\t\"Priority function accessed context.graph in async mode without a graph proxy. \" +\n\t\t\"Pass a graphRef or use a priority function that does not access context.graph.\";\n\tconst fail = (): never => {\n\t\tthrow new Error(msg);\n\t};\n\treturn {\n\t\tget directed(): boolean {\n\t\t\treturn fail();\n\t\t},\n\t\tget nodeCount(): number {\n\t\t\treturn fail();\n\t\t},\n\t\tget edgeCount(): number {\n\t\t\treturn fail();\n\t\t},\n\t\thasNode: fail,\n\t\tgetNode: fail,\n\t\tnodeIds: fail,\n\t\tneighbours: fail,\n\t\tdegree: fail,\n\t\tgetEdge: fail,\n\t\tedges: fail,\n\t};\n}\n\n/**\n * Build a PriorityContext for a node using a pre-fetched degree.\n *\n * When `graphRef` is provided (sync mode), it is used as `context.graph` so\n * priority functions can access the graph directly. When it is absent (async\n * mode), a Proxy is used in its place that throws a clear error if any\n * property is accessed — this prevents silent failures until Phase 4b\n * introduces a real async proxy graph.\n */\nfunction buildPriorityContext<N extends NodeData, E extends EdgeData>(\n\t_nodeId: NodeId,\n\tfrontierIndex: number,\n\tcombinedVisited: ReadonlyMap<NodeId, number>,\n\tallVisited: ReadonlySet<NodeId>,\n\tdiscoveredPaths: readonly ExpansionPath[],\n\titeration: number,\n\tdegree: number,\n\tgraphRef: ReadableGraph<N, E> | undefined,\n): PriorityContext<N, E> {\n\t// Resolve the graph reference. In async mode without a proxy, we construct\n\t// a sentinel that satisfies the ReadableGraph<N, E> interface structurally\n\t// but throws a clear error if any method is called. Phase 4b will replace\n\t// this with a real async proxy graph.\n\tconst graph: ReadableGraph<N, E> = graphRef ?? makeNoGraphSentinel<N, E>();\n\n\treturn {\n\t\tgraph,\n\t\tdegree,\n\t\tfrontierIndex,\n\t\tvisitedByFrontier: combinedVisited,\n\t\tallVisited,\n\t\tdiscoveredPaths,\n\t\titeration,\n\t};\n}\n","/**\n * BASE (Bidirectional Adaptive Seed Expansion) entry points.\n *\n * Provides synchronous `base()` and asynchronous `baseAsync()` entry points.\n * Both delegate to the shared `baseCore` generator, which yields GraphOp\n * objects instead of calling the graph directly.\n *\n * - `base()` drives the generator via `runSync` (zero overhead vs the old impl)\n * - `baseAsync()` drives the generator via `runAsync` (supports cancellation\n * and progress callbacks)\n *\n * Key properties:\n * 1. Priority-ordered exploration — global min-priority across all frontiers\n * 2. Frontier collision detection — path recorded when frontiers meet\n * 3. Implicit termination — halts when all queues empty\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport { runSync, runAsync } from \"../async/runners\";\nimport type { AsyncRunnerOptions, YieldStrategy } from \"../async/types\";\nimport type { ProgressStats } from \"../async/protocol\";\nimport { baseCore } from \"./base-core\";\nimport type { Seed, ExpansionResult, ExpansionConfig } from \"./types\";\n\n/**\n * Configuration for the async BASE expansion algorithm.\n *\n * Extends ExpansionConfig with async runner options (cancellation, progress,\n * yield strategy).\n */\nexport interface AsyncExpansionConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>\n\textends ExpansionConfig<N, E>, AsyncRunnerOptions {}\n\n/**\n * Run BASE expansion synchronously.\n *\n * Delegates to baseCore + runSync. Behaviour is identical to the previous\n * direct implementation — all existing callers are unaffected.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function base<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\tconst gen = baseCore<N, E>(\n\t\t{\n\t\t\tdirected: graph.directed,\n\t\t\tnodeCount: graph.nodeCount,\n\t\t\tedgeCount: graph.edgeCount,\n\t\t},\n\t\tseeds,\n\t\tconfig,\n\t\tgraph,\n\t);\n\treturn runSync(gen, graph);\n}\n\n/**\n * Run BASE expansion asynchronously.\n *\n * Delegates to baseCore + runAsync. Supports:\n * - Cancellation via AbortSignal (config.signal)\n * - Progress callbacks (config.onProgress)\n * - Custom cooperative yield strategies (config.yieldStrategy)\n *\n * Note: priority functions that access `context.graph` are not supported in\n * async mode without a graph proxy (Phase 4b). The default degree-based\n * priority (DOME) does not access context.graph and works correctly.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function baseAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\tconst [nodeCount, edgeCount] = await Promise.all([\n\t\tgraph.nodeCount,\n\t\tgraph.edgeCount,\n\t]);\n\tconst gen = baseCore<N, E>(\n\t\t{ directed: graph.directed, nodeCount, edgeCount },\n\t\tseeds,\n\t\tconfig,\n\t\t// No graphRef in async mode — priority functions that access\n\t\t// context.graph will receive the sentinel and throw. Phase 4b will\n\t\t// inject a real async proxy graph here.\n\t);\n\t// Build runner options conditionally to satisfy exactOptionalPropertyTypes:\n\t// optional properties must be omitted entirely rather than set to undefined.\n\tconst runnerOptions: {\n\t\tsignal?: AbortSignal;\n\t\tonProgress?: (stats: ProgressStats) => void | Promise<void>;\n\t\tyieldStrategy?: YieldStrategy;\n\t} = {};\n\tif (config?.signal !== undefined) {\n\t\trunnerOptions.signal = config.signal;\n\t}\n\tif (config?.onProgress !== undefined) {\n\t\trunnerOptions.onProgress = config.onProgress;\n\t}\n\tif (config?.yieldStrategy !== undefined) {\n\t\trunnerOptions.yieldStrategy = config.yieldStrategy;\n\t}\n\treturn runAsync(gen, graph, runnerOptions);\n}\n","/**\n * DOME (Degree-Ordered Multi-Expansion) algorithm.\n *\n * Simplest BASE variant: priority = node degree.\n * Lower degree nodes are expanded first (can be reversed via config).\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base, baseAsync } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\n\n/**\n * DOME priority: lower degree is expanded first.\n */\nfunction domePriority<N extends NodeData, E extends EdgeData>(\n\t_nodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\treturn context.degree;\n}\n\n/**\n * DOME high-degree priority: negate degree to prioritise high-degree nodes.\n */\nfunction domeHighDegreePriority<N extends NodeData, E extends EdgeData>(\n\t_nodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\treturn -context.degree;\n}\n\n/**\n * Run DOME expansion (degree-ordered).\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function dome<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: domePriority,\n\t});\n}\n\n/**\n * Run DOME expansion asynchronously (degree-ordered).\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function domeAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn baseAsync(graph, seeds, { ...config, priority: domePriority });\n}\n\n/**\n * DOME with reverse priority (high degree first).\n */\nexport function domeHighDegree<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: domeHighDegreePriority,\n\t});\n}\n\n/**\n * Run DOME high-degree expansion asynchronously (high degree first).\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function domeHighDegreeAsync<\n\tN extends NodeData,\n\tE extends EdgeData,\n>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn baseAsync(graph, seeds, {\n\t\t...config,\n\t\tpriority: domeHighDegreePriority,\n\t});\n}\n","/**\n * HAE (Heterogeneity-Aware Expansion) algorithm.\n *\n * Generalises EDGE with user-supplied type mapper for flexible type extraction.\n * Priority function: π(v) = (1 / (H_local(v) + ε)) × log(deg(v) + 1)\n *\n * where H_local(v) = Shannon entropy of the neighbour type distribution\n * computed using the provided typeMapper function.\n *\n * Allows custom type extraction beyond node.type.\n * Degenerates to DOME on homogeneous graphs (all types the same).\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { localTypeEntropy } from \"../utils/entropy\";\n\nconst EPSILON = 1e-10;\n\n/**\n * Configuration for HAE, extending base ExpansionConfig.\n */\nexport interface HAEConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> extends ExpansionConfig<N, E> {\n\t/** Function to extract type from a node (default: node.type) */\n\treadonly typeMapper?: (node: N) => string;\n}\n\n/**\n * Default type mapper - uses node.type property.\n */\nfunction defaultTypeMapper(node: NodeData): string {\n\treturn node.type ?? \"default\";\n}\n\n/**\n * Create a priority function using the given type mapper.\n */\nfunction createHAEPriority<N extends NodeData, E extends EdgeData>(\n\ttypeMapper: (node: N) => string,\n) {\n\treturn function haePriority(\n\t\tnodeId: string,\n\t\tcontext: PriorityContext<N, E>,\n\t): number {\n\t\tconst graph = context.graph;\n\t\tconst neighbours = graph.neighbours(nodeId);\n\n\t\t// Collect neighbour types using the custom mapper\n\t\tconst neighbourTypes: string[] = [];\n\t\tfor (const neighbour of neighbours) {\n\t\t\tconst node = graph.getNode(neighbour);\n\t\t\tif (node !== undefined) {\n\t\t\t\tneighbourTypes.push(typeMapper(node));\n\t\t\t}\n\t\t}\n\n\t\t// Compute local type entropy\n\t\tconst entropy = localTypeEntropy(neighbourTypes);\n\n\t\t// Priority = 1 / (entropy + ε) * log(degree + 1)\n\t\treturn (1 / (entropy + EPSILON)) * Math.log(context.degree + 1);\n\t};\n}\n\n/**\n * Run HAE expansion (Heterogeneity-Aware Expansion).\n *\n * Discovers paths by prioritising nodes with diverse neighbour types,\n * using a custom type mapper for flexible type extraction.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - HAE configuration with optional typeMapper\n * @returns Expansion result with discovered paths\n */\nexport function hae<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: HAEConfig<N, E>,\n): ExpansionResult {\n\tconst typeMapper = config?.typeMapper ?? defaultTypeMapper;\n\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: createHAEPriority<N, E>(typeMapper),\n\t});\n}\n\n/**\n * Run HAE expansion asynchronously.\n *\n * Note: the HAE priority function accesses `context.graph` to retrieve\n * neighbour types. Full async equivalence requires PriorityContext\n * refactoring (Phase 4b deferred). This export establishes the async API\n * surface; use with a `wrapAsync`-wrapped sync graph for testing.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - HAE configuration combined with async runner options\n * @returns Promise resolving to the expansion result\n */\nexport async function haeAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: HAEConfig<N, E> & AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\tconst typeMapper = config?.typeMapper ?? defaultTypeMapper;\n\n\treturn baseAsync(graph, seeds, {\n\t\t...config,\n\t\tpriority: createHAEPriority<N, E>(typeMapper),\n\t});\n}\n","/**\n * EDGE (Entropy-Driven Graph Expansion) algorithm.\n *\n * Discovers paths by prioritising nodes with diverse neighbour types.\n * Priority function: π(v) = (1 / (H_local(v) + ε)) × log(deg(v) + 1)\n *\n * where H_local(v) = Shannon entropy of the neighbour type distribution.\n *\n * High entropy (diverse types) → lower priority → expanded sooner.\n * Low entropy (homogeneous types) → higher priority → deferred.\n *\n * Delegates to HAE with the default `node.type` mapper.\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type { Seed, ExpansionResult, ExpansionConfig } from \"./types\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { hae, haeAsync } from \"./hae\";\n\n/** Default type mapper: reads `node.type`, falling back to \"default\". */\nconst defaultTypeMapper = (n: NodeData): string =>\n\ttypeof n.type === \"string\" ? n.type : \"default\";\n\n/**\n * Run EDGE expansion (Entropy-Driven Graph Expansion).\n *\n * Discovers paths by prioritising nodes with diverse neighbour types,\n * deferring nodes with homogeneous neighbourhoods.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function edge<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn hae(graph, seeds, {\n\t\t...config,\n\t\ttypeMapper: defaultTypeMapper,\n\t});\n}\n\n/**\n * Run EDGE expansion asynchronously.\n *\n * Delegates to `haeAsync` with the default `node.type` mapper.\n *\n * Note: the HAE priority function accesses `context.graph` to retrieve\n * neighbour types. Full async equivalence requires PriorityContext\n * refactoring (Phase 4b deferred). This export establishes the async API\n * surface; use with a `wrapAsync`-wrapped sync graph for testing.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function edgeAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn haeAsync(graph, seeds, {\n\t\t...config,\n\t\ttypeMapper: defaultTypeMapper,\n\t});\n}\n","/**\n * PIPE (Path-Potential Informed Priority Expansion) algorithm.\n *\n * Discovers paths by prioritising nodes that bridge multiple frontiers.\n * Priority function: π(v) = deg(v) / (1 + path_potential(v))\n *\n * where path_potential(v) = count of v's neighbours already visited by OTHER seed frontiers.\n *\n * High path potential (many bridges to other frontiers) → lower priority → expanded sooner.\n * Low path potential (few bridges) → higher priority → deferred.\n *\n * This incentivises discovery of paths that connect multiple frontiers.\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\n\n/**\n * Priority function using path potential.\n * Lower values = higher priority (expanded first).\n *\n * Path potential measures how many of a node's neighbours have been\n * visited by OTHER frontiers (not the current frontier).\n */\nfunction pipePriority<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\tconst graph = context.graph;\n\tconst neighbours = graph.neighbours(nodeId);\n\n\t// Count how many neighbours have been visited by OTHER frontiers\n\tlet pathPotential = 0;\n\tfor (const neighbour of neighbours) {\n\t\tconst visitedBy = context.visitedByFrontier.get(neighbour);\n\t\tif (visitedBy !== undefined && visitedBy !== context.frontierIndex) {\n\t\t\tpathPotential++;\n\t\t}\n\t}\n\n\t// Priority = degree / (1 + path_potential)\n\t// High path potential → lower priority (expanded sooner)\n\treturn context.degree / (1 + pathPotential);\n}\n\n/**\n * Run PIPE expansion (Path-Potential Informed Priority Expansion).\n *\n * Discovers paths by prioritising nodes that bridge multiple frontiers,\n * identifying connecting points between seed regions.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function pipe<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: pipePriority,\n\t});\n}\n\n/**\n * Run PIPE expansion asynchronously.\n *\n * Note: the PIPE priority function accesses `context.graph` to retrieve\n * neighbour lists. Full async equivalence requires PriorityContext\n * refactoring (Phase 4b deferred). This export establishes the async API\n * surface; use with a `wrapAsync`-wrapped sync graph for testing.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function pipeAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn baseAsync(graph, seeds, {\n\t\t...config,\n\t\tpriority: pipePriority,\n\t});\n}\n","/**\n * Shared helper functions for expansion algorithm priority computations.\n *\n * These utilities encapsulate repeated computation patterns across\n * multiple expansion algorithms.\n *\n * @module expansion/priority-helpers\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { PriorityContext, ExpansionPath } from \"./types\";\n\n/**\n * Compute the average mutual information between a node and all visited\n * nodes in the same frontier.\n *\n * Returns a value in [0, 1] — higher means the node is more similar\n * (on average) to already-visited same-frontier nodes.\n *\n * @param graph - Source graph\n * @param nodeId - Node being prioritised\n * @param context - Current priority context\n * @param mi - MI function to use for pairwise scoring\n * @returns Average MI score, or 0 if no same-frontier visited nodes exist\n */\nexport function avgFrontierMI<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tnodeId: NodeId,\n\tcontext: PriorityContext<N, E>,\n\tmi: (graph: ReadableGraph<N, E>, source: string, target: string) => number,\n): number {\n\tconst { frontierIndex, visitedByFrontier } = context;\n\n\tlet total = 0;\n\tlet count = 0;\n\n\tfor (const [visitedId, idx] of visitedByFrontier) {\n\t\tif (idx === frontierIndex && visitedId !== nodeId) {\n\t\t\ttotal += mi(graph, visitedId, nodeId);\n\t\t\tcount++;\n\t\t}\n\t}\n\n\treturn count > 0 ? total / count : 0;\n}\n\n/**\n * Count the number of a node's neighbours that have been visited by\n * frontiers other than the node's own frontier.\n *\n * A higher count indicates this node is likely to bridge two frontiers,\n * making it a strong candidate for path completion.\n *\n * @param graph - Source graph\n * @param nodeId - Node being evaluated\n * @param context - Current priority context\n * @returns Number of neighbours visited by other frontiers\n */\nexport function countCrossFrontierNeighbours<\n\tN extends NodeData,\n\tE extends EdgeData,\n>(\n\tgraph: ReadableGraph<N, E>,\n\tnodeId: NodeId,\n\tcontext: PriorityContext<N, E>,\n): number {\n\tconst { frontierIndex, visitedByFrontier } = context;\n\tconst nodeNeighbours = new Set(graph.neighbours(nodeId));\n\n\tlet count = 0;\n\tfor (const [visitedId, idx] of visitedByFrontier) {\n\t\tif (idx !== frontierIndex && nodeNeighbours.has(visitedId)) {\n\t\t\tcount++;\n\t\t}\n\t}\n\n\treturn count;\n}\n\n/**\n * Incrementally update salience counts for paths discovered since the\n * last update.\n *\n * Iterates only over paths from `fromIndex` onwards, avoiding redundant\n * re-processing of already-counted paths.\n *\n * @param salienceCounts - Mutable map of node ID to salience count (mutated in place)\n * @param paths - Full list of discovered paths\n * @param fromIndex - Index to start counting from (exclusive of earlier paths)\n * @returns The new `fromIndex` value (i.e. `paths.length` after update)\n */\nexport function updateSalienceCounts(\n\tsalienceCounts: Map<NodeId, number>,\n\tpaths: readonly ExpansionPath[],\n\tfromIndex: number,\n): number {\n\tfor (let i = fromIndex; i < paths.length; i++) {\n\t\tconst path = paths[i];\n\t\tif (path !== undefined) {\n\t\t\tfor (const node of path.nodes) {\n\t\t\t\tsalienceCounts.set(node, (salienceCounts.get(node) ?? 0) + 1);\n\t\t\t}\n\t\t}\n\t}\n\treturn paths.length;\n}\n","/**\n * SAGE (Salience-Accumulation Guided Expansion) algorithm.\n *\n * Two-phase expansion algorithm that tracks how often nodes appear\n * in discovered paths and uses this salience to guide expansion.\n *\n * Phase 1 (before first path): priority = log(degree + 1)\n * Phase 2 (after first path): priority = -(salience(v) × 1000 - degree)\n *\n * where salience(v) = count of discovered paths containing v\n *\n * In phase 2, nodes that appear frequently in paths are deprioritised,\n * encouraging exploration of fresh frontier regions.\n *\n * @module expansion/sage\n */\n\nimport type { NodeData, EdgeData, ReadableGraph, NodeId } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { updateSalienceCounts } from \"./priority-helpers\";\n\n/**\n * Run SAGE expansion algorithm.\n *\n * Salience-aware multi-frontier expansion with two phases:\n * - Phase 1: Degree-based priority (early exploration)\n * - Phase 2: Salience feedback (path-aware frontier steering)\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function sage<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\t// Closure state: encapsulate phase tracking and salience counts per call\n\tconst salienceCounts = new Map<NodeId, number>();\n\tlet inPhase2 = false;\n\tlet lastPathCount = 0;\n\n\t/**\n\t * SAGE priority function with phase transition logic.\n\t */\n\tfunction sagePriority(\n\t\tnodeId: NodeId,\n\t\tcontext: PriorityContext<N, E>,\n\t): number {\n\t\tconst pathCount = context.discoveredPaths.length;\n\n\t\t// Detect phase transition: first path discovered\n\t\tif (pathCount > 0 && !inPhase2) {\n\t\t\tinPhase2 = true;\n\t\t\t// No scan needed — the incremental update below handles it\n\t\t}\n\n\t\t// Update salience counts for newly discovered paths\n\t\tif (pathCount > lastPathCount) {\n\t\t\tlastPathCount = updateSalienceCounts(\n\t\t\t\tsalienceCounts,\n\t\t\t\tcontext.discoveredPaths,\n\t\t\t\tlastPathCount,\n\t\t\t);\n\t\t}\n\n\t\t// Phase 1: Degree-based priority before first path\n\t\tif (!inPhase2) {\n\t\t\treturn Math.log(context.degree + 1);\n\t\t}\n\n\t\t// Phase 2: Salience-guided priority\n\t\t// Nodes with high salience are deprioritised, fresh nodes get lower priority\n\t\tconst salience = salienceCounts.get(nodeId) ?? 0;\n\t\treturn -(salience * 1000 - context.degree);\n\t}\n\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: sagePriority,\n\t});\n}\n\n/**\n * Run SAGE expansion asynchronously.\n *\n * Creates fresh closure state (salienceCounts, phase tracking) for this\n * invocation. The SAGE priority function does not access `context.graph`\n * directly, so it is safe to use in async mode via `baseAsync`.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function sageAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\t// Fresh closure state — independent of any concurrent sync invocations\n\tconst salienceCounts = new Map<NodeId, number>();\n\tlet inPhase2 = false;\n\tlet lastPathCount = 0;\n\n\tfunction sagePriority(\n\t\tnodeId: NodeId,\n\t\tcontext: PriorityContext<N, E>,\n\t): number {\n\t\tconst pathCount = context.discoveredPaths.length;\n\n\t\tif (pathCount > 0 && !inPhase2) {\n\t\t\tinPhase2 = true;\n\t\t}\n\n\t\tif (pathCount > lastPathCount) {\n\t\t\tlastPathCount = updateSalienceCounts(\n\t\t\t\tsalienceCounts,\n\t\t\t\tcontext.discoveredPaths,\n\t\t\t\tlastPathCount,\n\t\t\t);\n\t\t}\n\n\t\tif (!inPhase2) {\n\t\t\treturn Math.log(context.degree + 1);\n\t\t}\n\n\t\tconst salience = salienceCounts.get(nodeId) ?? 0;\n\t\treturn -(salience * 1000 - context.degree);\n\t}\n\n\treturn baseAsync(graph, seeds, { ...config, priority: sagePriority });\n}\n","/**\n * REACH (Retrospective Expansion with Adaptive Convergence Heuristic) algorithm.\n *\n * Two-phase expansion algorithm that computes mean Jaccard similarity\n * between candidate nodes and discovered path endpoints, using this\n * mutual information estimate to guide expansion.\n *\n * Phase 1 (before first path): priority = log(degree + 1)\n * Phase 2 (after first path): priority = log(degree + 1) × (1 - MI_hat(v))\n *\n * where MI_hat(v) = mean Jaccard(N(v), N(endpoint)) over all discovered\n * path endpoints (source and target of each path).\n *\n * In phase 2, nodes with high neighbourhood similarity to path endpoints\n * are deprioritised, encouraging discovery of structurally dissimilar paths.\n *\n * @module expansion/reach\n */\n\nimport type { NodeData, EdgeData, ReadableGraph, NodeId } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { jaccard } from \"../ranking/mi/jaccard\";\n\n/**\n * Run REACH expansion algorithm.\n *\n * Mutual information-aware multi-frontier expansion with two phases:\n * - Phase 1: Degree-based priority (early exploration)\n * - Phase 2: Structural similarity feedback (MI-guided frontier steering)\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function reach<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\t// Closure state: encapsulate phase tracking\n\tlet inPhase2 = false;\n\n\t// Closure state: cache Jaccard scores by (source, target) key\n\t// Symmetric property ensures consistent ordering\n\tconst jaccardCache = new Map<string, number>();\n\n\t/**\n\t * Compute Jaccard similarity with caching.\n\t *\n\t * Exploits symmetry of Jaccard (J(A,B) = J(B,A)) to reduce\n\t * duplicate computations when the same pair appears in multiple\n\t * discovered paths. Key format ensures consistent ordering.\n\t */\n\tfunction cachedJaccard(source: NodeId, target: NodeId): number {\n\t\t// Symmetric key: consistent ordering ensures cache hits\n\t\tconst key =\n\t\t\tsource < target ? `${source}::${target}` : `${target}::${source}`;\n\n\t\tlet score = jaccardCache.get(key);\n\t\tif (score === undefined) {\n\t\t\tscore = jaccard(graph, source, target);\n\t\t\tjaccardCache.set(key, score);\n\t\t}\n\n\t\treturn score;\n\t}\n\n\t/**\n\t * REACH priority function with MI estimation.\n\t */\n\tfunction reachPriority(\n\t\tnodeId: NodeId,\n\t\tcontext: PriorityContext<N, E>,\n\t): number {\n\t\tconst pathCount = context.discoveredPaths.length;\n\n\t\t// Detect phase transition: first path discovered\n\t\tif (pathCount > 0 && !inPhase2) {\n\t\t\tinPhase2 = true;\n\t\t}\n\n\t\t// Phase 1: Degree-based priority before first path\n\t\tif (!inPhase2) {\n\t\t\treturn Math.log(context.degree + 1);\n\t\t}\n\n\t\t// Phase 2: Compute MI_hat(v) = mean Jaccard to all discovered path endpoints\n\t\t// Collect all endpoint nodes from discovered paths\n\t\tlet totalMI = 0;\n\t\tlet endpointCount = 0;\n\n\t\tfor (const path of context.discoveredPaths) {\n\t\t\tconst fromNodeId = path.fromSeed.id;\n\t\t\tconst toNodeId = path.toSeed.id;\n\n\t\t\t// Compute Jaccard similarity between candidate node and each endpoint\n\t\t\t// Using cached variant to avoid recomputing identical pairs\n\t\t\ttotalMI += cachedJaccard(nodeId, fromNodeId);\n\t\t\ttotalMI += cachedJaccard(nodeId, toNodeId);\n\t\t\tendpointCount += 2;\n\t\t}\n\n\t\tconst miHat = endpointCount > 0 ? totalMI / endpointCount : 0;\n\n\t\t// Phase 2 priority: degree-weighted by dissimilarity\n\t\t// Lower MI → lower priority value → expanded first\n\t\treturn Math.log(context.degree + 1) * (1 - miHat);\n\t}\n\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: reachPriority,\n\t});\n}\n\n/**\n * Run REACH expansion asynchronously.\n *\n * Creates fresh closure state (phase tracking, Jaccard cache) for this\n * invocation. The REACH priority function uses `jaccard(context.graph, ...)`\n * in Phase 2; in async mode `context.graph` is the sentinel and will throw.\n * Full async equivalence requires PriorityContext refactoring (Phase 4b\n * deferred). This export establishes the async API surface.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function reachAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\t// Fresh closure state — independent of any concurrent sync invocations\n\tlet inPhase2 = false;\n\n\t// Note: in Phase 2, jaccard(context.graph, ...) is called. In async mode,\n\t// context.graph is the sentinel and will throw. Phase 4b will resolve this.\n\tfunction reachPriority(\n\t\tnodeId: NodeId,\n\t\tcontext: PriorityContext<N, E>,\n\t): number {\n\t\tconst pathCount = context.discoveredPaths.length;\n\n\t\tif (pathCount > 0 && !inPhase2) {\n\t\t\tinPhase2 = true;\n\t\t}\n\n\t\tif (!inPhase2) {\n\t\t\treturn Math.log(context.degree + 1);\n\t\t}\n\n\t\tlet totalMI = 0;\n\t\tlet endpointCount = 0;\n\n\t\tfor (const path of context.discoveredPaths) {\n\t\t\t// context.graph is the sentinel in async mode — throws on access\n\t\t\ttotalMI += jaccard(context.graph, nodeId, path.fromSeed.id);\n\t\t\ttotalMI += jaccard(context.graph, nodeId, path.toSeed.id);\n\t\t\tendpointCount += 2;\n\t\t}\n\n\t\tconst miHat = endpointCount > 0 ? totalMI / endpointCount : 0;\n\t\treturn Math.log(context.degree + 1) * (1 - miHat);\n\t}\n\n\treturn baseAsync(graph, seeds, { ...config, priority: reachPriority });\n}\n","/**\n * MAZE (Multi-frontier Adaptive Zone) algorithm.\n *\n * Three-phase expansion algorithm combining path potential (PIPE) and\n * salience feedback (SAGE) with adaptive termination criteria.\n *\n * Phase 1 (before M paths found): π(v) = deg(v) / (1 + path_potential(v))\n * where path_potential(v) = count of neighbours visited by other frontiers\n *\n * Phase 2 (after M paths, salience feedback):\n * π(v) = [deg/(1+path_potential)] × [1/(1+λ×salience(v))]\n * where salience(v) = count of discovered paths containing v, λ = 1000\n *\n * Phase 3: Adaptive termination when combination of:\n * - Path count plateau (no new paths in recent iterations)\n * - Salience distribution stabilisation\n * - Frontier diversity convergence\n *\n * Simplified implementation: Phase 1 uses path potential, phase 2 adds\n * salience weighting, implicit phase 3 via collision detection.\n *\n * @module expansion/maze\n */\n\nimport type { NodeData, EdgeData, ReadableGraph, NodeId } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { updateSalienceCounts } from \"./priority-helpers\";\n\n/** Default threshold for switching to phase 2 (after M paths) */\nconst DEFAULT_PHASE2_THRESHOLD = 1;\n\n/** Salience weighting factor */\nconst SALIENCE_WEIGHT = 1000;\n\n/**\n * Run MAZE expansion algorithm.\n *\n * Multi-phase expansion combining path potential and salience with\n * adaptive frontier steering.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function maze<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\t// Closure state: encapsulate phase tracking and salience counts\n\tconst salienceCounts = new Map<NodeId, number>();\n\tlet inPhase2 = false;\n\tlet lastPathCount = 0;\n\n\t/**\n\t * MAZE priority function with path potential and salience feedback.\n\t */\n\tfunction mazePriority(\n\t\tnodeId: NodeId,\n\t\tcontext: PriorityContext<N, E>,\n\t): number {\n\t\tconst pathCount = context.discoveredPaths.length;\n\n\t\t// Detect phase transition: threshold of paths reached\n\t\tif (pathCount >= DEFAULT_PHASE2_THRESHOLD && !inPhase2) {\n\t\t\tinPhase2 = true;\n\t\t\t// Initialise salience counts from all existing paths\n\t\t\tupdateSalienceCounts(salienceCounts, context.discoveredPaths, 0);\n\t\t}\n\n\t\t// Incrementally update salience counts for newly discovered paths in phase 2\n\t\tif (inPhase2 && pathCount > lastPathCount) {\n\t\t\tlastPathCount = updateSalienceCounts(\n\t\t\t\tsalienceCounts,\n\t\t\t\tcontext.discoveredPaths,\n\t\t\t\tlastPathCount,\n\t\t\t);\n\t\t}\n\n\t\t// Compute path potential: neighbours visited by other frontiers\n\t\t// This is a bridge score indicating how likely this node is on important paths\n\t\tconst nodeNeighbours = graph.neighbours(nodeId);\n\t\tlet pathPotential = 0;\n\n\t\tfor (const neighbour of nodeNeighbours) {\n\t\t\tconst visitedBy = context.visitedByFrontier.get(neighbour);\n\t\t\tif (visitedBy !== undefined && visitedBy !== context.frontierIndex) {\n\t\t\t\tpathPotential++;\n\t\t\t}\n\t\t}\n\n\t\t// Phase 1: Path potential-based priority\n\t\t// Lower degree and high path potential = high priority (expanded first)\n\t\tif (!inPhase2) {\n\t\t\treturn context.degree / (1 + pathPotential);\n\t\t}\n\n\t\t// Phase 2: Salience-weighted path potential\n\t\t// Nodes on existing paths (high salience) are deprioritised\n\t\tconst salience = salienceCounts.get(nodeId) ?? 0;\n\t\tconst basePriority = context.degree / (1 + pathPotential);\n\t\tconst salienceFactor = 1 / (1 + SALIENCE_WEIGHT * salience);\n\n\t\treturn basePriority * salienceFactor;\n\t}\n\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: mazePriority,\n\t});\n}\n\n/**\n * Run MAZE expansion asynchronously.\n *\n * Creates fresh closure state (salienceCounts, phase tracking) for this\n * invocation. The MAZE priority function accesses `context.graph` to\n * retrieve neighbour lists for path potential computation. Full async\n * equivalence requires PriorityContext refactoring (Phase 4b deferred).\n * This export establishes the async API surface.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function mazeAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\t// Fresh closure state — independent of any concurrent sync invocations\n\tconst salienceCounts = new Map<NodeId, number>();\n\tlet inPhase2 = false;\n\tlet lastPathCount = 0;\n\n\tfunction mazePriority(\n\t\tnodeId: NodeId,\n\t\tcontext: PriorityContext<N, E>,\n\t): number {\n\t\tconst pathCount = context.discoveredPaths.length;\n\n\t\tif (pathCount >= DEFAULT_PHASE2_THRESHOLD && !inPhase2) {\n\t\t\tinPhase2 = true;\n\t\t\tupdateSalienceCounts(salienceCounts, context.discoveredPaths, 0);\n\t\t}\n\n\t\tif (inPhase2 && pathCount > lastPathCount) {\n\t\t\tlastPathCount = updateSalienceCounts(\n\t\t\t\tsalienceCounts,\n\t\t\t\tcontext.discoveredPaths,\n\t\t\t\tlastPathCount,\n\t\t\t);\n\t\t}\n\n\t\t// context.graph is the sentinel in pure async mode — Phase 4b will resolve this\n\t\tconst nodeNeighbours = context.graph.neighbours(nodeId);\n\t\tlet pathPotential = 0;\n\n\t\tfor (const neighbour of nodeNeighbours) {\n\t\t\tconst visitedBy = context.visitedByFrontier.get(neighbour);\n\t\t\tif (visitedBy !== undefined && visitedBy !== context.frontierIndex) {\n\t\t\t\tpathPotential++;\n\t\t\t}\n\t\t}\n\n\t\tif (!inPhase2) {\n\t\t\treturn context.degree / (1 + pathPotential);\n\t\t}\n\n\t\tconst salience = salienceCounts.get(nodeId) ?? 0;\n\t\tconst basePriority = context.degree / (1 + pathPotential);\n\t\tconst salienceFactor = 1 / (1 + SALIENCE_WEIGHT * salience);\n\n\t\treturn basePriority * salienceFactor;\n\t}\n\n\treturn baseAsync(graph, seeds, { ...config, priority: mazePriority });\n}\n","/**\n * TIDE (Type-Integrated Degree Estimation) algorithm.\n *\n * Prioritises exploration by edge degree rather than node degree.\n * Expands edges with lower combined endpoint degrees first.\n *\n * Useful for finding paths through sparse regions of the graph,\n * avoiding dense clusters.\n *\n * @module expansion/tide\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\n\n/**\n * TIDE priority function.\n *\n * Priority = degree(source) + degree(target)\n * Lower values = higher priority (explored first)\n */\nfunction tidePriority<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\t// Sum of source degree and neighbour degrees\n\tconst graph = context.graph;\n\tlet totalDegree = context.degree;\n\n\tfor (const neighbour of graph.neighbours(nodeId)) {\n\t\ttotalDegree += graph.degree(neighbour);\n\t}\n\n\treturn totalDegree;\n}\n\n/**\n * Run TIDE expansion algorithm.\n *\n * Expands from seeds prioritising low-degree edges first.\n * Useful for avoiding hubs and exploring sparse regions.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function tide<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: tidePriority,\n\t});\n}\n\n/**\n * Run TIDE expansion asynchronously.\n *\n * Note: the TIDE priority function accesses `context.graph` to retrieve\n * neighbour lists and per-neighbour degrees. Full async equivalence\n * requires PriorityContext refactoring (Phase 4b deferred). This export\n * establishes the async API surface.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function tideAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn baseAsync(graph, seeds, {\n\t\t...config,\n\t\tpriority: tidePriority,\n\t});\n}\n","/**\n * LACE (Local Association Context Expansion) algorithm.\n *\n * Prioritises exploration by mutual information scores.\n * Expands high-MI edges first, favouring paths with strong associations.\n *\n * Requires MI function configuration.\n *\n * @module expansion/lace\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { jaccard } from \"../ranking/mi/jaccard\";\nimport { avgFrontierMI } from \"./priority-helpers\";\n\n/**\n * Configuration for LACE expansion.\n */\nexport interface LACEConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> extends ExpansionConfig<N, E> {\n\t/** MI function for computing edge priorities (default: jaccard) */\n\treadonly mi?: (\n\t\tgraph: ReadableGraph<N, E>,\n\t\tsource: string,\n\t\ttarget: string,\n\t) => number;\n}\n\n/**\n * LACE priority function.\n *\n * Priority = 1 - avgMI(node, same-frontier visited nodes)\n * Higher average MI = lower priority value = explored first\n */\nfunction lacePriority<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n\tmi: (graph: ReadableGraph<N, E>, source: string, target: string) => number,\n): number {\n\tconst avgMi = avgFrontierMI(context.graph, nodeId, context, mi);\n\t// Invert so higher MI = lower priority value = expanded first\n\treturn 1 - avgMi;\n}\n\n/**\n * Run LACE expansion algorithm.\n *\n * Expands from seeds prioritising high-MI edges.\n * Useful for finding paths with strong semantic associations.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration with MI function\n * @returns Expansion result with discovered paths\n */\nexport function lace<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: LACEConfig<N, E>,\n): ExpansionResult {\n\tconst { mi = jaccard, ...restConfig } = config ?? {};\n\n\tconst priority = (nodeId: string, context: PriorityContext<N, E>): number =>\n\t\tlacePriority(nodeId, context, mi);\n\n\treturn base(graph, seeds, {\n\t\t...restConfig,\n\t\tpriority,\n\t});\n}\n\n/**\n * Run LACE expansion asynchronously.\n *\n * Note: the LACE priority function accesses `context.graph` via\n * `avgFrontierMI`. Full async equivalence requires PriorityContext\n * refactoring (Phase 4b deferred). This export establishes the async\n * API surface.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - LACE configuration combined with async runner options\n * @returns Promise resolving to the expansion result\n */\nexport async function laceAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: LACEConfig<N, E> & AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\tconst { mi = jaccard, ...restConfig } = config ?? {};\n\n\tconst priority = (nodeId: string, context: PriorityContext<N, E>): number =>\n\t\tlacePriority(nodeId, context, mi);\n\n\treturn baseAsync(graph, seeds, { ...restConfig, priority });\n}\n","/**\n * PIPE (Path Importance Priority Expansion) algorithm.\n *\n * Prioritises nodes that are more likely to be on important paths.\n * Uses betweenness-like estimation based on neighbourhood overlap.\n *\n * Useful for finding paths through \"bridge\" nodes.\n *\n * @module expansion/warp\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { countCrossFrontierNeighbours } from \"./priority-helpers\";\n\n/**\n * WARP priority function.\n *\n * Priority = 1 / (1 + bridge_score)\n * Bridge score = cross-frontier neighbour count plus bonus for nodes\n * already on discovered paths.\n * Higher bridge score = more likely to complete paths = explored first.\n */\nfunction warpPriority<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\t// Count neighbours visited by other frontiers\n\tlet bridgeScore = countCrossFrontierNeighbours(\n\t\tcontext.graph,\n\t\tnodeId,\n\t\tcontext,\n\t);\n\n\t// Additional bonus for nodes already present on discovered paths\n\tfor (const path of context.discoveredPaths) {\n\t\tif (path.nodes.includes(nodeId)) {\n\t\t\tbridgeScore += 2;\n\t\t}\n\t}\n\n\t// Invert: higher bridge score = lower priority value = expanded first\n\treturn 1 / (1 + bridgeScore);\n}\n\n/**\n * Run WARP expansion algorithm.\n *\n * Expands from seeds prioritising bridge nodes.\n * Useful for finding paths through structurally important nodes.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function warp<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: warpPriority,\n\t});\n}\n\n/**\n * Run WARP expansion asynchronously.\n *\n * Note: the WARP priority function accesses `context.graph` via\n * `countCrossFrontierNeighbours`. Full async equivalence requires\n * PriorityContext refactoring (Phase 4b deferred). This export\n * establishes the async API surface.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function warpAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn baseAsync(graph, seeds, {\n\t\t...config,\n\t\tpriority: warpPriority,\n\t});\n}\n","/**\n * FUSE (Forward Unified Semantic Exploration-Aware Graph Expansion) algorithm.\n *\n * Two-phase expansion:\n * 1. Initial DOME-style expansion to discover candidate paths\n * 2. Re-prioritise based on path salience scores\n *\n * Combines structural exploration with semantic ranking.\n *\n * @module expansion/fuse\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { jaccard } from \"../ranking/mi/jaccard\";\nimport { avgFrontierMI } from \"./priority-helpers\";\n\n/**\n * Configuration for FUSE expansion.\n */\nexport interface FUSEConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> extends ExpansionConfig<N, E> {\n\t/** MI function for salience computation (default: jaccard) */\n\treadonly mi?: (\n\t\tgraph: ReadableGraph<N, E>,\n\t\tsource: string,\n\t\ttarget: string,\n\t) => number;\n\t/** Weight for salience component (0-1, default: 0.5) */\n\treadonly salienceWeight?: number;\n}\n\n/**\n * @deprecated Use {@link FUSEConfig} instead.\n */\nexport type SAGEConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> = FUSEConfig<N, E>;\n\n/**\n * FUSE priority function.\n *\n * Combines degree with average frontier MI as a salience proxy:\n * Priority = (1 - w) * degree + w * (1 - avgMI)\n * Lower values = higher priority; high salience lowers priority\n */\nfunction fusePriority<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n\tmi: (graph: ReadableGraph<N, E>, source: string, target: string) => number,\n\tsalienceWeight: number,\n): number {\n\tconst avgSalience = avgFrontierMI(context.graph, nodeId, context, mi);\n\n\t// Combine degree with salience — lower priority value = expanded first\n\tconst degreeComponent = (1 - salienceWeight) * context.degree;\n\tconst salienceComponent = salienceWeight * (1 - avgSalience);\n\n\treturn degreeComponent + salienceComponent;\n}\n\n/**\n * Run FUSE expansion algorithm.\n *\n * Combines structural exploration with semantic salience.\n * Useful for finding paths that are both short and semantically meaningful.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration with MI function\n * @returns Expansion result with discovered paths\n */\nexport function fuse<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: FUSEConfig<N, E>,\n): ExpansionResult {\n\tconst { mi = jaccard, salienceWeight = 0.5, ...restConfig } = config ?? {};\n\n\tconst priority = (nodeId: string, context: PriorityContext<N, E>): number =>\n\t\tfusePriority(nodeId, context, mi, salienceWeight);\n\n\treturn base(graph, seeds, {\n\t\t...restConfig,\n\t\tpriority,\n\t});\n}\n\n/**\n * Run FUSE expansion asynchronously.\n *\n * Note: the FUSE priority function accesses `context.graph` via\n * `avgFrontierMI`. Full async equivalence requires PriorityContext\n * refactoring (Phase 4b deferred). This export establishes the async\n * API surface.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - FUSE configuration combined with async runner options\n * @returns Promise resolving to the expansion result\n */\nexport async function fuseAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: FUSEConfig<N, E> & AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\tconst { mi = jaccard, salienceWeight = 0.5, ...restConfig } = config ?? {};\n\n\tconst priority = (nodeId: string, context: PriorityContext<N, E>): number =>\n\t\tfusePriority(nodeId, context, mi, salienceWeight);\n\n\treturn baseAsync(graph, seeds, { ...restConfig, priority });\n}\n","/**\n * REACH (Rank-Enhanced Adaptive Collision Hash) algorithm.\n *\n * Two-phase expansion:\n * 1. Phase 1: Degree-ordered expansion to collect MI statistics\n * 2. Phase 2: MI-guided expansion using learned thresholds\n *\n * Adapts to graph structure by learning optimal MI thresholds.\n *\n * @module expansion/sift\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { jaccard } from \"../ranking/mi/jaccard\";\nimport { avgFrontierMI } from \"./priority-helpers\";\n\n/**\n * Configuration for REACH expansion.\n */\nexport interface REACHConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> extends ExpansionConfig<N, E> {\n\t/** MI function for salience computation (default: jaccard) */\n\treadonly mi?: (\n\t\tgraph: ReadableGraph<N, E>,\n\t\tsource: string,\n\t\ttarget: string,\n\t) => number;\n\t/** MI percentile threshold for phase 2 (default: 0.25) */\n\treadonly miThreshold?: number;\n\t/** Maximum nodes for phase 1 sampling (default: 1000) */\n\treadonly phase1MaxNodes?: number;\n}\n\n/**\n * REACH (SIFT) priority function.\n *\n * Prioritises nodes with average frontier MI above the threshold;\n * falls back to degree-based ordering for those below it.\n */\nfunction siftPriority<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n\tmi: (graph: ReadableGraph<N, E>, source: string, target: string) => number,\n\tmiThreshold: number,\n): number {\n\tconst avgMi = avgFrontierMI(context.graph, nodeId, context, mi);\n\n\t// If MI is above threshold, give high priority (low value)\n\t// Otherwise, fall back to degree\n\tif (avgMi >= miThreshold) {\n\t\treturn 1 - avgMi; // High MI = low priority value\n\t} else {\n\t\treturn context.degree + 100; // Low MI = delayed expansion\n\t}\n}\n\n/**\n * Run SIFT expansion algorithm.\n *\n * Two-phase adaptive expansion that learns MI thresholds\n * from initial sampling, then uses them for guided expansion.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function sift<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: REACHConfig<N, E>,\n): ExpansionResult {\n\tconst { mi = jaccard, miThreshold = 0.25, ...restConfig } = config ?? {};\n\n\t// Run guided expansion with MI threshold\n\tconst priority = (nodeId: string, context: PriorityContext<N, E>): number =>\n\t\tsiftPriority(nodeId, context, mi, miThreshold);\n\n\treturn base(graph, seeds, {\n\t\t...restConfig,\n\t\tpriority,\n\t});\n}\n\n/**\n * Run SIFT expansion asynchronously.\n *\n * Note: the SIFT priority function accesses `context.graph` via\n * `avgFrontierMI`. Full async equivalence requires PriorityContext\n * refactoring (Phase 4b deferred). This export establishes the async\n * API surface.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - SIFT (REACHConfig) configuration combined with async runner options\n * @returns Promise resolving to the expansion result\n */\nexport async function siftAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: REACHConfig<N, E> & AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\tconst { mi = jaccard, miThreshold = 0.25, ...restConfig } = config ?? {};\n\n\tconst priority = (nodeId: string, context: PriorityContext<N, E>): number =>\n\t\tsiftPriority(nodeId, context, mi, miThreshold);\n\n\treturn baseAsync(graph, seeds, { ...restConfig, priority });\n}\n","/**\n * FLUX (FlexibleAlgorithm Zone Exploration) algorithm.\n *\n * Switches between different expansion strategies based on\n * local graph density and progress.\n *\n * Strategies:\n * - Sparse regions: DOME (expand hubs)\n * - Dense regions: EDGE (expand through low-degree edges)\n * - Bridge nodes: PIPE (expand bridges)\n *\n * @module expansion/flux\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\nimport { baseAsync } from \"./base\";\nimport { countCrossFrontierNeighbours } from \"./priority-helpers\";\n\n/**\n * Configuration for MAZE expansion.\n */\nexport interface MAZEConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> extends ExpansionConfig<N, E> {\n\t/** Density threshold for switching to EDGE mode (default: 0.5) */\n\treadonly densityThreshold?: number;\n\t/** Bridge threshold for switching to PIPE mode (default: 0.3) */\n\treadonly bridgeThreshold?: number;\n}\n\n/**\n * Compute local density around a node.\n */\nfunction localDensity<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tnodeId: string,\n): number {\n\tconst neighbours = Array.from(graph.neighbours(nodeId));\n\tconst degree = neighbours.length;\n\n\tif (degree < 2) {\n\t\treturn 0;\n\t}\n\n\t// Count edges among neighbours\n\tlet edges = 0;\n\tfor (let i = 0; i < neighbours.length; i++) {\n\t\tfor (let j = i + 1; j < neighbours.length; j++) {\n\t\t\tconst ni = neighbours[i];\n\t\t\tconst nj = neighbours[j];\n\t\t\tif (\n\t\t\t\tni !== undefined &&\n\t\t\t\tnj !== undefined &&\n\t\t\t\tgraph.getEdge(ni, nj) !== undefined\n\t\t\t) {\n\t\t\t\tedges++;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst maxEdges = (degree * (degree - 1)) / 2;\n\treturn edges / maxEdges;\n}\n\n/**\n * MAZE adaptive priority function.\n *\n * Switches strategies based on local conditions:\n * - High density + low bridge: EDGE mode\n * - Low density + low bridge: DOME mode\n * - High bridge score: PIPE mode\n */\nfunction fluxPriority<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n\tdensityThreshold: number,\n\tbridgeThreshold: number,\n): number {\n\tconst graph = context.graph;\n\tconst degree = context.degree;\n\n\t// Compute local metrics\n\tconst density = localDensity(graph, nodeId);\n\tconst bridge = countCrossFrontierNeighbours(graph, nodeId, context);\n\n\t// Normalise bridge score by number of frontiers\n\tconst numFrontiers = new Set(context.visitedByFrontier.values()).size;\n\tconst normalisedBridge = numFrontiers > 0 ? bridge / numFrontiers : 0;\n\n\t// Select strategy\n\tif (normalisedBridge >= bridgeThreshold) {\n\t\t// PIPE mode: prioritise bridges\n\t\treturn 1 / (1 + bridge);\n\t} else if (density >= densityThreshold) {\n\t\t// EDGE mode: avoid dense regions, expand through sparse edges\n\t\treturn 1 / (degree + 1); // Low degree → low priority value → expanded first\n\t} else {\n\t\t// DOME mode: expand hubs first\n\t\treturn degree;\n\t}\n}\n\n/**\n * Run FLUX expansion algorithm.\n *\n * Adaptively switches between expansion strategies based on\n * local graph structure. Useful for heterogeneous graphs\n * with varying density.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function flux<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: MAZEConfig<N, E>,\n): ExpansionResult {\n\tconst {\n\t\tdensityThreshold = 0.5,\n\t\tbridgeThreshold = 0.3,\n\t\t...restConfig\n\t} = config ?? {};\n\n\tconst priority = (nodeId: string, context: PriorityContext<N, E>): number =>\n\t\tfluxPriority(nodeId, context, densityThreshold, bridgeThreshold);\n\n\treturn base(graph, seeds, {\n\t\t...restConfig,\n\t\tpriority,\n\t});\n}\n\n/**\n * Run FLUX expansion asynchronously.\n *\n * Note: the FLUX priority function accesses `context.graph` to compute\n * local density and cross-frontier bridge scores. Full async equivalence\n * requires PriorityContext refactoring (Phase 4b deferred). This export\n * establishes the async API surface.\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - FLUX (MAZEConfig) configuration combined with async runner options\n * @returns Promise resolving to the expansion result\n */\nexport async function fluxAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: MAZEConfig<N, E> & AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\tconst {\n\t\tdensityThreshold = 0.5,\n\t\tbridgeThreshold = 0.3,\n\t\t...restConfig\n\t} = config ?? {};\n\n\tconst priority = (nodeId: string, context: PriorityContext<N, E>): number =>\n\t\tfluxPriority(nodeId, context, densityThreshold, bridgeThreshold);\n\n\treturn baseAsync(graph, seeds, { ...restConfig, priority });\n}\n","/**\n * Standard BFS (Breadth-First Search) expansion.\n *\n * Simplest baseline: FIFO order based on discovery iteration.\n * All nodes at the same frontier are explored in discovery order.\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base, baseAsync } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\n\n/**\n * BFS priority: discovery iteration order (FIFO).\n */\nfunction bfsPriority<N extends NodeData, E extends EdgeData>(\n\t_nodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\treturn context.iteration;\n}\n\n/**\n * Run standard BFS expansion (FIFO discovery order).\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function standardBfs<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: bfsPriority,\n\t});\n}\n\n/**\n * Run standard BFS expansion asynchronously (FIFO discovery order).\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function standardBfsAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn baseAsync(graph, seeds, { ...config, priority: bfsPriority });\n}\n","/**\n * Frontier-Balanced expansion.\n *\n * Round-robin exploration across frontiers.\n * Each frontier expands one node before the next frontier gets a turn.\n * Ensures fair expansion across all seed frontiers.\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base, baseAsync } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\n\n/**\n * Frontier-balanced priority: frontier index dominates, then discovery iteration.\n * Scales frontier index by 1e9 to ensure round-robin ordering across frontiers.\n */\nfunction balancedPriority<N extends NodeData, E extends EdgeData>(\n\t_nodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\treturn context.frontierIndex * 1e9 + context.iteration;\n}\n\n/**\n * Run frontier-balanced expansion (round-robin across frontiers).\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function frontierBalanced<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: balancedPriority,\n\t});\n}\n\n/**\n * Run frontier-balanced expansion asynchronously (round-robin across frontiers).\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function frontierBalancedAsync<\n\tN extends NodeData,\n\tE extends EdgeData,\n>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn baseAsync(graph, seeds, { ...config, priority: balancedPriority });\n}\n","/**\n * Random-Priority expansion.\n *\n * Baseline exploration with random node priorities.\n * Uses deterministic seeded randomness for reproducibility.\n * Serves as a null hypothesis for algorithm comparison.\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base, baseAsync } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\n\n/**\n * Deterministic seeded random number generator.\n * Uses FNV-1a-like hash for input → [0, 1] output.\n *\n * @param input - String to hash\n * @param seed - Random seed for reproducibility\n * @returns Deterministic random value in [0, 1]\n */\nfunction seededRandom(input: string, seed = 0): number {\n\tlet h = seed;\n\tfor (let i = 0; i < input.length; i++) {\n\t\th = Math.imul(h ^ input.charCodeAt(i), 0x9e3779b9);\n\n\t\th ^= h >>> 16;\n\t}\n\n\treturn (h >>> 0) / 0xffffffff;\n}\n\n/**\n * Configuration for random-priority expansion.\n */\ninterface RandomPriorityConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> extends ExpansionConfig<N, E> {\n\t/** Random seed for deterministic reproducibility */\n\treadonly seed?: number;\n}\n\n/**\n * Async configuration for random-priority expansion.\n */\ninterface AsyncRandomPriorityConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> extends AsyncExpansionConfig<N, E> {\n\t/** Random seed for deterministic reproducibility */\n\treadonly seed?: number;\n}\n\n/**\n * Build a seeded random priority function for a given seed value.\n */\nfunction makeRandomPriorityFn<N extends NodeData, E extends EdgeData>(\n\tseed: number,\n): (nodeId: string, context: PriorityContext<N, E>) => number {\n\treturn (nodeId: string): number => seededRandom(nodeId, seed);\n}\n\n/**\n * Run random-priority expansion (null hypothesis baseline).\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function randomPriority<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: RandomPriorityConfig<N, E>,\n): ExpansionResult {\n\tconst { seed = 0 } = config ?? {};\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: makeRandomPriorityFn<N, E>(seed),\n\t});\n}\n\n/**\n * Run random-priority expansion asynchronously (null hypothesis baseline).\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function randomPriorityAsync<\n\tN extends NodeData,\n\tE extends EdgeData,\n>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncRandomPriorityConfig<N, E>,\n): Promise<ExpansionResult> {\n\tconst { seed = 0 } = config ?? {};\n\treturn baseAsync(graph, seeds, {\n\t\t...config,\n\t\tpriority: makeRandomPriorityFn<N, E>(seed),\n\t});\n}\n","/**\n * DFS-Priority expansion.\n *\n * Baseline exploration using LIFO (last-in, first-out) discovery order,\n * simulating depth-first search via the BASE framework.\n * Uses negative iteration count as priority so the most recently\n * discovered node is always expanded next.\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base, baseAsync } from \"./base\";\nimport type { AsyncExpansionConfig } from \"./base\";\n\n/**\n * DFS priority function: negative iteration produces LIFO ordering.\n *\n * Lower priority values are expanded first, so negating the iteration\n * counter ensures the most recently enqueued node is always next.\n */\nexport function dfsPriorityFn<N extends NodeData, E extends EdgeData>(\n\t_nodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\treturn -context.iteration;\n}\n\n/**\n * Run DFS-priority expansion (LIFO discovery order).\n *\n * Uses the BASE framework with a negative-iteration priority function,\n * which causes the most recently discovered node to be expanded first —\n * equivalent to depth-first search behaviour.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion configuration\n * @returns Expansion result with discovered paths\n */\nexport function dfsPriority<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: ExpansionConfig<N, E>,\n): ExpansionResult {\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: dfsPriorityFn,\n\t});\n}\n\n/**\n * Run DFS-priority expansion asynchronously (LIFO discovery order).\n *\n * @param graph - Async source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Expansion and async runner configuration\n * @returns Promise resolving to the expansion result\n */\nexport async function dfsPriorityAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: AsyncExpansionConfig<N, E>,\n): Promise<ExpansionResult> {\n\treturn baseAsync(graph, seeds, { ...config, priority: dfsPriorityFn });\n}\n","/**\n * K-Hop expansion.\n *\n * Fixed-depth BFS baseline that explores up to k hops from each seed.\n * Implements explicit depth-limited BFS with frontier collision detection\n * for path discovery between seeds. Each seed expands independently;\n * a path is recorded when a node visited by one seed's frontier is\n * encountered by another seed's frontier.\n *\n * Unlike the BASE framework, depth is tracked explicitly so the k-hop\n * constraint is exact rather than approximate.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionPath,\n\tExpansionStats,\n} from \"./types\";\nimport type { ExpansionConfig } from \"./types\";\n\n/**\n * Configuration for k-hop expansion.\n */\nexport interface KHopConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> extends ExpansionConfig<N, E> {\n\t/**\n\t * Maximum number of hops from any seed node.\n\t * Defaults to 2.\n\t */\n\treadonly k?: number;\n}\n\n/**\n * Run k-hop expansion (fixed-depth BFS).\n *\n * Explores all nodes reachable within exactly k hops of any seed using\n * breadth-first search. Paths between seeds are detected when a node\n * is reached by frontiers from two different seeds.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - K-hop configuration (k defaults to 2)\n * @returns Expansion result with discovered paths\n */\nexport function kHop<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: KHopConfig<N, E>,\n): ExpansionResult {\n\tconst startTime = performance.now();\n\n\tconst { k = 2 } = config ?? {};\n\n\tif (seeds.length === 0) {\n\t\treturn emptyResult(startTime);\n\t}\n\n\t// Per-frontier state: visited nodes and predecessor map for path reconstruction\n\tconst visitedByFrontier: Map<NodeId, NodeId | null>[] = seeds.map(\n\t\t(): Map<NodeId, NodeId | null> => new Map<NodeId, NodeId | null>(),\n\t);\n\t// Global map: node → frontier index that first visited it\n\tconst firstVisitedBy = new Map<NodeId, number>();\n\tconst allVisited = new Set<NodeId>();\n\tconst sampledEdgeMap = new Map<NodeId, Set<NodeId>>();\n\tconst discoveredPaths: ExpansionPath[] = [];\n\n\tlet iterations = 0;\n\tlet edgesTraversed = 0;\n\n\t// Initialise each frontier with its seed node\n\tfor (let i = 0; i < seeds.length; i++) {\n\t\tconst seed = seeds[i];\n\t\tif (seed === undefined) continue;\n\t\tif (!graph.hasNode(seed.id)) continue;\n\n\t\tvisitedByFrontier[i]?.set(seed.id, null);\n\t\tallVisited.add(seed.id);\n\n\t\tif (!firstVisitedBy.has(seed.id)) {\n\t\t\tfirstVisitedBy.set(seed.id, i);\n\t\t} else {\n\t\t\t// Seed collision: two seeds are the same node — record trivial path\n\t\t\tconst otherIdx = firstVisitedBy.get(seed.id) ?? -1;\n\t\t\tif (otherIdx < 0) continue;\n\t\t\tconst fromSeed = seeds[otherIdx];\n\t\t\tconst toSeed = seeds[i];\n\t\t\tif (fromSeed !== undefined && toSeed !== undefined) {\n\t\t\t\tdiscoveredPaths.push({ fromSeed, toSeed, nodes: [seed.id] });\n\t\t\t}\n\t\t}\n\t}\n\n\t// BFS level-by-level for each frontier simultaneously\n\t// Current frontier for each seed: nodes to expand at the next hop depth\n\tlet currentLevel: NodeId[][] = seeds.map((s, i): NodeId[] => {\n\t\tconst frontier = visitedByFrontier[i];\n\t\tif (frontier === undefined) return [];\n\t\treturn frontier.has(s.id) ? [s.id] : [];\n\t});\n\n\tfor (let hop = 0; hop < k; hop++) {\n\t\tconst nextLevel: NodeId[][] = seeds.map(() => []);\n\n\t\tfor (let i = 0; i < seeds.length; i++) {\n\t\t\tconst level = currentLevel[i];\n\t\t\tif (level === undefined) continue;\n\n\t\t\tconst frontierVisited = visitedByFrontier[i];\n\t\t\tif (frontierVisited === undefined) continue;\n\n\t\t\tfor (const nodeId of level) {\n\t\t\t\titerations++;\n\n\t\t\t\tfor (const neighbour of graph.neighbours(nodeId)) {\n\t\t\t\t\tedgesTraversed++;\n\n\t\t\t\t\t// Track sampled edge in canonical order\n\t\t\t\t\tconst [s, t] =\n\t\t\t\t\t\tnodeId < neighbour ? [nodeId, neighbour] : [neighbour, nodeId];\n\t\t\t\t\tlet targets = sampledEdgeMap.get(s);\n\t\t\t\t\tif (targets === undefined) {\n\t\t\t\t\t\ttargets = new Set();\n\t\t\t\t\t\tsampledEdgeMap.set(s, targets);\n\t\t\t\t\t}\n\t\t\t\t\ttargets.add(t);\n\n\t\t\t\t\t// Skip if this frontier has already visited this neighbour\n\t\t\t\t\tif (frontierVisited.has(neighbour)) continue;\n\n\t\t\t\t\tfrontierVisited.set(neighbour, nodeId);\n\t\t\t\t\tallVisited.add(neighbour);\n\t\t\t\t\tnextLevel[i]?.push(neighbour);\n\n\t\t\t\t\t// Path detection: collision with another frontier\n\t\t\t\t\tconst previousFrontier = firstVisitedBy.get(neighbour);\n\t\t\t\t\tif (previousFrontier !== undefined && previousFrontier !== i) {\n\t\t\t\t\t\tconst fromSeed = seeds[previousFrontier];\n\t\t\t\t\t\tconst toSeed = seeds[i];\n\t\t\t\t\t\tif (fromSeed !== undefined && toSeed !== undefined) {\n\t\t\t\t\t\t\tconst path = reconstructPath(\n\t\t\t\t\t\t\t\tneighbour,\n\t\t\t\t\t\t\t\tpreviousFrontier,\n\t\t\t\t\t\t\t\ti,\n\t\t\t\t\t\t\t\tvisitedByFrontier,\n\t\t\t\t\t\t\t\tseeds,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (path !== null) {\n\t\t\t\t\t\t\t\tconst alreadyFound = discoveredPaths.some(\n\t\t\t\t\t\t\t\t\t(p) =>\n\t\t\t\t\t\t\t\t\t\t(p.fromSeed.id === fromSeed.id &&\n\t\t\t\t\t\t\t\t\t\t\tp.toSeed.id === toSeed.id) ||\n\t\t\t\t\t\t\t\t\t\t(p.fromSeed.id === toSeed.id &&\n\t\t\t\t\t\t\t\t\t\t\tp.toSeed.id === fromSeed.id),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tif (!alreadyFound) {\n\t\t\t\t\t\t\t\t\tdiscoveredPaths.push(path);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!firstVisitedBy.has(neighbour)) {\n\t\t\t\t\t\tfirstVisitedBy.set(neighbour, i);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tcurrentLevel = nextLevel;\n\n\t\t// Stop early if all frontiers are exhausted\n\t\tif (currentLevel.every((level) => level.length === 0)) break;\n\t}\n\n\tconst endTime = performance.now();\n\n\t// Convert sampled edge map to set of tuples\n\tconst edgeTuples = new Set<readonly [NodeId, NodeId]>();\n\tfor (const [source, targets] of sampledEdgeMap) {\n\t\tfor (const target of targets) {\n\t\t\tedgeTuples.add([source, target] as const);\n\t\t}\n\t}\n\n\tconst visitedPerFrontier = visitedByFrontier.map((m) => new Set(m.keys()));\n\n\tconst stats: ExpansionStats = {\n\t\titerations,\n\t\tnodesVisited: allVisited.size,\n\t\tedgesTraversed,\n\t\tpathsFound: discoveredPaths.length,\n\t\tdurationMs: endTime - startTime,\n\t\talgorithm: \"k-hop\",\n\t\ttermination: \"exhausted\",\n\t};\n\n\treturn {\n\t\tpaths: discoveredPaths,\n\t\tsampledNodes: allVisited,\n\t\tsampledEdges: edgeTuples,\n\t\tvisitedPerFrontier,\n\t\tstats,\n\t};\n}\n\n/**\n * Reconstruct the path between two colliding frontiers.\n */\nfunction reconstructPath(\n\tcollisionNode: NodeId,\n\tfrontierA: number,\n\tfrontierB: number,\n\tvisitedByFrontier: readonly Map<NodeId, NodeId | null>[],\n\tseeds: readonly Seed[],\n): ExpansionPath | null {\n\tconst seedA = seeds[frontierA];\n\tconst seedB = seeds[frontierB];\n\tif (seedA === undefined || seedB === undefined) return null;\n\n\t// Trace back from collision node through frontier A to its seed\n\tconst pathA: NodeId[] = [collisionNode];\n\tconst predA = visitedByFrontier[frontierA];\n\tif (predA !== undefined) {\n\t\tlet node: NodeId | null | undefined = collisionNode;\n\t\tlet pred: NodeId | null | undefined = predA.get(node);\n\t\twhile (pred !== null && pred !== undefined) {\n\t\t\tpathA.unshift(pred);\n\t\t\tnode = pred;\n\t\t\tpred = predA.get(node);\n\t\t}\n\t}\n\n\t// Trace back from collision node through frontier B to its seed\n\tconst pathB: NodeId[] = [];\n\tconst predB = visitedByFrontier[frontierB];\n\tif (predB !== undefined) {\n\t\tlet node: NodeId | null | undefined = collisionNode;\n\t\tlet pred: NodeId | null | undefined = predB.get(node);\n\t\twhile (pred !== null && pred !== undefined) {\n\t\t\tpathB.push(pred);\n\t\t\tnode = pred;\n\t\t\tpred = predB.get(node);\n\t\t}\n\t}\n\n\treturn {\n\t\tfromSeed: seedA,\n\t\ttoSeed: seedB,\n\t\tnodes: [...pathA, ...pathB],\n\t};\n}\n\n/**\n * Create an empty result for early termination (no seeds).\n */\nfunction emptyResult(startTime: number): ExpansionResult {\n\treturn {\n\t\tpaths: [],\n\t\tsampledNodes: new Set(),\n\t\tsampledEdges: new Set(),\n\t\tvisitedPerFrontier: [],\n\t\tstats: {\n\t\t\titerations: 0,\n\t\t\tnodesVisited: 0,\n\t\t\tedgesTraversed: 0,\n\t\t\tpathsFound: 0,\n\t\t\tdurationMs: performance.now() - startTime,\n\t\t\talgorithm: \"k-hop\",\n\t\t\ttermination: \"exhausted\",\n\t\t},\n\t};\n}\n","/**\n * Random Walk with Restart expansion.\n *\n * Baseline exploration strategy using multiple random walks from each seed.\n * Walks proceed by uniformly sampling a neighbour at each step. With\n * probability `restartProbability` the walk restarts from the originating\n * seed node, simulating Personalised PageRank dynamics.\n *\n * Path detection: when a walk visits a node that was previously reached\n * by a walk from a different seed, an inter-seed path is recorded.\n *\n * This algorithm does NOT use the BASE framework — it constructs an\n * ExpansionResult directly.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionPath,\n\tExpansionStats,\n} from \"./types\";\n\n/**\n * Configuration for random-walk-with-restart expansion.\n */\nexport interface RandomWalkConfig {\n\t/** Probability of restarting a walk from its seed node (default: 0.15). */\n\treadonly restartProbability?: number;\n\t/** Number of walks to perform per seed node (default: 10). */\n\treadonly walks?: number;\n\t/** Maximum steps per walk (default: 20). */\n\treadonly walkLength?: number;\n\t/** Random seed for deterministic reproducibility (default: 0). */\n\treadonly seed?: number;\n}\n\n/**\n * Mulberry32 seeded PRNG — fast, compact, and high-quality for simulation.\n *\n * Returns a closure that yields the next pseudo-random value in [0, 1)\n * on each call.\n *\n * @param seed - 32-bit integer seed\n */\nfunction mulberry32(seed: number): () => number {\n\tlet s = seed;\n\treturn (): number => {\n\t\ts += 0x6d2b79f5;\n\t\tlet t = s;\n\t\tt = Math.imul(t ^ (t >>> 15), t | 1);\n\t\tt ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n\t\treturn ((t ^ (t >>> 14)) >>> 0) / 0x100000000;\n\t};\n}\n\n/**\n * Run random-walk-with-restart expansion.\n *\n * For each seed, performs `walks` independent random walks of up to\n * `walkLength` steps. At each step the walk either restarts (with\n * probability `restartProbability`) or moves to a uniformly sampled\n * neighbour. All visited nodes and traversed edges are collected.\n *\n * Inter-seed paths are detected when a walk reaches a node that was\n * previously reached by a walk originating from a different seed.\n * The recorded path contains only the two seed endpoints rather than\n * the full walk trajectory, consistent with the ExpansionPath contract.\n *\n * @param graph - Source graph\n * @param seeds - Seed nodes for expansion\n * @param config - Random walk configuration\n * @returns Expansion result with discovered paths\n */\nexport function randomWalk<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tseeds: readonly Seed[],\n\tconfig?: RandomWalkConfig,\n): ExpansionResult {\n\tconst startTime = performance.now();\n\n\tconst {\n\t\trestartProbability = 0.15,\n\t\twalks = 10,\n\t\twalkLength = 20,\n\t\tseed = 0,\n\t} = config ?? {};\n\n\tif (seeds.length === 0) {\n\t\treturn emptyResult(startTime);\n\t}\n\n\tconst rand = mulberry32(seed);\n\n\t// Map each visited node to the index of the seed whose walk first reached it\n\tconst firstVisitedBySeed = new Map<NodeId, number>();\n\tconst allVisited = new Set<NodeId>();\n\tconst sampledEdgeMap = new Map<NodeId, Set<NodeId>>();\n\n\t// Paths discovered when walks from different seeds collide\n\tconst discoveredPaths: ExpansionPath[] = [];\n\n\tlet iterations = 0;\n\tlet edgesTraversed = 0;\n\n\t// Track which nodes were visited by each frontier for visitedPerFrontier\n\tconst visitedPerFrontier: Set<NodeId>[] = seeds.map(() => new Set<NodeId>());\n\n\tfor (let seedIdx = 0; seedIdx < seeds.length; seedIdx++) {\n\t\tconst seed_ = seeds[seedIdx];\n\t\tif (seed_ === undefined) continue;\n\n\t\tconst seedId = seed_.id;\n\t\tif (!graph.hasNode(seedId)) continue;\n\n\t\t// Mark the seed itself as visited\n\t\tif (!firstVisitedBySeed.has(seedId)) {\n\t\t\tfirstVisitedBySeed.set(seedId, seedIdx);\n\t\t}\n\t\tallVisited.add(seedId);\n\t\tvisitedPerFrontier[seedIdx]?.add(seedId);\n\n\t\tfor (let w = 0; w < walks; w++) {\n\t\t\tlet current = seedId;\n\n\t\t\tfor (let step = 0; step < walkLength; step++) {\n\t\t\t\titerations++;\n\n\t\t\t\t// Restart with configured probability\n\t\t\t\tif (rand() < restartProbability) {\n\t\t\t\t\tcurrent = seedId;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Collect neighbours into an array for random sampling\n\t\t\t\tconst neighbourList: NodeId[] = [];\n\t\t\t\tfor (const nb of graph.neighbours(current)) {\n\t\t\t\t\tneighbourList.push(nb);\n\t\t\t\t}\n\n\t\t\t\tif (neighbourList.length === 0) {\n\t\t\t\t\t// Dead end — restart\n\t\t\t\t\tcurrent = seedId;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Uniform random neighbour selection\n\t\t\t\tconst nextIdx = Math.floor(rand() * neighbourList.length);\n\t\t\t\tconst next = neighbourList[nextIdx];\n\t\t\t\tif (next === undefined) {\n\t\t\t\t\tcurrent = seedId;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tedgesTraversed++;\n\n\t\t\t\t// Record traversed edge (canonical order)\n\t\t\t\tconst [s, t] = current < next ? [current, next] : [next, current];\n\t\t\t\tlet targets = sampledEdgeMap.get(s);\n\t\t\t\tif (targets === undefined) {\n\t\t\t\t\ttargets = new Set();\n\t\t\t\t\tsampledEdgeMap.set(s, targets);\n\t\t\t\t}\n\t\t\t\ttargets.add(t);\n\n\t\t\t\t// Path detection: collision with a walk from a different seed\n\t\t\t\tconst previousSeedIdx = firstVisitedBySeed.get(next);\n\t\t\t\tif (previousSeedIdx !== undefined && previousSeedIdx !== seedIdx) {\n\t\t\t\t\tconst fromSeed = seeds[previousSeedIdx];\n\t\t\t\t\tconst toSeed = seeds[seedIdx];\n\t\t\t\t\tif (fromSeed !== undefined && toSeed !== undefined) {\n\t\t\t\t\t\t// Record a path between the two seed endpoints\n\t\t\t\t\t\tconst path: ExpansionPath = {\n\t\t\t\t\t\t\tfromSeed,\n\t\t\t\t\t\t\ttoSeed,\n\t\t\t\t\t\t\tnodes: [fromSeed.id, next, toSeed.id].filter(\n\t\t\t\t\t\t\t\t// Deduplicate when next happens to be a seed itself\n\t\t\t\t\t\t\t\t(n, i, arr) => arr.indexOf(n) === i,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t};\n\t\t\t\t\t\t// Avoid duplicate seed-pair paths\n\t\t\t\t\t\tconst alreadyFound = discoveredPaths.some(\n\t\t\t\t\t\t\t(p) =>\n\t\t\t\t\t\t\t\t(p.fromSeed.id === fromSeed.id && p.toSeed.id === toSeed.id) ||\n\t\t\t\t\t\t\t\t(p.fromSeed.id === toSeed.id && p.toSeed.id === fromSeed.id),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (!alreadyFound) {\n\t\t\t\t\t\t\tdiscoveredPaths.push(path);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!firstVisitedBySeed.has(next)) {\n\t\t\t\t\tfirstVisitedBySeed.set(next, seedIdx);\n\t\t\t\t}\n\t\t\t\tallVisited.add(next);\n\t\t\t\tvisitedPerFrontier[seedIdx]?.add(next);\n\n\t\t\t\tcurrent = next;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst endTime = performance.now();\n\n\t// Convert sampled edge map to set of tuples\n\tconst edgeTuples = new Set<readonly [NodeId, NodeId]>();\n\tfor (const [source, targets] of sampledEdgeMap) {\n\t\tfor (const target of targets) {\n\t\t\tedgeTuples.add([source, target] as const);\n\t\t}\n\t}\n\n\tconst stats: ExpansionStats = {\n\t\titerations,\n\t\tnodesVisited: allVisited.size,\n\t\tedgesTraversed,\n\t\tpathsFound: discoveredPaths.length,\n\t\tdurationMs: endTime - startTime,\n\t\talgorithm: \"random-walk\",\n\t\ttermination: \"exhausted\",\n\t};\n\n\treturn {\n\t\tpaths: discoveredPaths,\n\t\tsampledNodes: allVisited,\n\t\tsampledEdges: edgeTuples,\n\t\tvisitedPerFrontier,\n\t\tstats,\n\t};\n}\n\n/**\n * Create an empty result for early termination (no seeds).\n */\nfunction emptyResult(startTime: number): ExpansionResult {\n\treturn {\n\t\tpaths: [],\n\t\tsampledNodes: new Set(),\n\t\tsampledEdges: new Set(),\n\t\tvisitedPerFrontier: [],\n\t\tstats: {\n\t\t\titerations: 0,\n\t\t\tnodesVisited: 0,\n\t\t\tedgesTraversed: 0,\n\t\t\tpathsFound: 0,\n\t\t\tdurationMs: performance.now() - startTime,\n\t\t\talgorithm: \"random-walk\",\n\t\t\ttermination: \"exhausted\",\n\t\t},\n\t};\n}\n"],"mappings":"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"}