graphwise 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -30
- package/dist/async/index.cjs +243 -0
- package/dist/async/index.cjs.map +1 -0
- package/dist/async/index.js +230 -0
- package/dist/async/index.js.map +1 -0
- package/dist/expansion/dfs-priority.d.ts +11 -0
- package/dist/expansion/dfs-priority.d.ts.map +1 -1
- package/dist/expansion/dome.d.ts +20 -0
- package/dist/expansion/dome.d.ts.map +1 -1
- package/dist/expansion/edge.d.ts +18 -0
- package/dist/expansion/edge.d.ts.map +1 -1
- package/dist/expansion/flux.d.ts +16 -0
- package/dist/expansion/flux.d.ts.map +1 -1
- package/dist/expansion/frontier-balanced.d.ts +11 -0
- package/dist/expansion/frontier-balanced.d.ts.map +1 -1
- package/dist/expansion/fuse.d.ts +16 -0
- package/dist/expansion/fuse.d.ts.map +1 -1
- package/dist/expansion/hae.d.ts +16 -0
- package/dist/expansion/hae.d.ts.map +1 -1
- package/dist/expansion/lace.d.ts +16 -0
- package/dist/expansion/lace.d.ts.map +1 -1
- package/dist/expansion/maze.d.ts +17 -0
- package/dist/expansion/maze.d.ts.map +1 -1
- package/dist/expansion/pipe.d.ts +16 -0
- package/dist/expansion/pipe.d.ts.map +1 -1
- package/dist/expansion/random-priority.d.ts +18 -0
- package/dist/expansion/random-priority.d.ts.map +1 -1
- package/dist/expansion/reach.d.ts +17 -0
- package/dist/expansion/reach.d.ts.map +1 -1
- package/dist/expansion/sage.d.ts +15 -0
- package/dist/expansion/sage.d.ts.map +1 -1
- package/dist/expansion/sift.d.ts +16 -0
- package/dist/expansion/sift.d.ts.map +1 -1
- package/dist/expansion/standard-bfs.d.ts +11 -0
- package/dist/expansion/standard-bfs.d.ts.map +1 -1
- package/dist/expansion/tide.d.ts +16 -0
- package/dist/expansion/tide.d.ts.map +1 -1
- package/dist/expansion/warp.d.ts +16 -0
- package/dist/expansion/warp.d.ts.map +1 -1
- package/dist/index/index.cjs +842 -215
- package/dist/index/index.cjs.map +1 -1
- package/dist/index/index.js +793 -211
- package/dist/index/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/ranking/mi/adamic-adar.d.ts +8 -0
- package/dist/ranking/mi/adamic-adar.d.ts.map +1 -1
- package/dist/ranking/mi/adaptive.d.ts +8 -0
- package/dist/ranking/mi/adaptive.d.ts.map +1 -1
- package/dist/ranking/mi/cosine.d.ts +7 -0
- package/dist/ranking/mi/cosine.d.ts.map +1 -1
- package/dist/ranking/mi/etch.d.ts +8 -0
- package/dist/ranking/mi/etch.d.ts.map +1 -1
- package/dist/ranking/mi/hub-promoted.d.ts +7 -0
- package/dist/ranking/mi/hub-promoted.d.ts.map +1 -1
- package/dist/ranking/mi/jaccard.d.ts +7 -0
- package/dist/ranking/mi/jaccard.d.ts.map +1 -1
- package/dist/ranking/mi/notch.d.ts +8 -0
- package/dist/ranking/mi/notch.d.ts.map +1 -1
- package/dist/ranking/mi/overlap-coefficient.d.ts +7 -0
- package/dist/ranking/mi/overlap-coefficient.d.ts.map +1 -1
- package/dist/ranking/mi/resource-allocation.d.ts +8 -0
- package/dist/ranking/mi/resource-allocation.d.ts.map +1 -1
- package/dist/ranking/mi/scale.d.ts +7 -0
- package/dist/ranking/mi/scale.d.ts.map +1 -1
- package/dist/ranking/mi/skew.d.ts +7 -0
- package/dist/ranking/mi/skew.d.ts.map +1 -1
- package/dist/ranking/mi/sorensen.d.ts +7 -0
- package/dist/ranking/mi/sorensen.d.ts.map +1 -1
- package/dist/ranking/mi/span.d.ts +8 -0
- package/dist/ranking/mi/span.d.ts.map +1 -1
- package/dist/ranking/mi/types.d.ts +12 -0
- package/dist/ranking/mi/types.d.ts.map +1 -1
- package/dist/ranking/parse.d.ts +24 -1
- package/dist/ranking/parse.d.ts.map +1 -1
- package/package.json +6 -1
package/dist/index/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/async/utils.ts","../../src/async/runners.ts","../../src/async/ops.ts","../../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/ranking/mi/jaccard.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","../../src/ranking/parse.ts","../../src/ranking/mi/adamic-adar.ts","../../src/ranking/mi/cosine.ts","../../src/ranking/mi/sorensen.ts","../../src/ranking/mi/resource-allocation.ts","../../src/ranking/mi/overlap-coefficient.ts","../../src/ranking/mi/hub-promoted.ts","../../src/ranking/mi/scale.ts","../../src/ranking/mi/skew.ts","../../src/ranking/mi/span.ts","../../src/ranking/mi/etch.ts","../../src/ranking/mi/notch.ts","../../src/ranking/mi/adaptive.ts","../../src/ranking/baselines/utils.ts","../../src/ranking/baselines/shortest.ts","../../src/ranking/baselines/degree-sum.ts","../../src/ranking/baselines/widest-path.ts","../../src/ranking/baselines/jaccard-arithmetic.ts","../../src/ranking/baselines/pagerank.ts","../../src/ranking/baselines/betweenness.ts","../../src/ranking/baselines/katz.ts","../../src/ranking/baselines/communicability.ts","../../src/ranking/baselines/resistance-distance.ts","../../src/ranking/baselines/random-ranking.ts","../../src/ranking/baselines/hitting-time.ts","../../src/extraction/ego-network.ts","../../src/extraction/k-core.ts","../../src/extraction/truss.ts","../../src/extraction/motif.ts","../../src/extraction/induced-subgraph.ts","../../src/extraction/node-filter.ts"],"sourcesContent":["/**\n * Async utility functions.\n *\n * @module async/utils\n */\n\n/** Collect an AsyncIterable into a readonly array. */\nexport async function collectAsyncIterable<T>(\n\titer: AsyncIterable<T>,\n): Promise<readonly T[]> {\n\tconst result: T[] = [];\n\tfor await (const item of iter) result.push(item);\n\treturn result;\n}\n\n/** Default yield strategy: setTimeout(0) to yield to the event loop. */\nexport function defaultYieldStrategy(): Promise<void> {\n\treturn new Promise((r) => {\n\t\tsetTimeout(r, 0);\n\t});\n}\n","/**\n * Sync and async runners for generator-based graph algorithms.\n *\n * The runner drives a generator that yields GraphOp objects, resolves each op\n * against the graph, and feeds the result back via gen.next(response). This\n * allows algorithm logic to be written once as a generator and executed\n * synchronously or asynchronously depending on the graph backing.\n *\n * @module async/runners\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type { GraphOp, GraphOpResponse } from \"./protocol\";\nimport type { AsyncRunnerOptions } from \"./types\";\nimport { collectAsyncIterable, defaultYieldStrategy } from \"./utils\";\n\n// ---------------------------------------------------------------------------\n// Sync runner\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a single GraphOp against a synchronous ReadableGraph.\n *\n * Returns a tagged GraphOpResponse so the receiving generator can narrow\n * the result type without type assertions.\n *\n * @param graph - The synchronous graph to query\n * @param op - The operation to resolve\n * @returns The tagged response\n */\nexport function resolveSyncOp<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\top: GraphOp,\n): GraphOpResponse<N, E> {\n\tswitch (op.tag) {\n\t\tcase \"neighbours\":\n\t\t\treturn {\n\t\t\t\ttag: \"neighbours\",\n\t\t\t\tvalue: Array.from(graph.neighbours(op.id, op.direction)),\n\t\t\t};\n\t\tcase \"degree\":\n\t\t\treturn { tag: \"degree\", value: graph.degree(op.id, op.direction) };\n\t\tcase \"getNode\":\n\t\t\treturn { tag: \"getNode\", value: graph.getNode(op.id) };\n\t\tcase \"getEdge\":\n\t\t\treturn { tag: \"getEdge\", value: graph.getEdge(op.source, op.target) };\n\t\tcase \"hasNode\":\n\t\t\treturn { tag: \"hasNode\", value: graph.hasNode(op.id) };\n\t\tcase \"yield\":\n\t\t\treturn { tag: \"yield\" };\n\t\tcase \"progress\":\n\t\t\treturn { tag: \"progress\" };\n\t}\n}\n\n/**\n * Drive a generator to completion using a synchronous graph.\n *\n * The generator yields GraphOp requests; each is resolved immediately\n * against the graph and the tagged response is fed back via gen.next().\n *\n * @param gen - The generator to drive\n * @param graph - The graph to resolve ops against\n * @returns The generator's return value\n */\nexport function runSync<N extends NodeData, E extends EdgeData, R>(\n\tgen: Generator<GraphOp, R, GraphOpResponse<N, E>>,\n\tgraph: ReadableGraph<N, E>,\n): R {\n\tlet step = gen.next();\n\twhile (step.done !== true) {\n\t\tconst response = resolveSyncOp(graph, step.value);\n\t\tstep = gen.next(response);\n\t}\n\treturn step.value;\n}\n\n// ---------------------------------------------------------------------------\n// Async runner\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a single GraphOp against an async ReadableGraph.\n *\n * AsyncIterables (neighbours) are collected into readonly arrays so the\n * generator receives the same value type as in sync mode. Returns a tagged\n * GraphOpResponse for type-safe narrowing without assertions.\n *\n * @param graph - The async graph to query\n * @param op - The operation to resolve\n * @returns A promise resolving to the tagged response\n */\nexport async function resolveAsyncOp<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\top: GraphOp,\n): Promise<GraphOpResponse<N, E>> {\n\tswitch (op.tag) {\n\t\tcase \"neighbours\":\n\t\t\treturn {\n\t\t\t\ttag: \"neighbours\",\n\t\t\t\tvalue: await collectAsyncIterable(\n\t\t\t\t\tgraph.neighbours(op.id, op.direction),\n\t\t\t\t),\n\t\t\t};\n\t\tcase \"degree\":\n\t\t\treturn { tag: \"degree\", value: await graph.degree(op.id, op.direction) };\n\t\tcase \"getNode\":\n\t\t\treturn { tag: \"getNode\", value: await graph.getNode(op.id) };\n\t\tcase \"getEdge\":\n\t\t\treturn {\n\t\t\t\ttag: \"getEdge\",\n\t\t\t\tvalue: await graph.getEdge(op.source, op.target),\n\t\t\t};\n\t\tcase \"hasNode\":\n\t\t\treturn { tag: \"hasNode\", value: await graph.hasNode(op.id) };\n\t\tcase \"yield\":\n\t\t\treturn { tag: \"yield\" };\n\t\tcase \"progress\":\n\t\t\treturn { tag: \"progress\" };\n\t}\n}\n\n/**\n * Drive a generator to completion using an async graph.\n *\n * Extends sync semantics with:\n * - Cancellation via AbortSignal (throws DOMException \"AbortError\")\n * - Cooperative yielding at `yield` ops (calls yieldStrategy)\n * - Progress callbacks at `progress` ops (may be async for backpressure)\n * - Error propagation: graph errors are forwarded via gen.throw(); if the\n * generator does not handle them, they propagate to the caller\n *\n * @param gen - The generator to drive\n * @param graph - The async graph to resolve ops against\n * @param options - Runner configuration\n * @returns A promise resolving to the generator's return value\n */\nexport async function runAsync<N extends NodeData, E extends EdgeData, R>(\n\tgen: Generator<GraphOp, R, GraphOpResponse<N, E>>,\n\tgraph: AsyncReadableGraph<N, E>,\n\toptions?: AsyncRunnerOptions,\n): Promise<R> {\n\tconst signal = options?.signal;\n\tconst onProgress = options?.onProgress;\n\tconst yieldStrategy = options?.yieldStrategy ?? defaultYieldStrategy;\n\n\tlet step = gen.next();\n\n\twhile (step.done !== true) {\n\t\t// Check for cancellation before processing each op. Throw the error\n\t\t// into the generator so that any finally blocks in the algorithm run\n\t\t// before the error propagates to the caller.\n\t\tif (signal?.aborted === true) {\n\t\t\tconst abortError = new DOMException(\"Aborted\", \"AbortError\");\n\t\t\ttry {\n\t\t\t\tgen.throw(abortError);\n\t\t\t} catch {\n\t\t\t\t// Generator did not handle the error — propagate it\n\t\t\t\tthrow abortError;\n\t\t\t}\n\t\t\t// Generator handled the error but we still honour cancellation\n\t\t\tthrow abortError;\n\t\t}\n\n\t\tconst op = step.value;\n\n\t\t// Handle cooperative yield ops without hitting the graph\n\t\tif (op.tag === \"yield\") {\n\t\t\tawait yieldStrategy();\n\t\t\tstep = gen.next({ tag: \"yield\" });\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Handle progress ops: call the callback (awaiting if async)\n\t\tif (op.tag === \"progress\") {\n\t\t\tif (onProgress !== undefined) {\n\t\t\t\tconst maybePromise = onProgress(op.stats);\n\t\t\t\tif (maybePromise instanceof Promise) {\n\t\t\t\t\tawait maybePromise;\n\t\t\t\t}\n\t\t\t}\n\t\t\tstep = gen.next({ tag: \"progress\" });\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Resolve graph ops, forwarding any errors into the generator\n\t\tlet response: GraphOpResponse<N, E>;\n\t\ttry {\n\t\t\tresponse = await resolveAsyncOp(graph, op);\n\t\t} catch (error) {\n\t\t\t// Forward the error into the generator; if unhandled, it propagates\n\t\t\tstep = gen.throw(error);\n\t\t\tcontinue;\n\t\t}\n\n\t\tstep = gen.next(response);\n\t}\n\n\treturn step.value;\n}\n","/**\n * Type-safe yield helpers for graph operations.\n *\n * Each function is a sub-generator that yields one GraphOp and returns\n * the correctly-typed result. Narrowing is done via the tagged discriminated\n * union in GraphOpResponse — no type assertions needed.\n *\n * Use with `yield*` inside algorithm generators.\n *\n * @module async/ops\n */\n\nimport type { NodeId, NodeData, EdgeData, Direction } from \"../graph\";\nimport type { GraphOp, GraphOpResponse, ProgressStats } from \"./protocol\";\n\nexport function* opNeighbours<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(\n\tid: NodeId,\n\tdirection?: Direction,\n): Generator<GraphOp, readonly NodeId[], GraphOpResponse<N, E>> {\n\tconst op: GraphOp =\n\t\tdirection !== undefined\n\t\t\t? { tag: \"neighbours\", id, direction }\n\t\t\t: { tag: \"neighbours\", id };\n\tconst response: GraphOpResponse<N, E> = yield op;\n\tif (response.tag !== \"neighbours\") {\n\t\tthrow new TypeError(`Expected neighbours response, got ${response.tag}`);\n\t}\n\treturn response.value;\n}\n\nexport function* opDegree<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(\n\tid: NodeId,\n\tdirection?: Direction,\n): Generator<GraphOp, number, GraphOpResponse<N, E>> {\n\tconst op: GraphOp =\n\t\tdirection !== undefined\n\t\t\t? { tag: \"degree\", id, direction }\n\t\t\t: { tag: \"degree\", id };\n\tconst response: GraphOpResponse<N, E> = yield op;\n\tif (response.tag !== \"degree\") {\n\t\tthrow new TypeError(`Expected degree response, got ${response.tag}`);\n\t}\n\treturn response.value;\n}\n\nexport function* opGetNode<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(id: NodeId): Generator<GraphOp, N | undefined, GraphOpResponse<N, E>> {\n\tconst response: GraphOpResponse<N, E> = yield { tag: \"getNode\", id };\n\tif (response.tag !== \"getNode\") {\n\t\tthrow new TypeError(`Expected getNode response, got ${response.tag}`);\n\t}\n\treturn response.value;\n}\n\nexport function* opGetEdge<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(\n\tsource: NodeId,\n\ttarget: NodeId,\n): Generator<GraphOp, E | undefined, GraphOpResponse<N, E>> {\n\tconst response: GraphOpResponse<N, E> = yield {\n\t\ttag: \"getEdge\",\n\t\tsource,\n\t\ttarget,\n\t};\n\tif (response.tag !== \"getEdge\") {\n\t\tthrow new TypeError(`Expected getEdge response, got ${response.tag}`);\n\t}\n\treturn response.value;\n}\n\nexport function* opHasNode<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(id: NodeId): Generator<GraphOp, boolean, GraphOpResponse<N, E>> {\n\tconst response: GraphOpResponse<N, E> = yield { tag: \"hasNode\", id };\n\tif (response.tag !== \"hasNode\") {\n\t\tthrow new TypeError(`Expected hasNode response, got ${response.tag}`);\n\t}\n\treturn response.value;\n}\n\nexport function* opYield<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(): Generator<GraphOp, void, GraphOpResponse<N, E>> {\n\tyield { tag: \"yield\" };\n}\n\nexport function* opProgress<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(stats: ProgressStats): Generator<GraphOp, void, GraphOpResponse<N, E>> {\n\tyield { tag: \"progress\", stats };\n}\n","/**\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 {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\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\t// DOME uses degree as priority (lower degree = higher priority)\n\tconst domePriority = (\n\t\tnodeId: string,\n\t\tcontext: PriorityContext<N, E>,\n\t): number => {\n\t\treturn context.degree;\n\t};\n\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: domePriority,\n\t});\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\t// Negate degree to prioritise high-degree nodes\n\tconst domePriority = (\n\t\tnodeId: string,\n\t\tcontext: PriorityContext<N, E>,\n\t): number => {\n\t\treturn -context.degree;\n\t};\n\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: domePriority,\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 {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } 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 * 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 { Seed, ExpansionResult, ExpansionConfig } from \"./types\";\nimport { hae } 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 * 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 {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } 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 * 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 {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } 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 * Jaccard similarity coefficient for edge salience.\n *\n * Measures overlap between neighbourhoods of connected nodes:\n * MI(u,v) = |N(u) ∩ N(v)| / |N(u) ∪ N(v)|\n *\n * Range: [0, 1]\n * - 0: No shared neighbours (low salience)\n * - 1: Identical neighbourhoods (high salience)\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport { computeJaccard } from \"../../utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute Jaccard similarity between neighbourhoods of two nodes.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration\n * @returns Jaccard coefficient in [0, 1]\n */\nexport function jaccard<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\tconst {\n\t\tjaccard: jaccardScore,\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t} = computeJaccard(graph, source, target);\n\n\t// Return 0 only when the union is empty (no neighbours on either side)\n\tif (sourceNeighbours.size === 0 && targetNeighbours.size === 0) {\n\t\treturn 0;\n\t}\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, jaccardScore);\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 {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } 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 * 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 {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } 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 * 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 {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } 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 * 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 {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } 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 * 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 {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } 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 * 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 {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } 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 * 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 {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } 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 * 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 {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } 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 * 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 {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\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\t// BFS uses iteration order (discovery order) as priority\n\tconst bfsPriority = (\n\t\t_nodeId: string,\n\t\tcontext: PriorityContext<N, E>,\n\t): number => {\n\t\t// Suppress unused variable warning\n\t\tvoid graph;\n\t\treturn context.iteration;\n\t};\n\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: bfsPriority,\n\t});\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 {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } from \"./base\";\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\t// Frontier-balanced priority: frontier index first, then iteration\n\t// Lower frontier index is prioritised, but within each frontier round they share turns\n\tconst balancedPriority = (\n\t\t_nodeId: string,\n\t\tcontext: PriorityContext<N, E>,\n\t): number => {\n\t\t// Suppress unused variable warning\n\t\tvoid graph;\n\t\t// Scale frontier index to dominate: each frontier gets 1e9 slots\n\t\t// then iteration order within that frontier range\n\t\treturn context.frontierIndex * 1e9 + context.iteration;\n\t};\n\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: balancedPriority,\n\t});\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 {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } 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 * 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\n\t// Random priority: seeded hash of node ID\n\tconst randomPriorityFn = (\n\t\tnodeId: string,\n\t\tcontext: PriorityContext<N, E>,\n\t): number => {\n\t\t// Suppress unused variable warning\n\t\tvoid context;\n\t\tvoid graph;\n\t\treturn seededRandom(nodeId, seed);\n\t};\n\n\treturn base(graph, seeds, {\n\t\t...config,\n\t\tpriority: randomPriorityFn,\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 {\n\tSeed,\n\tExpansionResult,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\nimport { base } 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 * 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","/**\n * PARSE (Path-Aware Ranking via Salience Estimation).\n *\n * Ranks discovered paths by computing geometric mean of edge MI scores.\n * Path salience = (∏ MI(uᵢ, uᵢ₊₁))^(1/|path|)\n *\n * This ranking is length-unbiased: shorter paths with strong edges\n * can outrank longer paths with weak edges.\n *\n * @module ranking/parse\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { ExpansionPath } from \"../expansion/types\";\nimport type { MIFunction } from \"./mi/types\";\nimport { jaccard } from \"./mi/jaccard\";\n\n/**\n * Configuration for PARSE ranking.\n */\nexport interface PARSEConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> {\n\t/** MI function to use (default: jaccard) */\n\treadonly mi?: MIFunction<N, E>;\n\t/** Minimum epsilon for MI (default: 1e-10) */\n\treadonly epsilon?: number;\n\t/** Whether to include salience scores in result (default: true) */\n\treadonly includeSalience?: boolean;\n}\n\n/**\n * A ranked path with salience score.\n */\nexport interface RankedPath extends ExpansionPath {\n\t/** Salience score (geometric mean of edge MI) */\n\treadonly salience: number;\n}\n\n/**\n * Result of PARSE ranking.\n */\nexport interface PARSEResult {\n\t/** Paths ranked by salience (highest first) */\n\treadonly paths: readonly RankedPath[];\n\t/** Ranking statistics */\n\treadonly stats: {\n\t\t/** Total paths ranked */\n\t\treadonly pathsRanked: number;\n\t\t/** Mean salience */\n\t\treadonly meanSalience: number;\n\t\t/** Median salience */\n\t\treadonly medianSalience: number;\n\t\t/** Maximum salience */\n\t\treadonly maxSalience: number;\n\t\t/** Minimum salience */\n\t\treadonly minSalience: number;\n\t\t/** Ranking duration in milliseconds */\n\t\treadonly durationMs: number;\n\t};\n}\n\n/**\n * Rank paths using PARSE (Path-Aware Ranking via Salience Estimation).\n *\n * Computes geometric mean of edge MI scores for each path,\n * then sorts by salience (highest first).\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths with statistics\n */\nexport function parse<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: PARSEConfig<N, E>,\n): PARSEResult {\n\tconst startTime = performance.now();\n\n\tconst { mi = jaccard, epsilon = 1e-10 } = config ?? {};\n\n\tconst rankedPaths: RankedPath[] = [];\n\n\tfor (const path of paths) {\n\t\tconst salience = computePathSalience(graph, path, mi, epsilon);\n\t\trankedPaths.push({\n\t\t\t...path,\n\t\t\tsalience,\n\t\t});\n\t}\n\n\t// Sort by salience descending\n\trankedPaths.sort((a, b) => b.salience - a.salience);\n\n\tconst endTime = performance.now();\n\n\t// Compute statistics\n\tconst saliences = rankedPaths.map((p) => p.salience);\n\tconst meanSalience =\n\t\tsaliences.length > 0\n\t\t\t? saliences.reduce((a, b) => a + b, 0) / saliences.length\n\t\t\t: 0;\n\tconst sortedSaliences = [...saliences].sort((a, b) => a - b);\n\tconst mid = Math.floor(sortedSaliences.length / 2);\n\tconst medianSalience =\n\t\tsortedSaliences.length > 0\n\t\t\t? sortedSaliences.length % 2 !== 0\n\t\t\t\t? (sortedSaliences[mid] ?? 0)\n\t\t\t\t: ((sortedSaliences[mid - 1] ?? 0) + (sortedSaliences[mid] ?? 0)) / 2\n\t\t\t: 0;\n\tconst maxSalience =\n\t\tsortedSaliences.length > 0\n\t\t\t? (sortedSaliences[sortedSaliences.length - 1] ?? 0)\n\t\t\t: 0;\n\tconst minSalience =\n\t\tsortedSaliences.length > 0 ? (sortedSaliences[0] ?? 0) : 0;\n\n\treturn {\n\t\tpaths: rankedPaths,\n\t\tstats: {\n\t\t\tpathsRanked: rankedPaths.length,\n\t\t\tmeanSalience,\n\t\t\tmedianSalience,\n\t\t\tmaxSalience,\n\t\t\tminSalience,\n\t\t\tdurationMs: endTime - startTime,\n\t\t},\n\t};\n}\n\n/**\n * Compute salience for a single path.\n *\n * Uses geometric mean of edge MI scores for length-unbiased ranking.\n */\nfunction computePathSalience<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpath: ExpansionPath,\n\tmi: MIFunction<N, E>,\n\tepsilon: number,\n): number {\n\tconst nodes = path.nodes;\n\n\tif (nodes.length < 2) {\n\t\treturn epsilon;\n\t}\n\n\t// Compute MI for each edge\n\tlet productMi = 1;\n\tlet edgeCount = 0;\n\n\tfor (let i = 0; i < nodes.length - 1; i++) {\n\t\tconst source = nodes[i];\n\t\tconst target = nodes[i + 1];\n\n\t\tif (source !== undefined && target !== undefined) {\n\t\t\tconst edgeMi = mi(graph, source, target);\n\t\t\tproductMi *= Math.max(epsilon, edgeMi);\n\t\t\tedgeCount++;\n\t\t}\n\t}\n\n\tif (edgeCount === 0) {\n\t\treturn epsilon;\n\t}\n\n\t// Geometric mean\n\tconst salience = Math.pow(productMi, 1 / edgeCount);\n\treturn Math.max(epsilon, Math.min(1, salience));\n}\n","/**\n * Adamic-Adar index for edge salience.\n *\n * Sum of inverse log degrees of common neighbours:\n * MI(u,v) = Σ_{z ∈ N(u) ∩ N(v)} 1 / log(deg(z) + 1)\n *\n * Range: [0, ∞) - higher values indicate stronger association\n * Normalised to [0, 1] by dividing by max possible value.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport { neighbourSet, neighbourIntersection } from \"../../utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute Adamic-Adar index between neighbourhoods of two nodes.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration\n * @returns Adamic-Adar index (normalised to [0, 1] if configured)\n */\nexport function adamicAdar<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10, normalise = true } = config ?? {};\n\n\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute common neighbours\n\tconst commonNeighbours = neighbourIntersection(\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t);\n\n\t// Sum inverse log degrees of common neighbours\n\tlet score = 0;\n\tfor (const neighbour of commonNeighbours) {\n\t\tconst degree = graph.degree(neighbour);\n\t\tscore += 1 / Math.log(degree + 1);\n\t}\n\n\t// Normalise to [0, 1] if requested\n\tif (normalise && commonNeighbours.size > 0) {\n\t\t// Max possible is when all common neighbours have minimum degree (1)\n\t\t// 1 / log(1 + 1) = 1 / log(2)\n\t\tconst maxScore = commonNeighbours.size / Math.log(2);\n\t\tscore = score / maxScore;\n\t}\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n","/**\n * Cosine similarity for edge salience.\n *\n * Measures similarity between neighbourhoods using vector cosine:\n * MI(u,v) = |N(u) ∩ N(v)| / (√|N(u)| × √|N(v)|)\n *\n * Range: [0, 1]\n * - 0: No overlap or one node has no neighbours\n * - 1: Identical neighbourhoods of equal size\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport { neighbourSet, neighbourOverlap } from \"../../utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute cosine similarity between neighbourhoods of two nodes.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration\n * @returns Cosine similarity in [0, 1]\n */\nexport function cosine<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute intersection size\n\tconst { intersection } = neighbourOverlap(sourceNeighbours, targetNeighbours);\n\n\t// Compute denominator: √|N(u)| × √|N(v)|\n\tconst denominator =\n\t\tMath.sqrt(sourceNeighbours.size) * Math.sqrt(targetNeighbours.size);\n\n\t// Avoid division by zero\n\tif (denominator === 0) {\n\t\treturn 0;\n\t}\n\n\tconst score = intersection / denominator;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n","/**\n * Sorensen-Dice coefficient for edge salience.\n *\n * Harmonic mean of overlap:\n * MI(u,v) = 2|N(u) ∩ N(v)| / (|N(u)| + |N(v)|)\n *\n * Range: [0, 1]\n * - 0: No shared neighbours\n * - 1: Identical neighbourhoods\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport { neighbourSet, neighbourOverlap } from \"../../utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute Sorensen-Dice similarity between neighbourhoods of two nodes.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration\n * @returns Sorensen-Dice coefficient in [0, 1]\n */\nexport function sorensen<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute intersection size\n\tconst { intersection } = neighbourOverlap(sourceNeighbours, targetNeighbours);\n\n\t// Compute denominator: |N(u)| + |N(v)|\n\tconst denominator = sourceNeighbours.size + targetNeighbours.size;\n\n\t// Avoid division by zero\n\tif (denominator === 0) {\n\t\treturn 0;\n\t}\n\n\tconst score = (2 * intersection) / denominator;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n","/**\n * Resource Allocation index for edge salience.\n *\n * Sum of inverse degrees of common neighbours:\n * MI(u,v) = Σ_{z ∈ N(u) ∩ N(v)} 1 / deg(z)\n *\n * Range: [0, ∞)\n * Assumption: shared neighbours with low degree are more informative than hubs.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport { neighbourSet, neighbourIntersection } from \"../../utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute Resource Allocation index between neighbourhoods of two nodes.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration\n * @returns Resource Allocation index (normalised to [0, 1] if configured)\n */\nexport function resourceAllocation<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10, normalise = true } = config ?? {};\n\n\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute common neighbours\n\tconst commonNeighbours = neighbourIntersection(\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t);\n\n\t// Sum inverse degrees of common neighbours\n\tlet score = 0;\n\tfor (const neighbour of commonNeighbours) {\n\t\tconst degree = graph.degree(neighbour);\n\t\tif (degree > 0) {\n\t\t\tscore += 1 / degree;\n\t\t}\n\t}\n\n\t// Normalise to [0, 1] if requested\n\tif (normalise && commonNeighbours.size > 0) {\n\t\t// Max possible is when all common neighbours have minimum degree (1)\n\t\tconst maxScore = commonNeighbours.size;\n\t\tscore = score / maxScore;\n\t}\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n","/**\n * Overlap Coefficient for edge salience.\n *\n * Minimum-based neighbourhood overlap:\n * MI(u,v) = |N(u) ∩ N(v)| / min(|N(u)|, |N(v)|)\n *\n * Range: [0, 1]\n * - 0: No shared neighbours or one node has no neighbours\n * - 1: One neighbourhood is a subset of the other\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport { neighbourSet, neighbourOverlap } from \"../../utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute Overlap Coefficient between neighbourhoods of two nodes.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration\n * @returns Overlap Coefficient in [0, 1]\n */\nexport function overlapCoefficient<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute intersection size\n\tconst { intersection } = neighbourOverlap(sourceNeighbours, targetNeighbours);\n\n\t// Compute denominator: min(|N(u)|, |N(v)|)\n\tconst denominator = Math.min(sourceNeighbours.size, targetNeighbours.size);\n\n\t// Avoid division by zero\n\tif (denominator === 0) {\n\t\treturn 0;\n\t}\n\n\tconst score = intersection / denominator;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n","/**\n * Hub Promoted index for edge salience.\n *\n * Hub-promoting neighbourhood overlap:\n * MI(u,v) = |N(u) ∩ N(v)| / min(deg(u), deg(v))\n *\n * Range: [0, 1]\n * Uses node degrees rather than neighbourhood set sizes.\n * Similar to Overlap Coefficient but normalised by degree.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport { neighbourSet, neighbourOverlap } from \"../../utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute Hub Promoted index between neighbourhoods of two nodes.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration\n * @returns Hub Promoted index in [0, 1]\n */\nexport function hubPromoted<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute intersection size\n\tconst { intersection } = neighbourOverlap(sourceNeighbours, targetNeighbours);\n\n\t// Compute denominator using actual degrees\n\tconst sourceDegree = graph.degree(source);\n\tconst targetDegree = graph.degree(target);\n\tconst denominator = Math.min(sourceDegree, targetDegree);\n\n\t// Avoid division by zero\n\tif (denominator === 0) {\n\t\treturn 0;\n\t}\n\n\tconst score = intersection / denominator;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n","/**\n * SCALE (Structural Coherence via Adjacency Lattice Entropy) MI variant.\n *\n * Density-normalised Jaccard, correcting for graph density variation.\n * Formula: MI(u,v) = Jaccard(u,v) / ρ(G)\n *\n * where ρ(G) = 2 * |E| / (|V| * (|V| - 1)) for undirected graphs\n * ρ(G) = |E| / (|V| * (|V| - 1)) for directed graphs\n *\n * Range: [0, ∞) but typically scales with graph density\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport { computeJaccard } from \"../../utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute SCALE MI between two nodes.\n */\nexport function scale<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\tconst { jaccard: jaccardScore } = computeJaccard(graph, source, target);\n\n\t// Compute graph density\n\tconst n = graph.nodeCount;\n\tconst m = graph.edgeCount;\n\n\t// ρ(G) = 2|E| / (|V|(|V|-1)) for undirected; |E| / (|V|(|V|-1)) for directed\n\tconst possibleEdges = n * (n - 1);\n\tconst density =\n\t\tpossibleEdges > 0 ? (graph.directed ? m : 2 * m) / possibleEdges : 0;\n\n\t// Avoid division by zero: if density is 0, fall back to epsilon\n\tif (density === 0) {\n\t\treturn epsilon;\n\t}\n\n\tconst score = jaccardScore / density;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n","/**\n * SKEW (Structural Kernel Entropy Weighting) MI variant.\n *\n * IDF-style rarity weighting on endpoints, applied to Jaccard base.\n * Formula: MI(u,v) = Jaccard(u,v) * log(N/deg(u)+1) * log(N/deg(v)+1)\n *\n * Range: [0, ∞) but typically [0, 1] for well-connected graphs\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport { computeJaccard } from \"../../utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute SKEW MI between two nodes.\n */\nexport function skew<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\tconst { jaccard: jaccardScore } = computeJaccard(graph, source, target);\n\n\t// Compute IDF-style weights for endpoints\n\tconst N = graph.nodeCount;\n\tconst sourceDegree = graph.degree(source);\n\tconst targetDegree = graph.degree(target);\n\n\tconst sourceIdf = Math.log(N / (sourceDegree + 1));\n\tconst targetIdf = Math.log(N / (targetDegree + 1));\n\n\tconst score = jaccardScore * sourceIdf * targetIdf;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n","/**\n * SPAN (Structural Pattern ANalysis) MI variant.\n *\n * Clustering-coefficient penalty, favouring bridge edges.\n * Formula: MI(u,v) = Jaccard(u,v) * (1 - max(cc(u), cc(v)))\n *\n * Nodes with high clustering coefficient are tightly embedded in triangles;\n * edges between such nodes are less likely to be bridge edges. This variant\n * downweights such edges, favouring paths through bridge edges.\n *\n * Range: [0, 1]\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport { computeJaccard } from \"../../utils\";\nimport { localClusteringCoefficient } from \"../../utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute SPAN MI between two nodes.\n */\nexport function span<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\tconst { jaccard: jaccardScore } = computeJaccard(graph, source, target);\n\n\t// Compute clustering coefficients\n\tconst sourceCc = localClusteringCoefficient(graph, source);\n\tconst targetCc = localClusteringCoefficient(graph, target);\n\n\t// Apply bridge penalty: downweight edges between highly-embedded nodes\n\tconst bridgePenalty = 1 - Math.max(sourceCc, targetCc);\n\n\tconst score = jaccardScore * bridgePenalty;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n","/**\n * ETCH (Edge Topology Coherence via Homophily) MI variant.\n *\n * Edge-type rarity weighting applied to Jaccard base.\n * Formula: MI(u,v) = Jaccard(u,v) * rarity(edgeType(u,v))\n * where rarity(t) = log(|E| / count(edges with type t))\n *\n * Edges of rare types (fewer instances in the graph) receive higher salience,\n * making discoveries across unusual edge relationships more significant.\n *\n * Range: [0, ∞) but typically [0, 1] for well-typed edges\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport { computeJaccard, countEdgesOfType } from \"../../utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute ETCH MI between two nodes.\n */\nexport function etch<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\tconst { jaccard: jaccardScore } = computeJaccard(graph, source, target);\n\n\t// Get edge between source and target\n\tconst edge = graph.getEdge(source, target);\n\n\t// If edge has no type or doesn't exist, fall back to Jaccard\n\tif (edge?.type === undefined) {\n\t\treturn Math.max(epsilon, jaccardScore);\n\t}\n\n\t// Compute edge rarity: log(total edges / edges of this type)\n\tconst edgeTypeCount = countEdgesOfType(graph, edge.type);\n\n\t// Avoid division by zero\n\tif (edgeTypeCount === 0) {\n\t\treturn Math.max(epsilon, jaccardScore);\n\t}\n\n\tconst rarity = Math.log(graph.edgeCount / edgeTypeCount);\n\tconst score = jaccardScore * rarity;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n","/**\n * NOTCH (Neighbourhood Overlap Topology Coherence via Homophily) MI variant.\n *\n * Node-type rarity weighting applied to Jaccard base.\n * Formula: MI(u,v) = Jaccard(u,v) * rarity(nodeType(u)) * rarity(nodeType(v))\n * where rarity(t) = log(|V| / count(nodes with type t))\n *\n * Paths connecting nodes of rare types (fewer instances in the graph) receive higher\n * salience, making discoveries involving unusual node types more significant.\n *\n * Range: [0, ∞) but typically [0, 1] for well-typed nodes\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport { computeJaccard, countNodesOfType } from \"../../utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute NOTCH MI between two nodes.\n */\nexport function notch<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\tconst { jaccard: jaccardScore } = computeJaccard(graph, source, target);\n\n\t// Get node data\n\tconst sourceNode = graph.getNode(source);\n\tconst targetNode = graph.getNode(target);\n\n\t// If either node lacks a type, fall back to Jaccard\n\tif (sourceNode?.type === undefined || targetNode?.type === undefined) {\n\t\treturn Math.max(epsilon, jaccardScore);\n\t}\n\n\t// Compute node rarity: log(total nodes / nodes of this type)\n\tconst sourceTypeCount = countNodesOfType(graph, sourceNode.type);\n\tconst targetTypeCount = countNodesOfType(graph, targetNode.type);\n\n\t// Avoid division by zero\n\tif (sourceTypeCount === 0 || targetTypeCount === 0) {\n\t\treturn Math.max(epsilon, jaccardScore);\n\t}\n\n\tconst sourceRarity = Math.log(graph.nodeCount / sourceTypeCount);\n\tconst targetRarity = Math.log(graph.nodeCount / targetTypeCount);\n\n\tconst score = jaccardScore * sourceRarity * targetRarity;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n","/**\n * Unified Adaptive MI - combines multiple MI signals dynamically.\n *\n * Adapts to graph structure by weighting different MI components\n * based on structural properties.\n *\n * Three-component weighted sum:\n * - Structural: Jaccard neighbourhood overlap\n * - Degree: Adamic-Adar inverse-log-degree weighting\n * - Overlap: Overlap coefficient (intersection / min degree)\n *\n * Range: [0, 1] - higher values indicate stronger association\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport { computeJaccard, neighbourOverlap } from \"../../utils\";\nimport type { AdaptiveMIConfig } from \"./types\";\nimport { adamicAdar } from \"./adamic-adar\";\n\n/**\n * Compute unified adaptive MI between two connected nodes.\n *\n * Combines structural, degree, and overlap signals with\n * configurable weighting.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration with component weights\n * @returns Adaptive MI score in [0, 1]\n */\nexport function adaptive<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: AdaptiveMIConfig,\n): number {\n\tconst {\n\t\tepsilon = 1e-10,\n\t\tstructuralWeight = 0.4,\n\t\tdegreeWeight = 0.3,\n\t\toverlapWeight = 0.3,\n\t} = config ?? {};\n\n\t// Compute Jaccard and retrieve neighbourhood sets for the overlap coefficient\n\tconst {\n\t\tjaccard: jaccardScore,\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t} = computeJaccard(graph, source, target);\n\n\t// Component 1: Structural similarity (Jaccard)\n\t// Returns 0 only when both neighbourhood sets are empty (union = 0); otherwise applies epsilon floor\n\tconst structural =\n\t\tsourceNeighbours.size === 0 && targetNeighbours.size === 0\n\t\t\t? 0\n\t\t\t: Math.max(epsilon, jaccardScore);\n\n\t// Component 2: Degree-weighted association (Adamic-Adar, normalised)\n\tconst degreeComponent = adamicAdar(graph, source, target, {\n\t\tepsilon,\n\t\tnormalise: true,\n\t});\n\n\t// Component 3: Overlap coefficient\n\tlet overlap: number;\n\tif (sourceNeighbours.size > 0 && targetNeighbours.size > 0) {\n\t\tconst { intersection } = neighbourOverlap(\n\t\t\tsourceNeighbours,\n\t\t\ttargetNeighbours,\n\t\t);\n\t\tconst minDegree = Math.min(sourceNeighbours.size, targetNeighbours.size);\n\t\toverlap = minDegree > 0 ? intersection / minDegree : epsilon;\n\t} else {\n\t\toverlap = epsilon;\n\t}\n\n\t// Normalise weights\n\tconst totalWeight = structuralWeight + degreeWeight + overlapWeight;\n\n\t// Weighted combination\n\tconst score =\n\t\t(structuralWeight * structural +\n\t\t\tdegreeWeight * degreeComponent +\n\t\t\toverlapWeight * overlap) /\n\t\ttotalWeight;\n\n\treturn Math.max(epsilon, Math.min(1, score));\n}\n","/**\n * Shared utilities for baseline ranking methods.\n *\n * @packageDocumentation\n */\n\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineResult, ScoredPath } from \"./types\";\n\n/**\n * Normalise a set of scored paths and return them sorted highest-first.\n *\n * All scores are normalised relative to the maximum observed score.\n * When `includeScores` is false, raw (un-normalised) scores are preserved.\n * Handles degenerate cases: empty input and all-zero scores.\n *\n * @param paths - Original paths in input order\n * @param scored - Paths paired with their computed scores\n * @param method - Method name to embed in the result\n * @param includeScores - When true, normalise scores to [0, 1]; when false, keep raw scores\n * @returns BaselineResult with ranked paths\n */\nexport function normaliseAndRank(\n\tpaths: readonly ExpansionPath[],\n\tscored: readonly { readonly path: ExpansionPath; readonly score: number }[],\n\tmethod: string,\n\tincludeScores: boolean,\n): BaselineResult {\n\tif (scored.length === 0) {\n\t\treturn { paths: [], method };\n\t}\n\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case: all paths get score 0\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({ ...path, score: 0 })),\n\t\t\tmethod,\n\t\t};\n\t}\n\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn { paths: ranked, method };\n}\n","/**\n * Shortest path baseline ranking.\n *\n * Ranks paths by length (shorter = higher score).\n * Score = 1 / length (normalised).\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Rank paths by length (shortest first).\n *\n * Score = 1 / path_length, normalised to [0, 1].\n *\n * @param _graph - Source graph (unused for length ranking)\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (shortest first)\n */\nexport function shortest<N extends NodeData, E extends EdgeData>(\n\t_graph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"shortest\",\n\t\t};\n\t}\n\n\t// Compute raw scores (1 / length)\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map(\n\t\t(path) => ({\n\t\t\tpath,\n\t\t\tscore: 1 / path.nodes.length,\n\t\t}),\n\t);\n\n\treturn normaliseAndRank(paths, scored, \"shortest\", includeScores);\n}\n","/**\n * Degree-Sum baseline ranking.\n *\n * Ranks paths by sum of node degrees.\n * Higher degree nodes may indicate more connected (central) nodes.\n * Score = sum(deg(v) for v in path), normalised to [0, 1].\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Rank paths by sum of node degrees.\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest degree-sum first)\n */\nexport function degreeSum<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"degree-sum\",\n\t\t};\n\t}\n\n\t// Compute raw scores (sum of degrees)\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tlet degreeSum = 0;\n\t\tfor (const nodeId of path.nodes) {\n\t\t\tdegreeSum += graph.degree(nodeId);\n\t\t}\n\t\treturn { path, score: degreeSum };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"degree-sum\", includeScores);\n}\n","/**\n * Widest-Path baseline ranking.\n *\n * Ranks paths by bottleneck similarity (minimum edge salience).\n * Uses Jaccard similarity as the edge salience metric.\n * Score = min(jaccard(u, v) for each edge (u,v) in path).\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { jaccard } from \"../mi/jaccard\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Rank paths by widest bottleneck (minimum edge similarity).\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest bottleneck first)\n */\nexport function widestPath<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"widest-path\",\n\t\t};\n\t}\n\n\t// Compute raw scores (minimum edge similarity per path)\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tif (path.nodes.length < 2) {\n\t\t\t// Single-node path: no edges\n\t\t\treturn { path, score: 1 };\n\t\t}\n\n\t\tlet minSimilarity = Number.POSITIVE_INFINITY;\n\t\tfor (let i = 0; i < path.nodes.length - 1; i++) {\n\t\t\tconst source = path.nodes[i];\n\t\t\tconst target = path.nodes[i + 1];\n\t\t\tif (source === undefined || target === undefined) continue;\n\n\t\t\tconst edgeSimilarity = jaccard(graph, source, target);\n\t\t\tminSimilarity = Math.min(minSimilarity, edgeSimilarity);\n\t\t}\n\n\t\t// If no edges were found, default to 1\n\t\tconst score =\n\t\t\tminSimilarity === Number.POSITIVE_INFINITY ? 1 : minSimilarity;\n\t\treturn { path, score };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"widest-path\", includeScores);\n}\n","/**\n * Jaccard-Arithmetic baseline ranking.\n *\n * Ranks paths by arithmetic mean of edge Jaccard similarities.\n * Contrast with PARSE which uses geometric mean.\n * Score = (1/k) * sum(jaccard(u, v) for each edge (u,v)).\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { jaccard } from \"../mi/jaccard\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Rank paths by arithmetic mean of edge Jaccard similarities.\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest arithmetic mean first)\n */\nexport function jaccardArithmetic<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"jaccard-arithmetic\",\n\t\t};\n\t}\n\n\t// Compute raw scores (arithmetic mean of edge similarities)\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tif (path.nodes.length < 2) {\n\t\t\t// Single-node path: no edges, score = 1\n\t\t\treturn { path, score: 1 };\n\t\t}\n\n\t\tlet similaritySum = 0;\n\t\tlet edgeCount = 0;\n\n\t\tfor (let i = 0; i < path.nodes.length - 1; i++) {\n\t\t\tconst source = path.nodes[i];\n\t\t\tconst target = path.nodes[i + 1];\n\t\t\tif (source === undefined || target === undefined) continue;\n\n\t\t\tconst edgeSimilarity = jaccard(graph, source, target);\n\t\t\tsimilaritySum += edgeSimilarity;\n\t\t\tedgeCount++;\n\t\t}\n\n\t\t// Arithmetic mean\n\t\tconst score = edgeCount > 0 ? similaritySum / edgeCount : 1;\n\t\treturn { path, score };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"jaccard-arithmetic\", includeScores);\n}\n","/**\n * PageRank baseline ranking.\n *\n * Computes PageRank centrality for all nodes, then sums PR values per path.\n * Uses power iteration: r(v) = (1-d)/N + d * sum(r(u)/deg_out(u) for u->v)\n * Parameters: d=0.85 (damping factor), tolerance=1e-6, max 100 iterations.\n * Score = sum(pagerank(v) for v in path), normalised to [0, 1].\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Compute PageRank centrality for all nodes using power iteration.\n *\n * @param graph - Source graph\n * @param damping - Damping factor (default 0.85)\n * @param tolerance - Convergence tolerance (default 1e-6)\n * @param maxIterations - Maximum iterations (default 100)\n * @returns Map of node ID to PageRank value\n */\nfunction computePageRank<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tdamping = 0.85,\n\ttolerance = 1e-6,\n\tmaxIterations = 100,\n): Map<string, number> {\n\tconst nodes = Array.from(graph.nodeIds());\n\tconst n = nodes.length;\n\n\tif (n === 0) {\n\t\treturn new Map();\n\t}\n\n\t// Initialise ranks uniformly\n\tconst ranks = new Map<string, number>();\n\tconst newRanks = new Map<string, number>();\n\tfor (const nodeId of nodes) {\n\t\tranks.set(nodeId, 1 / n);\n\t\tnewRanks.set(nodeId, 0);\n\t}\n\n\t// Power iteration\n\tlet isCurrentRanks = true; // Track which map is current\n\n\tfor (let iteration = 0; iteration < maxIterations; iteration++) {\n\t\tlet maxChange = 0;\n\t\tconst currMap = isCurrentRanks ? ranks : newRanks;\n\t\tconst nextMap = isCurrentRanks ? newRanks : ranks;\n\n\t\tfor (const nodeId of nodes) {\n\t\t\t// Sum contributions from incoming neighbours\n\t\t\tlet incomingSum = 0;\n\n\t\t\tfor (const incomingId of graph.neighbours(nodeId, \"in\")) {\n\t\t\t\tconst incomingRank = currMap.get(incomingId) ?? 0;\n\t\t\t\tconst outDegree = graph.degree(incomingId);\n\t\t\t\tif (outDegree > 0) {\n\t\t\t\t\tincomingSum += incomingRank / outDegree;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// PageRank formula\n\t\t\tconst newRank = (1 - damping) / n + damping * incomingSum;\n\t\t\tnextMap.set(nodeId, newRank);\n\n\t\t\t// Track convergence\n\t\t\tconst oldRank = currMap.get(nodeId) ?? 0;\n\t\t\tmaxChange = Math.max(maxChange, Math.abs(newRank - oldRank));\n\t\t}\n\n\t\t// Check convergence before swapping\n\t\tif (maxChange < tolerance) {\n\t\t\tbreak;\n\t\t}\n\n\t\t// Swap buffers and clear the old current map for next iteration\n\t\tisCurrentRanks = !isCurrentRanks;\n\t\tcurrMap.clear();\n\t}\n\n\treturn isCurrentRanks ? ranks : newRanks;\n}\n\n/**\n * Rank paths by sum of PageRank scores.\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest PageRank sum first)\n */\nexport function pagerank<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"pagerank\",\n\t\t};\n\t}\n\n\t// Compute PageRank\n\tconst ranks = computePageRank(graph);\n\n\t// Score paths by sum of node ranks\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tlet prSum = 0;\n\t\tfor (const nodeId of path.nodes) {\n\t\t\tprSum += ranks.get(nodeId) ?? 0;\n\t\t}\n\t\treturn { path, score: prSum };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"pagerank\", includeScores);\n}\n","/**\n * Betweenness baseline ranking.\n *\n * Computes betweenness centrality using Brandes algorithm O(|V||E|).\n * Score = sum(betweenness(v) for v in path), normalised to [0, 1].\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Compute betweenness centrality for all nodes using Brandes algorithm.\n *\n * @param graph - Source graph\n * @returns Map of node ID to betweenness value\n */\nfunction computeBetweenness<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n): Map<string, number> {\n\tconst nodes = Array.from(graph.nodeIds());\n\tconst betweenness = new Map<string, number>();\n\n\t// Initialise all betweenness to 0\n\tfor (const nodeId of nodes) {\n\t\tbetweenness.set(nodeId, 0);\n\t}\n\n\t// For each node as source\n\tfor (const source of nodes) {\n\t\t// BFS to find shortest paths\n\t\tconst predecessors = new Map<string, string[]>();\n\t\tconst distance = new Map<string, number>();\n\t\tconst sigma = new Map<string, number>();\n\t\tconst queue: string[] = [];\n\n\t\t// Initialise\n\t\tfor (const nodeId of nodes) {\n\t\t\tpredecessors.set(nodeId, []);\n\t\t\tdistance.set(nodeId, -1);\n\t\t\tsigma.set(nodeId, 0);\n\t\t}\n\n\t\tdistance.set(source, 0);\n\t\tsigma.set(source, 1);\n\t\tqueue.push(source);\n\n\t\t// BFS\n\t\tfor (const v of queue) {\n\t\t\tconst vDist = distance.get(v) ?? -1;\n\t\t\tconst neighbours = graph.neighbours(v);\n\n\t\t\tfor (const w of neighbours) {\n\t\t\t\tconst wDist = distance.get(w) ?? -1;\n\n\t\t\t\t// First time seeing w?\n\t\t\t\tif (wDist < 0) {\n\t\t\t\t\tdistance.set(w, vDist + 1);\n\t\t\t\t\tqueue.push(w);\n\t\t\t\t}\n\n\t\t\t\t// Shortest path to w through v?\n\t\t\t\tif (wDist === vDist + 1) {\n\t\t\t\t\tconst wSigma = sigma.get(w) ?? 0;\n\t\t\t\t\tconst vSigma = sigma.get(v) ?? 0;\n\t\t\t\t\tsigma.set(w, wSigma + vSigma);\n\n\t\t\t\t\tconst wPred = predecessors.get(w) ?? [];\n\t\t\t\t\twPred.push(v);\n\t\t\t\t\tpredecessors.set(w, wPred);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Accumulation\n\t\tconst delta = new Map<string, number>();\n\t\tfor (const nodeId of nodes) {\n\t\t\tdelta.set(nodeId, 0);\n\t\t}\n\n\t\t// Process in reverse order of distance\n\t\tconst sorted = [...nodes].sort((a, b) => {\n\t\t\tconst aD = distance.get(a) ?? -1;\n\t\t\tconst bD = distance.get(b) ?? -1;\n\t\t\treturn bD - aD;\n\t\t});\n\n\t\tfor (const w of sorted) {\n\t\t\tif (w === source) continue;\n\n\t\t\tconst wDelta = delta.get(w) ?? 0;\n\t\t\tconst wSigma = sigma.get(w) ?? 0;\n\n\t\t\tconst wPred = predecessors.get(w) ?? [];\n\t\t\tfor (const v of wPred) {\n\t\t\t\tconst vSigma = sigma.get(v) ?? 0;\n\t\t\t\tconst vDelta = delta.get(v) ?? 0;\n\n\t\t\t\tif (wSigma > 0) {\n\t\t\t\t\tdelta.set(v, vDelta + (vSigma / wSigma) * (1 + wDelta));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (w !== source) {\n\t\t\t\tconst current = betweenness.get(w) ?? 0;\n\t\t\t\tbetweenness.set(w, current + wDelta);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn betweenness;\n}\n\n/**\n * Rank paths by sum of betweenness scores.\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest betweenness sum first)\n */\nexport function betweenness<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"betweenness\",\n\t\t};\n\t}\n\n\t// Compute betweenness\n\tconst bcMap = computeBetweenness(graph);\n\n\t// Score paths by sum of betweenness\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tlet bcSum = 0;\n\t\tfor (const nodeId of path.nodes) {\n\t\t\tbcSum += bcMap.get(nodeId) ?? 0;\n\t\t}\n\t\treturn { path, score: bcSum };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"betweenness\", includeScores);\n}\n","/**\n * Katz baseline ranking.\n *\n * Truncated Katz centrality: score(s,t) = sum_{k=1}^{K} beta^k * walks_k(s,t)\n * Parameters: K=5 (truncation depth), beta=0.005 (safe damping < 1/lambda_1).\n * For path scoring: score(P) = katz(P.start, P.end), normalised to [0, 1].\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Compute truncated Katz centrality between two nodes.\n *\n * Uses iterative matrix-vector products to avoid full matrix powers.\n * score(s,t) = sum_{k=1}^{K} beta^k * walks_k(s,t)\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param k - Truncation depth (default 5)\n * @param beta - Attenuation factor (default 0.005)\n * @returns Katz score\n */\nfunction computeKatz<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: string,\n\ttarget: string,\n\tk = 5,\n\tbeta = 0.005,\n): number {\n\tconst nodes = Array.from(graph.nodeIds());\n\tconst nodeToIdx = new Map<string, number>();\n\tnodes.forEach((nodeId, idx) => {\n\t\tnodeToIdx.set(nodeId, idx);\n\t});\n\n\tconst n = nodes.length;\n\tif (n === 0) {\n\t\treturn 0;\n\t}\n\n\tconst sourceIdx = nodeToIdx.get(source);\n\tconst targetIdx = nodeToIdx.get(target);\n\n\tif (sourceIdx === undefined || targetIdx === undefined) {\n\t\treturn 0;\n\t}\n\n\t// Current column of A^depth (number of walks of length depth from each node to target)\n\tlet walks = new Float64Array(n);\n\twalks[targetIdx] = 1; // Base case: walks[target] = 1\n\n\tlet katzScore = 0;\n\n\t// Iterate from depth 1 to k\n\tfor (let depth = 1; depth <= k; depth++) {\n\t\t// Multiply by adjacency matrix: walks_next[i] = sum_j A[i,j] * walks[j]\n\t\tconst walksNext = new Float64Array(n);\n\n\t\tfor (const sourceNode of nodes) {\n\t\t\tconst srcIdx = nodeToIdx.get(sourceNode);\n\t\t\tif (srcIdx === undefined) continue;\n\n\t\t\tconst neighbours = graph.neighbours(sourceNode);\n\t\t\tfor (const neighbourId of neighbours) {\n\t\t\t\tconst nIdx = nodeToIdx.get(neighbourId);\n\t\t\t\tif (nIdx === undefined) continue;\n\n\t\t\t\twalksNext[srcIdx] = (walksNext[srcIdx] ?? 0) + (walks[nIdx] ?? 0);\n\t\t\t}\n\t\t}\n\n\t\t// Add contribution: beta^depth * walks_depth[source]\n\t\tconst walkCount = walksNext[sourceIdx] ?? 0;\n\t\tkatzScore += Math.pow(beta, depth) * walkCount;\n\n\t\twalks = walksNext;\n\t}\n\n\treturn katzScore;\n}\n\n/**\n * Rank paths by Katz centrality between endpoints.\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest Katz score first)\n */\nexport function katz<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"katz\",\n\t\t};\n\t}\n\n\t// Score paths by Katz between endpoints\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tconst source = path.nodes[0];\n\t\tconst target = path.nodes[path.nodes.length - 1];\n\n\t\tif (source === undefined || target === undefined) {\n\t\t\treturn { path, score: 0 };\n\t\t}\n\n\t\tconst katzScore = computeKatz(graph, source, target);\n\t\treturn { path, score: katzScore };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"katz\", includeScores);\n}\n","/**\n * Communicability baseline ranking.\n *\n * Computes communicability between nodes using truncated Taylor series.\n * (e^A)_{s,t} ≈ sum_{k=0}^{15} A^k_{s,t} / k!\n * For path scoring: score(P) = communicability(P.start, P.end), normalised to [0, 1].\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Compute truncated communicability between two nodes.\n *\n * Uses Taylor series expansion: (e^A)_{s,t} ≈ sum_{k=0}^{K} A^k_{s,t} / k!\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param k - Truncation depth (default 15)\n * @returns Communicability score\n */\nfunction computeCommunicability<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: string,\n\ttarget: string,\n\tk = 15,\n): number {\n\tconst nodes = Array.from(graph.nodeIds());\n\tconst nodeToIdx = new Map<string, number>();\n\tnodes.forEach((nodeId, idx) => {\n\t\tnodeToIdx.set(nodeId, idx);\n\t});\n\n\tconst n = nodes.length;\n\tif (n === 0) {\n\t\treturn 0;\n\t}\n\n\tconst sourceIdx = nodeToIdx.get(source);\n\tconst targetIdx = nodeToIdx.get(target);\n\n\tif (sourceIdx === undefined || targetIdx === undefined) {\n\t\treturn 0;\n\t}\n\n\t// Current column of A^depth\n\tlet walks = new Float64Array(n);\n\twalks[targetIdx] = 1; // Base case: walks[target] = 1\n\n\t// Compute sum: sum_{k=0}^{K} A^k_{s,t} / k!\n\tlet commScore = walks[sourceIdx] ?? 0; // k=0 term (identity matrix): A^0 = I, so [I]_{s,t} = delta_{s,t}\n\n\tlet factorial = 1;\n\n\t// Iterate from k=1 to k\n\tfor (let depth = 1; depth <= k; depth++) {\n\t\t// Multiply by adjacency matrix: walks_next[i] = sum_j A[i,j] * walks[j]\n\t\tconst walksNext = new Float64Array(n);\n\n\t\tfor (const fromNode of nodes) {\n\t\t\tconst fromIdx = nodeToIdx.get(fromNode);\n\t\t\tif (fromIdx === undefined) continue;\n\n\t\t\tconst neighbours = graph.neighbours(fromNode);\n\t\t\tfor (const toNodeId of neighbours) {\n\t\t\t\tconst toIdx = nodeToIdx.get(toNodeId);\n\t\t\t\tif (toIdx === undefined) continue;\n\n\t\t\t\twalksNext[fromIdx] = (walksNext[fromIdx] ?? 0) + (walks[toIdx] ?? 0);\n\t\t\t}\n\t\t}\n\n\t\tfactorial *= depth;\n\n\t\t// Add contribution: A^depth[s,t] / k!\n\t\tcommScore += (walksNext[sourceIdx] ?? 0) / factorial;\n\n\t\twalks = walksNext;\n\t}\n\n\treturn commScore;\n}\n\n/**\n * Rank paths by communicability between endpoints.\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest communicability first)\n */\nexport function communicability<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"communicability\",\n\t\t};\n\t}\n\n\t// Score paths by communicability between endpoints\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tconst source = path.nodes[0];\n\t\tconst target = path.nodes[path.nodes.length - 1];\n\n\t\tif (source === undefined || target === undefined) {\n\t\t\treturn { path, score: 0 };\n\t\t}\n\n\t\tconst commScore = computeCommunicability(graph, source, target);\n\t\treturn { path, score: commScore };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"communicability\", includeScores);\n}\n","/**\n * Resistance-Distance baseline ranking.\n *\n * Computes effective resistance via Laplacian pseudoinverse (dense, small graphs only).\n * For path scoring: score(P) = 1 / resistance(P.start, P.end), normalised to [0, 1].\n * Size guard: throws if nodeCount > 5000 (O(n^3) complexity).\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Compute effective resistance between two nodes via Laplacian pseudoinverse.\n *\n * Resistance = L^+_{s,s} + L^+_{t,t} - 2*L^+_{s,t}\n * where L^+ is the pseudoinverse of the Laplacian matrix.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @returns Effective resistance\n */\nfunction computeResistance<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: string,\n\ttarget: string,\n): number {\n\tconst nodes = Array.from(graph.nodeIds());\n\tconst nodeToIdx = new Map<string, number>();\n\tnodes.forEach((nodeId, idx) => {\n\t\tnodeToIdx.set(nodeId, idx);\n\t});\n\n\tconst n = nodes.length;\n\n\tif (n === 0 || n > 5000) {\n\t\tthrow new Error(\n\t\t\t`Cannot compute resistance distance: graph too large (${String(n)} nodes). Maximum 5000.`,\n\t\t);\n\t}\n\n\tconst sourceIdx = nodeToIdx.get(source);\n\tconst targetIdx = nodeToIdx.get(target);\n\n\tif (sourceIdx === undefined || targetIdx === undefined) {\n\t\treturn 0;\n\t}\n\n\t// Build Laplacian matrix: L = D - A\n\tconst L: number[][] = Array.from({ length: n }, () =>\n\t\tArray.from({ length: n }, () => 0),\n\t);\n\n\tfor (let i = 0; i < n; i++) {\n\t\tconst nodeId = nodes[i];\n\t\tif (nodeId === undefined) continue;\n\n\t\tconst degree = graph.degree(nodeId);\n\t\tconst row = L[i];\n\t\tif (row !== undefined) {\n\t\t\trow[i] = degree; // Diagonal\n\t\t}\n\n\t\tconst neighbours = graph.neighbours(nodeId);\n\t\tfor (const neighbourId of neighbours) {\n\t\t\tconst j = nodeToIdx.get(neighbourId);\n\t\t\tif (j !== undefined && row !== undefined) {\n\t\t\t\trow[j] = -1; // Off-diagonal\n\t\t\t}\n\t\t}\n\t}\n\n\t// Compute pseudoinverse using Moore-Penrose approach (simplified)\n\t// For small graphs, use LU decomposition with diagonal adjustment\n\tconst Lpinv = pinv(L);\n\n\t// Resistance = L^+_{s,s} + L^+_{t,t} - 2*L^+_{s,t}\n\tconst resistance =\n\t\t(Lpinv[sourceIdx]?.[sourceIdx] ?? 0) +\n\t\t(Lpinv[targetIdx]?.[targetIdx] ?? 0) -\n\t\t2 * (Lpinv[sourceIdx]?.[targetIdx] ?? 0);\n\n\t// Clamp to positive (numerical stability)\n\treturn Math.max(resistance, 1e-10);\n}\n\n/**\n * Compute Moore-Penrose pseudoinverse of a matrix.\n * Simplified implementation for small dense matrices.\n *\n * @param A - Square matrix\n * @returns Pseudoinverse A^+\n */\nfunction pinv(A: number[][]): number[][] {\n\tconst n = A.length;\n\tif (n === 0) return [];\n\n\t// Create copy for singular value computation\n\tconst M = A.map((row) => [...row]);\n\n\t// Simplified: add small regularisation to diagonal before inversion\n\tconst epsilon = 1e-10;\n\tfor (let i = 0; i < n; i++) {\n\t\tconst row = M[i];\n\t\tif (row !== undefined) {\n\t\t\trow[i] = (row[i] ?? 0) + epsilon;\n\t\t}\n\t}\n\n\t// Gaussian elimination with partial pivoting to compute inverse\n\tconst Minv = gaussianInverse(M);\n\n\treturn Minv;\n}\n\n/**\n * Compute matrix inverse using Gaussian elimination with partial pivoting.\n *\n * @param A - Matrix to invert\n * @returns Inverted matrix\n */\nfunction gaussianInverse(A: number[][]): number[][] {\n\tconst n = A.length;\n\n\t// Create augmented matrix [A | I]\n\tconst aug: number[][] = A.map((row, i) => {\n\t\tconst identity: number[] = Array.from({ length: n }, (_, j) =>\n\t\t\ti === j ? 1 : 0,\n\t\t);\n\t\tconst combined: number[] = [...row, ...identity];\n\t\treturn combined;\n\t});\n\n\t// Forward elimination with partial pivoting\n\tfor (let col = 0; col < n; col++) {\n\t\t// Find pivot\n\t\tlet maxRow = col;\n\t\tfor (let row = col + 1; row < n; row++) {\n\t\t\tconst currentRow = aug[row];\n\t\t\tconst maxRowRef = aug[maxRow];\n\t\t\tif (\n\t\t\t\tcurrentRow !== undefined &&\n\t\t\t\tmaxRowRef !== undefined &&\n\t\t\t\tMath.abs(currentRow[col] ?? 0) > Math.abs(maxRowRef[col] ?? 0)\n\t\t\t) {\n\t\t\t\tmaxRow = row;\n\t\t\t}\n\t\t}\n\n\t\t// Swap rows\n\t\tconst currentCol = aug[col];\n\t\tconst maxRowAug = aug[maxRow];\n\t\tif (currentCol !== undefined && maxRowAug !== undefined) {\n\t\t\taug[col] = maxRowAug;\n\t\t\taug[maxRow] = currentCol;\n\t\t}\n\n\t\t// Scale pivot row\n\t\tconst pivotRow = aug[col];\n\t\tconst pivot = pivotRow?.[col];\n\t\tif (pivot === undefined || Math.abs(pivot) < 1e-12) {\n\t\t\tcontinue; // Skip singular column\n\t\t}\n\n\t\tif (pivotRow !== undefined) {\n\t\t\tfor (let j = col; j < 2 * n; j++) {\n\t\t\t\tpivotRow[j] = (pivotRow[j] ?? 0) / pivot;\n\t\t\t}\n\t\t}\n\n\t\t// Eliminate below and above\n\t\tfor (let row = 0; row < n; row++) {\n\t\t\tif (row === col) continue;\n\n\t\t\tconst eliminationRow = aug[row];\n\t\t\tconst factor = eliminationRow?.[col] ?? 0;\n\t\t\tif (eliminationRow !== undefined && pivotRow !== undefined) {\n\t\t\t\tfor (let j = col; j < 2 * n; j++) {\n\t\t\t\t\teliminationRow[j] =\n\t\t\t\t\t\t(eliminationRow[j] ?? 0) - factor * (pivotRow[j] ?? 0);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Extract inverse (right half of augmented matrix)\n\tconst Ainv: number[][] = [];\n\tfor (let i = 0; i < n; i++) {\n\t\tconst row = aug[i];\n\t\tAinv[i] = (row?.slice(n) ?? []).map((v) => v);\n\t}\n\n\treturn Ainv;\n}\n\n/**\n * Rank paths by reciprocal of resistance distance between endpoints.\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest conductance first)\n */\nexport function resistanceDistance<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"resistance-distance\",\n\t\t};\n\t}\n\n\t// Check graph size\n\tconst nodeCount = Array.from(graph.nodeIds()).length;\n\tif (nodeCount > 5000) {\n\t\tthrow new Error(\n\t\t\t`Cannot rank paths: graph too large (${String(nodeCount)} nodes). Resistance distance requires O(n^3) computation; maximum 5000 nodes.`,\n\t\t);\n\t}\n\n\t// Score paths by conductance (1 / resistance)\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tconst source = path.nodes[0];\n\t\tconst target = path.nodes[path.nodes.length - 1];\n\n\t\tif (source === undefined || target === undefined) {\n\t\t\treturn { path, score: 0 };\n\t\t}\n\n\t\tconst resistance = computeResistance(graph, source, target);\n\t\t// Score = conductance = 1 / resistance\n\t\tconst score = 1 / resistance;\n\t\treturn { path, score };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"resistance-distance\", includeScores);\n}\n","/**\n * Random-Ranking baseline.\n *\n * Null hypothesis baseline: ranks paths by seeded random scores.\n * Uses deterministic hash for reproducibility.\n * Score = seededRandom(nodes.join(','), seed), normalised to [0, 1].\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Configuration for random ranking.\n */\ninterface RandomRankingConfig extends BaselineConfig {\n\t/** Random seed for deterministic reproducibility */\n\treadonly seed?: number;\n}\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 * Rank paths randomly (null hypothesis baseline).\n *\n * @param _graph - Source graph (unused)\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (randomly ordered)\n */\nexport function randomRanking<N extends NodeData, E extends EdgeData>(\n\t_graph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: RandomRankingConfig,\n): BaselineResult {\n\tconst { includeScores = true, seed = 0 } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"random\",\n\t\t};\n\t}\n\n\t// Score paths by seeded random hash of node list\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tconst nodesKey = path.nodes.join(\",\");\n\t\tconst score = seededRandom(nodesKey, seed);\n\t\treturn { path, score };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"random\", includeScores);\n}\n","/**\n * Hitting-time ranking baseline.\n *\n * Ranks paths by the inverse of the expected number of steps\n * in a random walk from source to target.\n *\n * Score = 1 / hittingTime(source, target)\n *\n * Two computation modes:\n * - Approximate: Monte Carlo random walk simulation (default, efficient for large graphs)\n * - Exact: Fundamental matrix approach (for small graphs)\n * - Auto: Automatic mode selection (switches at ~100 nodes)\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Configuration for hitting-time ranking.\n */\ninterface HittingTimeConfig extends BaselineConfig {\n\t/** Computation mode: \"exact\", \"approximate\", or \"auto\" (default: \"auto\") */\n\treadonly mode?: \"exact\" | \"approximate\" | \"auto\";\n\t/** Number of Monte Carlo walks for approximate mode (default: 1000) */\n\treadonly walks?: number;\n\t/** Maximum steps per walk (default: 10000) */\n\treadonly maxSteps?: number;\n\t/** Random seed for reproducibility (default: 42) */\n\treadonly seed?: number;\n}\n\n/**\n * Seeded deterministic random number generator (LCG).\n * Suitable for reproducible random walk simulation.\n */\nclass SeededRNG {\n\tprivate state: number;\n\n\tconstructor(seed: number) {\n\t\tthis.state = seed;\n\t}\n\n\t/**\n\t * Generate next pseudorandom value in [0, 1).\n\t */\n\tnext(): number {\n\t\t// Linear congruential generator: standard MINSTD parameters\n\t\tthis.state = (this.state * 1103515245 + 12345) & 0x7fffffff;\n\t\treturn this.state / 0x7fffffff;\n\t}\n}\n\n/**\n * Compute hitting time via Monte Carlo random walk simulation.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param walks - Number of walks to simulate\n * @param maxSteps - Maximum steps per walk\n * @param rng - Seeded RNG instance\n * @returns Average hitting time across walks\n */\nfunction computeHittingTimeApproximate<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: string,\n\ttarget: string,\n\twalks: number,\n\tmaxSteps: number,\n\trng: SeededRNG,\n): number {\n\tif (source === target) {\n\t\treturn 0; // Hitting time from a node to itself is 0\n\t}\n\n\tlet totalSteps = 0;\n\tlet successfulWalks = 0;\n\n\tfor (let w = 0; w < walks; w++) {\n\t\tlet current: string = source;\n\t\tlet steps = 0;\n\n\t\twhile (current !== target && steps < maxSteps) {\n\t\t\tconst neighbours = Array.from(graph.neighbours(current));\n\t\t\tif (neighbours.length === 0) {\n\t\t\t\t// Stuck in sink node\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Uniformly choose next neighbour\n\t\t\tconst nextIdx = Math.floor(rng.next() * neighbours.length);\n\t\t\tconst nextNode = neighbours[nextIdx];\n\t\t\tif (nextNode === undefined) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcurrent = nextNode;\n\t\t\tsteps++;\n\t\t}\n\n\t\tif (current === target) {\n\t\t\ttotalSteps += steps;\n\t\t\tsuccessfulWalks++;\n\t\t}\n\t}\n\n\t// Return average if any walks succeeded\n\tif (successfulWalks > 0) {\n\t\treturn totalSteps / successfulWalks;\n\t}\n\n\t// Return maxSteps as estimate if no walks succeeded\n\treturn maxSteps;\n}\n\n/**\n * Compute hitting time via exact fundamental matrix method.\n *\n * For small graphs, computes exact expected hitting times using\n * the fundamental matrix of the random walk.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @returns Exact hitting time (or approximation if convergence fails)\n */\nfunction computeHittingTimeExact<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: string,\n\ttarget: string,\n): number {\n\tif (source === target) {\n\t\treturn 0;\n\t}\n\n\tconst nodes = Array.from(graph.nodeIds());\n\tconst nodeToIdx = new Map<string, number>();\n\tnodes.forEach((nodeId, idx) => {\n\t\tnodeToIdx.set(nodeId, idx);\n\t});\n\n\tconst n = nodes.length;\n\tconst sourceIdx = nodeToIdx.get(source);\n\tconst targetIdx = nodeToIdx.get(target);\n\n\tif (sourceIdx === undefined || targetIdx === undefined) {\n\t\treturn 0;\n\t}\n\n\t// Build transition matrix P with absorbing target\n\tconst P: number[][] = [];\n\tfor (let i = 0; i < n; i++) {\n\t\tconst row: number[] = [];\n\t\tfor (let j = 0; j < n; j++) {\n\t\t\trow[j] = 0;\n\t\t}\n\t\tP[i] = row;\n\t}\n\n\tfor (const nodeId of nodes) {\n\t\tconst idx = nodeToIdx.get(nodeId);\n\t\tif (idx === undefined) continue;\n\n\t\tconst pRow = P[idx];\n\t\tif (pRow === undefined) continue;\n\n\t\tif (idx === targetIdx) {\n\t\t\t// Target is absorbing state\n\t\t\tpRow[idx] = 1;\n\t\t} else {\n\t\t\tconst neighbours = Array.from(graph.neighbours(nodeId));\n\t\t\tconst degree = neighbours.length;\n\n\t\t\tif (degree > 0) {\n\t\t\t\tfor (const neighbourId of neighbours) {\n\t\t\t\t\tconst nIdx = nodeToIdx.get(neighbourId);\n\t\t\t\t\tif (nIdx !== undefined) {\n\t\t\t\t\t\tpRow[nIdx] = 1 / degree;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Compute fundamental matrix N = (I - Q)^(-1)\n\t// where Q is the submatrix of P excluding the absorbing state\n\tconst transientIndices: number[] = [];\n\n\tfor (let i = 0; i < n; i++) {\n\t\tif (i !== targetIdx) {\n\t\t\ttransientIndices.push(i);\n\t\t}\n\t}\n\n\tconst m = transientIndices.length;\n\tconst Q: number[][] = [];\n\tfor (let i = 0; i < m; i++) {\n\t\tconst row: number[] = [];\n\t\tfor (let j = 0; j < m; j++) {\n\t\t\trow[j] = 0;\n\t\t}\n\t\tQ[i] = row;\n\t}\n\n\tfor (let i = 0; i < m; i++) {\n\t\tconst qRow = Q[i];\n\t\tif (qRow === undefined) continue;\n\t\tconst origI = transientIndices[i];\n\t\tif (origI === undefined) continue;\n\t\tconst pRow = P[origI];\n\t\tif (pRow === undefined) continue;\n\n\t\tfor (let j = 0; j < m; j++) {\n\t\t\tconst origJ = transientIndices[j];\n\t\t\tif (origJ === undefined) continue;\n\t\t\tqRow[j] = pRow[origJ] ?? 0;\n\t\t}\n\t}\n\n\t// Compute I - Q\n\tconst IMQ: number[][] = [];\n\tfor (let i = 0; i < m; i++) {\n\t\tconst row: number[] = [];\n\t\tfor (let j = 0; j < m; j++) {\n\t\t\trow[j] = i === j ? 1 : 0;\n\t\t}\n\t\tIMQ[i] = row;\n\t}\n\n\tfor (let i = 0; i < m; i++) {\n\t\tconst imqRow = IMQ[i];\n\t\tif (imqRow === undefined) continue;\n\t\tconst qRow = Q[i];\n\n\t\tfor (let j = 0; j < m; j++) {\n\t\t\tconst qVal = qRow?.[j] ?? 0;\n\t\t\timqRow[j] = (i === j ? 1 : 0) - qVal;\n\t\t}\n\t}\n\n\t// Invert (I - Q) using Gaussian elimination\n\tconst N = invertMatrix(IMQ);\n\n\tif (N === null) {\n\t\t// Fallback if inversion fails\n\t\treturn 1;\n\t}\n\n\t// Hitting time from source to target = sum of row corresponding to source\n\tconst sourceTransientIdx = transientIndices.indexOf(sourceIdx);\n\tif (sourceTransientIdx < 0) {\n\t\treturn 0; // Source is already the target\n\t}\n\n\tlet hittingTime = 0;\n\tconst row = N[sourceTransientIdx];\n\tif (row !== undefined) {\n\t\tfor (const val of row) {\n\t\t\thittingTime += val;\n\t\t}\n\t}\n\n\treturn hittingTime;\n}\n\n/**\n * Invert a square matrix using Gaussian elimination with partial pivoting.\n *\n * @param matrix - Input matrix (n × n)\n * @returns Inverted matrix, or null if singular\n */\nfunction invertMatrix(matrix: number[][]): number[][] | null {\n\tconst n = matrix.length;\n\tconst aug: number[][] = [];\n\n\t// Create augmented matrix [A | I]\n\tfor (let i = 0; i < n; i++) {\n\t\tconst row: number[] = [];\n\t\tconst matRow = matrix[i];\n\n\t\tfor (let j = 0; j < n; j++) {\n\t\t\trow[j] = matRow?.[j] ?? 0;\n\t\t}\n\t\tfor (let j = 0; j < n; j++) {\n\t\t\trow[n + j] = i === j ? 1 : 0;\n\t\t}\n\t\taug[i] = row;\n\t}\n\n\t// Forward elimination with partial pivoting\n\tfor (let col = 0; col < n; col++) {\n\t\t// Find pivot\n\t\tlet pivotRow = col;\n\t\tconst pivotCol = aug[pivotRow];\n\t\tif (pivotCol === undefined) return null;\n\n\t\tfor (let row = col + 1; row < n; row++) {\n\t\t\tconst currRowVal = aug[row]?.[col] ?? 0;\n\t\t\tconst pivotRowVal = pivotCol[col] ?? 0;\n\t\t\tif (Math.abs(currRowVal) > Math.abs(pivotRowVal)) {\n\t\t\t\tpivotRow = row;\n\t\t\t}\n\t\t}\n\n\t\t// Check for singular matrix\n\t\tconst augPivot = aug[pivotRow];\n\t\tif (augPivot === undefined || Math.abs(augPivot[col] ?? 0) < 1e-10) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Swap rows\n\t\t[aug[col], aug[pivotRow]] = [aug[pivotRow] ?? [], aug[col] ?? []];\n\n\t\t// Scale pivot row\n\t\tconst scaledPivotRow = aug[col];\n\t\tif (scaledPivotRow === undefined) return null;\n\t\tconst pivot = scaledPivotRow[col] ?? 1;\n\n\t\tfor (let j = 0; j < 2 * n; j++) {\n\t\t\tscaledPivotRow[j] = (scaledPivotRow[j] ?? 0) / pivot;\n\t\t}\n\n\t\t// Eliminate column below pivot\n\t\tfor (let row = col + 1; row < n; row++) {\n\t\t\tconst currRow = aug[row];\n\t\t\tif (currRow === undefined) continue;\n\t\t\tconst factor = currRow[col] ?? 0;\n\n\t\t\tfor (let j = 0; j < 2 * n; j++) {\n\t\t\t\tcurrRow[j] = (currRow[j] ?? 0) - factor * (scaledPivotRow[j] ?? 0);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Back substitution\n\tfor (let col = n - 1; col > 0; col--) {\n\t\tconst colRow = aug[col];\n\t\tif (colRow === undefined) return null;\n\n\t\tfor (let row = col - 1; row >= 0; row--) {\n\t\t\tconst currRow = aug[row];\n\t\t\tif (currRow === undefined) continue;\n\n\t\t\tconst factor = currRow[col] ?? 0;\n\t\t\tfor (let j = 0; j < 2 * n; j++) {\n\t\t\t\tcurrRow[j] = (currRow[j] ?? 0) - factor * (colRow[j] ?? 0);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Extract inverse from augmented matrix\n\tconst inv: number[][] = [];\n\tfor (let i = 0; i < n; i++) {\n\t\tconst row: number[] = [];\n\t\tfor (let j = 0; j < n; j++) {\n\t\t\trow[j] = 0;\n\t\t}\n\t\tinv[i] = row;\n\t}\n\n\tfor (let i = 0; i < n; i++) {\n\t\tconst invRow = inv[i];\n\t\tif (invRow === undefined) continue;\n\t\tconst augRow = aug[i];\n\t\tif (augRow === undefined) continue;\n\n\t\tfor (let j = 0; j < n; j++) {\n\t\t\tinvRow[j] = augRow[n + j] ?? 0;\n\t\t}\n\t}\n\n\treturn inv;\n}\n\n/**\n * Rank paths by inverse hitting time between endpoints.\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest inverse hitting time first)\n */\nexport function hittingTime<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: HittingTimeConfig,\n): BaselineResult {\n\tconst {\n\t\tincludeScores = true,\n\t\tmode = \"auto\",\n\t\twalks = 1000,\n\t\tmaxSteps = 10000,\n\t\tseed = 42,\n\t} = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"hitting-time\",\n\t\t};\n\t}\n\n\t// Choose computation mode\n\tconst nodeCount = Array.from(graph.nodeIds()).length;\n\tconst actualMode =\n\t\tmode === \"auto\" ? (nodeCount < 100 ? \"exact\" : \"approximate\") : mode;\n\n\tconst rng = new SeededRNG(seed);\n\n\t// Score paths by inverse hitting time between endpoints\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tconst source = path.nodes[0];\n\t\tconst target = path.nodes[path.nodes.length - 1];\n\n\t\tif (source === undefined || target === undefined) {\n\t\t\treturn { path, score: 0 };\n\t\t}\n\n\t\tconst ht =\n\t\t\tactualMode === \"exact\"\n\t\t\t\t? computeHittingTimeExact(graph, source, target)\n\t\t\t\t: computeHittingTimeApproximate(\n\t\t\t\t\t\tgraph,\n\t\t\t\t\t\tsource,\n\t\t\t\t\t\ttarget,\n\t\t\t\t\t\twalks,\n\t\t\t\t\t\tmaxSteps,\n\t\t\t\t\t\trng,\n\t\t\t\t\t);\n\n\t\t// Use inverse: shorter hitting time = higher score\n\t\tconst score = ht > 0 ? 1 / ht : 0;\n\t\treturn { path, score };\n\t});\n\n\t// Guard against non-finite scores before normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\tif (!Number.isFinite(maxScore)) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({ ...path, score: 0 })),\n\t\t\tmethod: \"hitting-time\",\n\t\t};\n\t}\n\n\treturn normaliseAndRank(paths, scored, \"hitting-time\", includeScores);\n}\n","/**\n * Ego-network (k-hop neighbourhood) extraction.\n *\n * Extracts the induced subgraph of all nodes within k hops of a centre node.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport { AdjacencyMapGraph } from \"../graph/adjacency-map\";\n\n/**\n * Options for ego-network extraction.\n */\nexport interface EgoNetworkOptions {\n\t/** Number of hops from the centre node. Default: 1. */\n\treadonly hops?: number;\n}\n\n/**\n * Extract the ego-network (k-hop neighbourhood) of a centre node.\n *\n * The ego-network includes all nodes reachable within k hops from the\n * centre node, plus all edges between those nodes (induced subgraph).\n *\n * For directed graphs, the search follows outgoing edges by default.\n * To include incoming edges, use direction 'both' in the underlying traversal.\n *\n * @param graph - The source graph\n * @param centre - The centre node ID\n * @param options - Extraction options\n * @returns An induced subgraph of the k-hop neighbourhood\n * @throws Error if the centre node does not exist in the graph\n *\n * @example\n * ```typescript\n * // 2-hop neighbourhood\n * const ego = extractEgoNetwork(graph, 'A', { hops: 2 });\n * ```\n */\nexport function extractEgoNetwork<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tcentre: NodeId,\n\toptions?: EgoNetworkOptions,\n): AdjacencyMapGraph<N, E> {\n\tconst hops = options?.hops ?? 1;\n\n\tif (!graph.hasNode(centre)) {\n\t\tthrow new Error(`Centre node '${centre}' does not exist in the graph`);\n\t}\n\n\tif (hops < 0) {\n\t\tthrow new Error(`Hops must be non-negative, got ${String(hops)}`);\n\t}\n\n\t// Find all nodes within k hops using BFS\n\tconst nodesInEgoNetwork = new Set<NodeId>([centre]);\n\n\tif (hops > 0) {\n\t\tconst visited = new Set<NodeId>([centre]);\n\t\t// Queue entries: [nodeId, distance from centre]\n\t\tconst queue: [NodeId, number][] = [[centre, 0]];\n\n\t\twhile (queue.length > 0) {\n\t\t\tconst entry = queue.shift();\n\t\t\tif (entry === undefined) break;\n\t\t\tconst [current, distance] = entry;\n\n\t\t\tif (distance < hops) {\n\t\t\t\tfor (const neighbour of graph.neighbours(current)) {\n\t\t\t\t\tif (!visited.has(neighbour)) {\n\t\t\t\t\t\tvisited.add(neighbour);\n\t\t\t\t\t\tnodesInEgoNetwork.add(neighbour);\n\t\t\t\t\t\tqueue.push([neighbour, distance + 1]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Build induced subgraph\n\tconst result = graph.directed\n\t\t? AdjacencyMapGraph.directed<N, E>()\n\t\t: AdjacencyMapGraph.undirected<N, E>();\n\n\t// Add nodes\n\tfor (const nodeId of nodesInEgoNetwork) {\n\t\tconst nodeData = graph.getNode(nodeId);\n\t\tif (nodeData !== undefined) {\n\t\t\tresult.addNode(nodeData);\n\t\t}\n\t}\n\n\t// Add edges between nodes in the ego network\n\tfor (const edge of graph.edges()) {\n\t\tif (\n\t\t\tnodesInEgoNetwork.has(edge.source) &&\n\t\t\tnodesInEgoNetwork.has(edge.target)\n\t\t) {\n\t\t\tresult.addEdge(edge);\n\t\t}\n\t}\n\n\treturn result;\n}\n","/**\n * K-core decomposition algorithm.\n *\n * A k-core is the maximal subgraph where every node has degree at least k.\n * The decomposition is computed by iteratively removing nodes with degree < k.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport { AdjacencyMapGraph } from \"../graph/adjacency-map\";\n\n/**\n * Extract the k-core of a graph.\n *\n * The k-core is the maximal connected subgraph where every node has\n * degree at least k. This is computed using a peeling algorithm that\n * iteratively removes nodes with degree less than k.\n *\n * For undirected graphs, degree counts all adjacent nodes.\n * For directed graphs, degree counts both in- and out-neighbours.\n *\n * @param graph - The source graph\n * @param k - The minimum degree threshold\n * @returns A new graph containing the k-core (may be empty)\n *\n * @example\n * ```typescript\n * // Extract the 3-core (nodes with at least 3 neighbours)\n * const core3 = extractKCore(graph, 3);\n * ```\n */\nexport function extractKCore<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tk: number,\n): AdjacencyMapGraph<N, E> {\n\tif (k < 0) {\n\t\tthrow new Error(`k must be non-negative, got ${String(k)}`);\n\t}\n\n\t// Track remaining nodes and their degrees\n\tconst remaining = new Set<NodeId>();\n\tconst degrees = new Map<NodeId, number>();\n\n\tfor (const nodeId of graph.nodeIds()) {\n\t\tremaining.add(nodeId);\n\t\t// For directed graphs, use total degree (both directions)\n\t\tconst deg = graph.directed\n\t\t\t? graph.degree(nodeId, \"both\")\n\t\t\t: graph.degree(nodeId);\n\t\tdegrees.set(nodeId, deg);\n\t}\n\n\t// Use a queue for nodes to remove (degree < k)\n\tconst toRemove: NodeId[] = [];\n\n\tfor (const [nodeId, deg] of degrees) {\n\t\tif (deg < k) {\n\t\t\ttoRemove.push(nodeId);\n\t\t}\n\t}\n\n\t// Iteratively remove nodes with degree < k\n\twhile (toRemove.length > 0) {\n\t\tconst nodeId = toRemove.shift();\n\t\tif (nodeId === undefined) break;\n\n\t\tif (!remaining.has(nodeId)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tremaining.delete(nodeId);\n\n\t\t// Update degrees of neighbours\n\t\tconst neighbours = graph.directed\n\t\t\t? graph.neighbours(nodeId, \"both\")\n\t\t\t: graph.neighbours(nodeId);\n\n\t\tfor (const neighbour of neighbours) {\n\t\t\tif (remaining.has(neighbour)) {\n\t\t\t\tconst currentDeg = degrees.get(neighbour) ?? 0;\n\t\t\t\tconst newDeg = currentDeg - 1;\n\t\t\t\tdegrees.set(neighbour, newDeg);\n\n\t\t\t\tif (newDeg < k && newDeg === k - 1) {\n\t\t\t\t\t// Only add to queue if crossing below k threshold\n\t\t\t\t\ttoRemove.push(neighbour);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Build the result as an induced subgraph\n\tconst result = graph.directed\n\t\t? AdjacencyMapGraph.directed<N, E>()\n\t\t: AdjacencyMapGraph.undirected<N, E>();\n\n\t// Add remaining nodes\n\tfor (const nodeId of remaining) {\n\t\tconst nodeData = graph.getNode(nodeId);\n\t\tif (nodeData !== undefined) {\n\t\t\tresult.addNode(nodeData);\n\t\t}\n\t}\n\n\t// Add edges between remaining nodes\n\tfor (const edge of graph.edges()) {\n\t\tif (remaining.has(edge.source) && remaining.has(edge.target)) {\n\t\t\tresult.addEdge(edge);\n\t\t}\n\t}\n\n\treturn result;\n}\n","/**\n * K-truss decomposition algorithm.\n *\n * A k-truss is the maximal subgraph where every edge participates in at\n * least k-2 triangles. The 2-truss is the entire graph, the 3-truss\n * requires each edge to be in at least one triangle, etc.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport { AdjacencyMapGraph } from \"../graph/adjacency-map\";\n\n/**\n * Count triangles involving a given edge.\n *\n * For an edge (u, v), count common neighbours of u and v.\n * Each common neighbour w forms a triangle u-v-w.\n *\n * @param graph - The graph\n * @param u - First endpoint\n * @param v - Second endpoint\n * @returns Number of triangles containing the edge (u, v)\n */\nfunction countEdgeTriangles(\n\tgraph: ReadableGraph,\n\tu: NodeId,\n\tv: NodeId,\n): number {\n\tconst uNeighbours = new Set(graph.neighbours(u));\n\tlet count = 0;\n\n\tfor (const w of graph.neighbours(v)) {\n\t\tif (w !== u && uNeighbours.has(w)) {\n\t\t\tcount++;\n\t\t}\n\t}\n\n\treturn count;\n}\n\n/**\n * Extract the k-truss of a graph.\n *\n * The k-truss is the maximal subgraph where every edge participates in\n * at least k-2 triangles. This is computed by iteratively removing edges\n * with fewer than k-2 triangles, then removing isolated nodes.\n *\n * Note: K-truss is typically defined for undirected graphs. For directed\n * graphs, this treats the graph as undirected for triangle counting.\n *\n * @param graph - The source graph\n * @param k - The minimum triangle count threshold (edge must be in >= k-2 triangles)\n * @returns A new graph containing the k-truss (may be empty)\n *\n * @example\n * ```typescript\n * // Extract the 3-truss (edges in at least 1 triangle)\n * const truss3 = extractKTruss(graph, 3);\n * ```\n */\nexport function extractKTruss<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tk: number,\n): AdjacencyMapGraph<N, E> {\n\tif (k < 2) {\n\t\tthrow new Error(`k must be at least 2, got ${String(k)}`);\n\t}\n\n\tconst minTriangles = k - 2;\n\n\t// Build undirected adjacency for triangle counting\n\t// Store as Map<NodeId, Set<NodeId>> for efficient lookup\n\tconst adjacency = new Map<NodeId, Set<NodeId>>();\n\tconst edgeData = new Map<string, E>();\n\tconst remainingEdges = new Set<string>();\n\n\tfor (const nodeId of graph.nodeIds()) {\n\t\tadjacency.set(nodeId, new Set());\n\t}\n\n\t// Build adjacency (treating as undirected)\n\tfor (const edge of graph.edges()) {\n\t\tconst { source, target } = edge;\n\n\t\t// Add to adjacency (both directions)\n\t\tadjacency.get(source)?.add(target);\n\t\tadjacency.get(target)?.add(source);\n\n\t\t// Store edge data with canonical key\n\t\tconst key =\n\t\t\tsource < target ? `${source}::${target}` : `${target}::${source}`;\n\t\tedgeData.set(key, edge);\n\t\tremainingEdges.add(key);\n\t}\n\n\t// Compute initial triangle counts for each edge\n\tconst triangleCounts = new Map<string, number>();\n\tconst edgesToRemove: string[] = [];\n\n\tfor (const key of remainingEdges) {\n\t\tconst edge = edgeData.get(key);\n\t\tif (edge !== undefined) {\n\t\t\tconst count = countEdgeTriangles(graph, edge.source, edge.target);\n\t\t\ttriangleCounts.set(key, count);\n\t\t\tif (count < minTriangles) {\n\t\t\t\tedgesToRemove.push(key);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Iteratively remove edges with insufficient triangles\n\twhile (edgesToRemove.length > 0) {\n\t\tconst edgeKey = edgesToRemove.shift();\n\t\tif (edgeKey === undefined) break;\n\n\t\tif (!remainingEdges.has(edgeKey)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tremainingEdges.delete(edgeKey);\n\t\tconst edge = edgeData.get(edgeKey);\n\n\t\tif (edge === undefined) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { source, target } = edge;\n\n\t\t// Remove from adjacency\n\t\tadjacency.get(source)?.delete(target);\n\t\tadjacency.get(target)?.delete(source);\n\n\t\t// Find triangles that were broken and update counts\n\t\t// Common neighbours form triangles (source, target, neighbour)\n\t\tconst sourceNeighbours = adjacency.get(source);\n\t\tif (sourceNeighbours !== undefined) {\n\t\t\tfor (const w of adjacency.get(target) ?? []) {\n\t\t\t\tif (sourceNeighbours.has(w)) {\n\t\t\t\t\t// Triangle (source, target, w) is broken\n\t\t\t\t\t// Update triangle counts for edges (source, w) and (target, w)\n\t\t\t\t\tconst keySw = source < w ? `${source}::${w}` : `${w}::${source}`;\n\t\t\t\t\tconst keyTw = target < w ? `${target}::${w}` : `${w}::${target}`;\n\n\t\t\t\t\tfor (const keyToUpdate of [keySw, keyTw]) {\n\t\t\t\t\t\tif (remainingEdges.has(keyToUpdate)) {\n\t\t\t\t\t\t\tconst currentCount = triangleCounts.get(keyToUpdate) ?? 0;\n\t\t\t\t\t\t\tconst newCount = currentCount - 1;\n\t\t\t\t\t\t\ttriangleCounts.set(keyToUpdate, newCount);\n\n\t\t\t\t\t\t\tif (newCount < minTriangles && newCount === minTriangles - 1) {\n\t\t\t\t\t\t\t\tedgesToRemove.push(keyToUpdate);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Determine which nodes are still connected by remaining edges\n\tconst nodesWithEdges = new Set<NodeId>();\n\tfor (const key of remainingEdges) {\n\t\tconst edge = edgeData.get(key);\n\t\tif (edge !== undefined) {\n\t\t\tnodesWithEdges.add(edge.source);\n\t\t\tnodesWithEdges.add(edge.target);\n\t\t}\n\t}\n\n\t// Build the result\n\tconst result = graph.directed\n\t\t? AdjacencyMapGraph.directed<N, E>()\n\t\t: AdjacencyMapGraph.undirected<N, E>();\n\n\t// Add nodes that have at least one remaining edge\n\tfor (const nodeId of nodesWithEdges) {\n\t\tconst nodeData = graph.getNode(nodeId);\n\t\tif (nodeData !== undefined) {\n\t\t\tresult.addNode(nodeData);\n\t\t}\n\t}\n\n\t// Add remaining edges\n\tfor (const key of remainingEdges) {\n\t\tconst edge = edgeData.get(key);\n\t\tif (\n\t\t\tedge !== undefined &&\n\t\t\tresult.hasNode(edge.source) &&\n\t\t\tresult.hasNode(edge.target)\n\t\t) {\n\t\t\tresult.addEdge(edge);\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Compute the truss number for each edge.\n *\n * The truss number of an edge is the largest k such that the edge\n * belongs to the k-truss.\n *\n * @param graph - The source graph\n * @returns Map from edge key (canonical \"u::v\") to truss number\n *\n * @example\n * ```typescript\n * const trussNumbers = computeTrussNumbers(graph);\n * const edgeKey = 'A::B'; // where A < B lexicographically\n * console.log(`Edge A-B is in the ${trussNumbers.get(edgeKey)}-truss`);\n * ```\n */\nexport function computeTrussNumbers<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n): Map<string, number> {\n\t// Build adjacency and edge tracking\n\tconst adjacency = new Map<NodeId, Set<NodeId>>();\n\tconst edgeData = new Map<string, E>();\n\tconst remainingEdges = new Set<string>();\n\n\tfor (const nodeId of graph.nodeIds()) {\n\t\tadjacency.set(nodeId, new Set());\n\t}\n\n\tfor (const edge of graph.edges()) {\n\t\tconst { source, target } = edge;\n\t\tadjacency.get(source)?.add(target);\n\t\tadjacency.get(target)?.add(source);\n\n\t\tconst key =\n\t\t\tsource < target ? `${source}::${target}` : `${target}::${source}`;\n\t\tedgeData.set(key, edge);\n\t\tremainingEdges.add(key);\n\t}\n\n\t// Compute initial triangle counts\n\tconst triangleCounts = new Map<string, number>();\n\tfor (const key of remainingEdges) {\n\t\tconst edge = edgeData.get(key);\n\t\tif (edge !== undefined) {\n\t\t\ttriangleCounts.set(\n\t\t\t\tkey,\n\t\t\t\tcountEdgeTriangles(graph, edge.source, edge.target),\n\t\t\t);\n\t\t}\n\t}\n\n\t// Result map\n\tconst trussNumbers = new Map<string, number>();\n\n\t// Process edges in order of triangle count\n\tconst edgesByTriangleCount = new Map<number, Set<string>>();\n\n\tfor (const [key, count] of triangleCounts) {\n\t\tif (!edgesByTriangleCount.has(count)) {\n\t\t\tedgesByTriangleCount.set(count, new Set());\n\t\t}\n\t\tedgesByTriangleCount.get(count)?.add(key);\n\t}\n\n\t// Process from lowest triangle count upwards\n\tconst sortedCounts = [...edgesByTriangleCount.keys()].sort((a, b) => a - b);\n\n\tfor (const currentCount of sortedCounts) {\n\t\tconst bucket = edgesByTriangleCount.get(currentCount);\n\t\tif (bucket === undefined) continue;\n\n\t\twhile (bucket.size > 0) {\n\t\t\tconst edgeKey = bucket.values().next().value;\n\t\t\tif (edgeKey === undefined) break;\n\t\t\tbucket.delete(edgeKey);\n\n\t\t\tif (!remainingEdges.has(edgeKey)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Truss number is triangle count + 2\n\t\t\tconst trussNumber = currentCount + 2;\n\t\t\ttrussNumbers.set(edgeKey, trussNumber);\n\t\t\tremainingEdges.delete(edgeKey);\n\n\t\t\tconst edge = edgeData.get(edgeKey);\n\t\t\tif (edge === undefined) continue;\n\n\t\t\tconst { source, target } = edge;\n\n\t\t\t// Remove from adjacency\n\t\t\tadjacency.get(source)?.delete(target);\n\t\t\tadjacency.get(target)?.delete(source);\n\n\t\t\t// Update triangle counts for affected edges\n\t\t\tconst sourceNeighbours = adjacency.get(source);\n\t\t\tif (sourceNeighbours !== undefined) {\n\t\t\t\tfor (const w of adjacency.get(target) ?? []) {\n\t\t\t\t\tif (sourceNeighbours.has(w)) {\n\t\t\t\t\t\tconst keySw = source < w ? `${source}::${w}` : `${w}::${source}`;\n\t\t\t\t\t\tconst keyTw = target < w ? `${target}::${w}` : `${w}::${target}`;\n\n\t\t\t\t\t\tfor (const keyToUpdate of [keySw, keyTw]) {\n\t\t\t\t\t\t\tif (remainingEdges.has(keyToUpdate)) {\n\t\t\t\t\t\t\t\tconst oldCount = triangleCounts.get(keyToUpdate) ?? 0;\n\t\t\t\t\t\t\t\tconst newCount = oldCount - 1;\n\t\t\t\t\t\t\t\ttriangleCounts.set(keyToUpdate, newCount);\n\n\t\t\t\t\t\t\t\t// Move to new bucket\n\t\t\t\t\t\t\t\tedgesByTriangleCount.get(oldCount)?.delete(keyToUpdate);\n\t\t\t\t\t\t\t\tif (!edgesByTriangleCount.has(newCount)) {\n\t\t\t\t\t\t\t\t\tedgesByTriangleCount.set(newCount, new Set());\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tedgesByTriangleCount.get(newCount)?.add(keyToUpdate);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn trussNumbers;\n}\n","/**\n * Motif enumeration algorithms.\n *\n * Motifs are small recurring subgraph patterns. This module provides\n * enumeration and counting of motifs of size 3 and 4.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\n\n/**\n * Result of a motif census operation.\n */\nexport interface MotifCensus {\n\t/** Map from motif type identifier to count */\n\treadonly counts: ReadonlyMap<string, number>;\n\t/** Optional map from motif type to node instances */\n\treadonly instances?: ReadonlyMap<string, readonly NodeId[][]>;\n}\n\n/**\n * Canonicalise an edge pattern for hashing.\n *\n * Returns a canonical string representation of a small graph pattern.\n */\nfunction canonicalisePattern(\n\tnodeCount: number,\n\tedges: readonly (readonly [number, number])[],\n): string {\n\t// For small graphs (3-4 nodes), we enumerate all permutations\n\t// and return the lexicographically smallest edge list\n\n\tconst permutations = getPermutations(nodeCount);\n\tlet minPattern: string | null = null;\n\n\tfor (const perm of permutations) {\n\t\t// Transform edges according to permutation\n\t\tconst transformedEdges = edges\n\t\t\t.map(([u, v]) => {\n\t\t\t\tconst pu = perm[u] ?? -1;\n\t\t\t\tconst pv = perm[v] ?? -1;\n\t\t\t\tif (pu < 0 || pv < 0) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\treturn pu < pv\n\t\t\t\t\t? `${String(pu)}-${String(pv)}`\n\t\t\t\t\t: `${String(pv)}-${String(pu)}`;\n\t\t\t})\n\t\t\t.filter((edge): edge is string => edge !== undefined)\n\t\t\t.sort()\n\t\t\t.join(\",\");\n\n\t\tif (minPattern === null || transformedEdges < minPattern) {\n\t\t\tminPattern = transformedEdges;\n\t\t}\n\t}\n\n\treturn minPattern ?? \"\";\n}\n\n/**\n * Generate all permutations of [0, n-1].\n */\nfunction getPermutations(n: number): number[][] {\n\tif (n === 0) return [[]];\n\tif (n === 1) return [[0]];\n\n\tconst result: number[][] = [];\n\tconst arr = Array.from({ length: n }, (_, i) => i);\n\n\tfunction permute(start: number): void {\n\t\tif (start === n - 1) {\n\t\t\tresult.push([...arr]);\n\t\t\treturn;\n\t\t}\n\n\t\tfor (let i = start; i < n; i++) {\n\t\t\tconst startVal = arr[start];\n\t\t\tconst iVal = arr[i];\n\t\t\tif (startVal === undefined || iVal === undefined) continue;\n\t\t\tarr[start] = iVal;\n\t\t\tarr[i] = startVal;\n\t\t\tpermute(start + 1);\n\t\t\tarr[start] = startVal;\n\t\t\tarr[i] = iVal;\n\t\t}\n\t}\n\n\tpermute(0);\n\treturn result;\n}\n\n/**\n * Enumerate all 3-node motifs in the graph.\n *\n * A 3-node motif (triad) can be one of 4 isomorphism classes for undirected graphs:\n * - Empty: no edges\n * - 1-edge: single edge\n * - 2-star: two edges sharing a node (path of length 2)\n * - Triangle: three edges (complete graph K3)\n *\n * For directed graphs, there are 16 isomorphism classes.\n *\n * @param graph - The source graph\n * @param includeInstances - Whether to include node instances in the result\n * @returns Motif census with counts and optionally instances\n */\nfunction enumerate3NodeMotifs<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tincludeInstances: boolean,\n): MotifCensus {\n\tconst counts = new Map<string, number>();\n\tconst instances = includeInstances\n\t\t? new Map<string, NodeId[][]>()\n\t\t: undefined;\n\n\tconst nodeList = [...graph.nodeIds()];\n\tconst n = nodeList.length;\n\n\t// Iterate over all triples of nodes\n\tfor (let i = 0; i < n; i++) {\n\t\tconst ni = nodeList[i];\n\t\tif (ni === undefined) continue;\n\t\tfor (let j = i + 1; j < n; j++) {\n\t\t\tconst nj = nodeList[j];\n\t\t\tif (nj === undefined) continue;\n\t\t\tfor (let k = j + 1; k < n; k++) {\n\t\t\t\tconst nk = nodeList[k];\n\t\t\t\tif (nk === undefined) continue;\n\n\t\t\t\tconst nodes: [NodeId, NodeId, NodeId] = [ni, nj, nk];\n\t\t\t\tconst edges: [number, number][] = [];\n\n\t\t\t\t// Check all 3 possible edges\n\t\t\t\tconst edgeChecks: [number, number][] = [\n\t\t\t\t\t[0, 1],\n\t\t\t\t\t[0, 2],\n\t\t\t\t\t[1, 2],\n\t\t\t\t];\n\n\t\t\t\tfor (const [u, v] of edgeChecks) {\n\t\t\t\t\tconst nu = nodes[u];\n\t\t\t\t\tconst nv = nodes[v];\n\t\t\t\t\tif (nu === undefined || nv === undefined) continue;\n\n\t\t\t\t\tif (graph.getEdge(nu, nv) !== undefined) {\n\t\t\t\t\t\tedges.push([u, v]);\n\t\t\t\t\t} else if (!graph.directed && graph.getEdge(nv, nu) !== undefined) {\n\t\t\t\t\t\tedges.push([u, v]);\n\t\t\t\t\t} else if (graph.directed && graph.getEdge(nv, nu) !== undefined) {\n\t\t\t\t\t\t// For directed graphs, store directed edge\n\t\t\t\t\t\tedges.push([v, u]);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst pattern = canonicalisePattern(3, edges);\n\t\t\t\tconst count = counts.get(pattern) ?? 0;\n\t\t\t\tcounts.set(pattern, count + 1);\n\n\t\t\t\tif (includeInstances && instances !== undefined) {\n\t\t\t\t\tif (!instances.has(pattern)) {\n\t\t\t\t\t\tinstances.set(pattern, []);\n\t\t\t\t\t}\n\t\t\t\t\tconst patternInstances = instances.get(pattern);\n\t\t\t\t\tif (patternInstances !== undefined) {\n\t\t\t\t\t\tpatternInstances.push([ni, nj, nk]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (instances !== undefined) {\n\t\treturn { counts, instances };\n\t}\n\treturn { counts };\n}\n\n/**\n * Enumerate all 4-node motifs in the graph.\n *\n * A 4-node motif can be one of 11 isomorphism classes for undirected graphs\n * (ranging from empty to complete K4), or many more for directed graphs.\n *\n * @param graph - The source graph\n * @param includeInstances - Whether to include node instances in the result\n * @returns Motif census with counts and optionally instances\n */\nfunction enumerate4NodeMotifs<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tincludeInstances: boolean,\n): MotifCensus {\n\tconst counts = new Map<string, number>();\n\tconst instances = includeInstances\n\t\t? new Map<string, NodeId[][]>()\n\t\t: undefined;\n\n\tconst nodeList = [...graph.nodeIds()];\n\tconst n = nodeList.length;\n\n\t// Iterate over all quadruples of nodes\n\tfor (let i = 0; i < n; i++) {\n\t\tconst ni = nodeList[i];\n\t\tif (ni === undefined) continue;\n\t\tfor (let j = i + 1; j < n; j++) {\n\t\t\tconst nj = nodeList[j];\n\t\t\tif (nj === undefined) continue;\n\t\t\tfor (let k = j + 1; k < n; k++) {\n\t\t\t\tconst nk = nodeList[k];\n\t\t\t\tif (nk === undefined) continue;\n\t\t\t\tfor (let l = k + 1; l < n; l++) {\n\t\t\t\t\tconst nl = nodeList[l];\n\t\t\t\t\tif (nl === undefined) continue;\n\n\t\t\t\t\tconst nodes: [NodeId, NodeId, NodeId, NodeId] = [ni, nj, nk, nl];\n\t\t\t\t\tconst edges: [number, number][] = [];\n\n\t\t\t\t\t// Check all 6 possible edges\n\t\t\t\t\tconst edgeChecks: [number, number][] = [\n\t\t\t\t\t\t[0, 1],\n\t\t\t\t\t\t[0, 2],\n\t\t\t\t\t\t[0, 3],\n\t\t\t\t\t\t[1, 2],\n\t\t\t\t\t\t[1, 3],\n\t\t\t\t\t\t[2, 3],\n\t\t\t\t\t];\n\n\t\t\t\t\tfor (const [u, v] of edgeChecks) {\n\t\t\t\t\t\tconst nu = nodes[u];\n\t\t\t\t\t\tconst nv = nodes[v];\n\t\t\t\t\t\tif (nu === undefined || nv === undefined) continue;\n\n\t\t\t\t\t\tif (graph.getEdge(nu, nv) !== undefined) {\n\t\t\t\t\t\t\tedges.push([u, v]);\n\t\t\t\t\t\t} else if (!graph.directed && graph.getEdge(nv, nu) !== undefined) {\n\t\t\t\t\t\t\tedges.push([u, v]);\n\t\t\t\t\t\t} else if (graph.directed && graph.getEdge(nv, nu) !== undefined) {\n\t\t\t\t\t\t\t// For directed graphs, store directed edge\n\t\t\t\t\t\t\tedges.push([v, u]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst pattern = canonicalisePattern(4, edges);\n\t\t\t\t\tconst count = counts.get(pattern) ?? 0;\n\t\t\t\t\tcounts.set(pattern, count + 1);\n\n\t\t\t\t\tif (includeInstances && instances !== undefined) {\n\t\t\t\t\t\tif (!instances.has(pattern)) {\n\t\t\t\t\t\t\tinstances.set(pattern, []);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst patternInstances = instances.get(pattern);\n\t\t\t\t\t\tif (patternInstances !== undefined) {\n\t\t\t\t\t\t\tpatternInstances.push([ni, nj, nk, nl]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (instances !== undefined) {\n\t\treturn { counts, instances };\n\t}\n\treturn { counts };\n}\n\n/**\n * Human-readable names for common 3-node motifs.\n */\nconst MOTIF_3_NAMES: ReadonlyMap<string, string> = new Map([\n\t[\"\", \"empty\"], // No edges\n\t[\"0-1\", \"1-edge\"], // Single edge\n\t[\"0-1,0-2\", \"2-star\"], // Path of length 2 (V-shape)\n\t[\"0-1,1-2\", \"path-3\"], // Path of length 2 (alternative)\n\t[\"0-1,0-2,1-2\", \"triangle\"], // Complete K3\n]);\n\n/**\n * Human-readable names for common 4-node motifs.\n */\nconst MOTIF_4_NAMES: ReadonlyMap<string, string> = new Map([\n\t[\"\", \"empty\"],\n\t[\"0-1\", \"1-edge\"],\n\t[\"0-1,0-2\", \"2-star\"],\n\t[\"0-1,0-2,0-3\", \"3-star\"],\n\t[\"0-1,0-2,1-2\", \"triangle\"], // K3 + isolated\n\t[\"0-1,0-2,1-2,2-3\", \"paw\"], // Triangle with tail\n\t[\"0-1,0-2,2-3\", \"path-4\"], // Path of length 3\n\t[\"0-1,0-2,1-3,2-3\", \"4-cycle\"], // Cycle C4\n\t[\"0-1,0-2,1-2,0-3,1-3\", \"diamond\"], // K4 minus one edge\n\t[\"0-1,0-2,0-3,1-2,1-3,2-3\", \"K4\"], // Complete graph\n]);\n\n/**\n * Enumerate motifs of a given size in the graph.\n *\n * This function counts all occurrences of each distinct motif type\n * (isomorphism class) in the graph. For graphs with many nodes,\n * 4-motif enumeration can be expensive (O(n^4) worst case).\n *\n * @param graph - The source graph\n * @param size - Motif size (3 or 4 nodes)\n * @returns Motif census with counts per motif type\n *\n * @example\n * ```typescript\n * // Count all triangles and other 3-node patterns\n * const census3 = enumerateMotifs(graph, 3);\n * console.log(`Triangles: ${census3.counts.get('0-1,0-2,1-2')}`);\n *\n * // Count 4-node patterns\n * const census4 = enumerateMotifs(graph, 4);\n * ```\n */\nexport function enumerateMotifs<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsize: 3 | 4,\n): MotifCensus {\n\t// Don't include instances by default for efficiency\n\treturn size === 3\n\t\t? enumerate3NodeMotifs(graph, false)\n\t\t: enumerate4NodeMotifs(graph, false);\n}\n\n/**\n * Enumerate motifs with optional instance tracking.\n *\n * @param graph - The source graph\n * @param size - Motif size (3 or 4 nodes)\n * @param includeInstances - Whether to include node instances\n * @returns Motif census with counts and optionally instances\n */\nexport function enumerateMotifsWithInstances<\n\tN extends NodeData,\n\tE extends EdgeData,\n>(\n\tgraph: ReadableGraph<N, E>,\n\tsize: 3 | 4,\n\tincludeInstances: boolean,\n): MotifCensus {\n\treturn size === 3\n\t\t? enumerate3NodeMotifs(graph, includeInstances)\n\t\t: enumerate4NodeMotifs(graph, includeInstances);\n}\n\n/**\n * Get a human-readable name for a motif pattern.\n *\n * @param pattern - The canonical pattern string\n * @param size - Motif size (3 or 4 nodes)\n * @returns A human-readable name, or the pattern itself if unknown\n */\nexport function getMotifName(pattern: string, size: 3 | 4): string {\n\tconst names = size === 3 ? MOTIF_3_NAMES : MOTIF_4_NAMES;\n\treturn names.get(pattern) ?? pattern;\n}\n","/**\n * Induced subgraph extraction.\n *\n * Extracts a subgraph containing exactly the specified nodes and all\n * edges between them from the original graph.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport { AdjacencyMapGraph } from \"../graph/adjacency-map\";\n\n/**\n * Extract the induced subgraph containing exactly the specified nodes.\n *\n * The induced subgraph includes all nodes from the input set that exist\n * in the original graph, plus all edges where both endpoints are in the set.\n *\n * @param graph - The source graph\n * @param nodes - Set of node IDs to include in the subgraph\n * @returns A new graph containing the induced subgraph\n *\n * @example\n * ```typescript\n * const subgraph = extractInducedSubgraph(graph, new Set(['A', 'B', 'C']));\n * ```\n */\nexport function extractInducedSubgraph<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tnodes: ReadonlySet<NodeId>,\n): AdjacencyMapGraph<N, E> {\n\tconst result = graph.directed\n\t\t? AdjacencyMapGraph.directed<N, E>()\n\t\t: AdjacencyMapGraph.undirected<N, E>();\n\n\t// Add nodes that exist in both the set and the graph\n\tfor (const nodeId of nodes) {\n\t\tconst nodeData = graph.getNode(nodeId);\n\t\tif (nodeData !== undefined) {\n\t\t\tresult.addNode(nodeData);\n\t\t}\n\t}\n\n\t// Add edges where both endpoints exist in the result\n\tfor (const edge of graph.edges()) {\n\t\tif (result.hasNode(edge.source) && result.hasNode(edge.target)) {\n\t\t\tresult.addEdge(edge);\n\t\t}\n\t}\n\n\treturn result;\n}\n","/**\n * Filtered subgraph extraction.\n *\n * Extracts a subgraph based on predicate functions for nodes and edges.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport { AdjacencyMapGraph } from \"../graph/adjacency-map\";\n\n/**\n * Options for filtering a subgraph.\n */\nexport interface FilterOptions<N extends NodeData, E extends EdgeData> {\n\t/** Predicate to filter nodes. Return true to include the node. */\n\treadonly nodePredicate?: (node: N) => boolean;\n\t/** Predicate to filter edges. Return true to include the edge. */\n\treadonly edgePredicate?: (edge: E) => boolean;\n\t/** Whether to remove nodes that become isolated after edge filtering. Default: false. */\n\treadonly removeIsolated?: boolean;\n}\n\n/**\n * Extract a filtered subgraph based on node and edge predicates.\n *\n * Nodes are first filtered by the node predicate (if provided).\n * Edges are then filtered by the edge predicate (if provided), and only\n * retained if both endpoints pass the node predicate.\n *\n * @param graph - The source graph\n * @param options - Filter options specifying node/edge predicates\n * @returns A new graph containing only nodes and edges that pass the predicates\n *\n * @example\n * ```typescript\n * // Extract subgraph of high-weight nodes\n * const filtered = filterSubgraph(graph, {\n * nodePredicate: (node) => (node.weight ?? 0) > 0.5,\n * removeIsolated: true\n * });\n * ```\n */\nexport function filterSubgraph<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\toptions?: FilterOptions<N, E>,\n): AdjacencyMapGraph<N, E> {\n\tconst {\n\t\tnodePredicate,\n\t\tedgePredicate,\n\t\tremoveIsolated = false,\n\t} = options ?? {};\n\n\tconst result = graph.directed\n\t\t? AdjacencyMapGraph.directed<N, E>()\n\t\t: AdjacencyMapGraph.undirected<N, E>();\n\n\t// Track which nodes were added\n\tconst includedNodes = new Set<NodeId>();\n\n\t// Add nodes that pass the predicate\n\tfor (const nodeId of graph.nodeIds()) {\n\t\tconst nodeData = graph.getNode(nodeId);\n\t\tif (nodeData !== undefined) {\n\t\t\tif (nodePredicate === undefined || nodePredicate(nodeData)) {\n\t\t\t\tresult.addNode(nodeData);\n\t\t\t\tincludedNodes.add(nodeId);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Add edges that pass both endpoint and edge predicates\n\tfor (const edge of graph.edges()) {\n\t\tif (!includedNodes.has(edge.source) || !includedNodes.has(edge.target)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (edgePredicate === undefined || edgePredicate(edge)) {\n\t\t\tresult.addEdge(edge);\n\t\t}\n\t}\n\n\t// Remove isolated nodes if requested\n\tif (removeIsolated) {\n\t\tconst isolatedNodes: NodeId[] = [];\n\t\tfor (const nodeId of result.nodeIds()) {\n\t\t\tif (result.degree(nodeId) === 0) {\n\t\t\t\tisolatedNodes.push(nodeId);\n\t\t\t}\n\t\t}\n\t\tfor (const nodeId of isolatedNodes) {\n\t\t\tresult.removeNode(nodeId);\n\t\t}\n\t}\n\n\treturn result;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","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/ranking/mi/jaccard.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","../../src/ranking/parse.ts","../../src/ranking/mi/adamic-adar.ts","../../src/ranking/mi/cosine.ts","../../src/ranking/mi/sorensen.ts","../../src/ranking/mi/resource-allocation.ts","../../src/ranking/mi/overlap-coefficient.ts","../../src/ranking/mi/hub-promoted.ts","../../src/ranking/mi/scale.ts","../../src/ranking/mi/skew.ts","../../src/ranking/mi/span.ts","../../src/ranking/mi/etch.ts","../../src/ranking/mi/notch.ts","../../src/ranking/mi/adaptive.ts","../../src/ranking/baselines/utils.ts","../../src/ranking/baselines/shortest.ts","../../src/ranking/baselines/degree-sum.ts","../../src/ranking/baselines/widest-path.ts","../../src/ranking/baselines/jaccard-arithmetic.ts","../../src/ranking/baselines/pagerank.ts","../../src/ranking/baselines/betweenness.ts","../../src/ranking/baselines/katz.ts","../../src/ranking/baselines/communicability.ts","../../src/ranking/baselines/resistance-distance.ts","../../src/ranking/baselines/random-ranking.ts","../../src/ranking/baselines/hitting-time.ts","../../src/extraction/ego-network.ts","../../src/extraction/k-core.ts","../../src/extraction/truss.ts","../../src/extraction/motif.ts","../../src/extraction/induced-subgraph.ts","../../src/extraction/node-filter.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 * Jaccard similarity coefficient for edge salience.\n *\n * Measures overlap between neighbourhoods of connected nodes:\n * MI(u,v) = |N(u) ∩ N(v)| / |N(u) ∪ N(v)|\n *\n * Range: [0, 1]\n * - 0: No shared neighbours (low salience)\n * - 1: Identical neighbourhoods (high salience)\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { AsyncReadableGraph } from \"../../graph/async-interfaces\";\nimport { computeJaccard } from \"../../utils\";\nimport { collectAsyncIterable } from \"../../async/utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute Jaccard similarity between neighbourhoods of two nodes.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration\n * @returns Jaccard coefficient in [0, 1]\n */\nexport function jaccard<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\tconst {\n\t\tjaccard: jaccardScore,\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t} = computeJaccard(graph, source, target);\n\n\t// Return 0 only when the union is empty (no neighbours on either side)\n\tif (sourceNeighbours.size === 0 && targetNeighbours.size === 0) {\n\t\treturn 0;\n\t}\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, jaccardScore);\n}\n\n/**\n * Async variant of Jaccard similarity for use with async graph data sources.\n *\n * Fetches both neighbourhoods concurrently, then applies the same formula.\n */\nexport async function jaccardAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): Promise<number> {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Fetch both neighbourhoods in parallel\n\tconst [sourceNeighboursArr, targetNeighboursArr] = await Promise.all([\n\t\tcollectAsyncIterable(graph.neighbours(source)),\n\t\tcollectAsyncIterable(graph.neighbours(target)),\n\t]);\n\n\tconst srcSet = new Set(sourceNeighboursArr.filter((n) => n !== target));\n\tconst tgtSet = new Set(targetNeighboursArr.filter((n) => n !== source));\n\n\t// Return 0 only when the union is empty (no neighbours on either side)\n\tif (srcSet.size === 0 && tgtSet.size === 0) {\n\t\treturn 0;\n\t}\n\n\t// Compute intersection and union sizes\n\tlet intersection = 0;\n\tfor (const n of srcSet) {\n\t\tif (tgtSet.has(n)) intersection++;\n\t}\n\tconst union = srcSet.size + tgtSet.size - intersection;\n\n\tconst jaccardScore = union > 0 ? intersection / union : 0;\n\n\treturn Math.max(epsilon, jaccardScore);\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","/**\n * PARSE (Path-Aware Ranking via Salience Estimation).\n *\n * Ranks discovered paths by computing geometric mean of edge MI scores.\n * Path salience = (∏ MI(uᵢ, uᵢ₊₁))^(1/|path|)\n *\n * This ranking is length-unbiased: shorter paths with strong edges\n * can outrank longer paths with weak edges.\n *\n * @module ranking/parse\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type { ExpansionPath } from \"../expansion/types\";\nimport type { MIFunction, AsyncMIFunction } from \"./mi/types\";\nimport { jaccard } from \"./mi/jaccard\";\nimport { jaccardAsync } from \"./mi/jaccard\";\n\n/**\n * Configuration for PARSE ranking.\n */\nexport interface PARSEConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> {\n\t/** MI function to use (default: jaccard) */\n\treadonly mi?: MIFunction<N, E>;\n\t/** Minimum epsilon for MI (default: 1e-10) */\n\treadonly epsilon?: number;\n\t/** Whether to include salience scores in result (default: true) */\n\treadonly includeSalience?: boolean;\n}\n\n/**\n * A ranked path with salience score.\n */\nexport interface RankedPath extends ExpansionPath {\n\t/** Salience score (geometric mean of edge MI) */\n\treadonly salience: number;\n}\n\n/**\n * Result of PARSE ranking.\n */\nexport interface PARSEResult {\n\t/** Paths ranked by salience (highest first) */\n\treadonly paths: readonly RankedPath[];\n\t/** Ranking statistics */\n\treadonly stats: {\n\t\t/** Total paths ranked */\n\t\treadonly pathsRanked: number;\n\t\t/** Mean salience */\n\t\treadonly meanSalience: number;\n\t\t/** Median salience */\n\t\treadonly medianSalience: number;\n\t\t/** Maximum salience */\n\t\treadonly maxSalience: number;\n\t\t/** Minimum salience */\n\t\treadonly minSalience: number;\n\t\t/** Ranking duration in milliseconds */\n\t\treadonly durationMs: number;\n\t};\n}\n\n/**\n * Rank paths using PARSE (Path-Aware Ranking via Salience Estimation).\n *\n * Computes geometric mean of edge MI scores for each path,\n * then sorts by salience (highest first).\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths with statistics\n */\nexport function parse<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: PARSEConfig<N, E>,\n): PARSEResult {\n\tconst startTime = performance.now();\n\n\tconst { mi = jaccard, epsilon = 1e-10 } = config ?? {};\n\n\tconst rankedPaths: RankedPath[] = [];\n\n\tfor (const path of paths) {\n\t\tconst salience = computePathSalience(graph, path, mi, epsilon);\n\t\trankedPaths.push({\n\t\t\t...path,\n\t\t\tsalience,\n\t\t});\n\t}\n\n\t// Sort by salience descending\n\trankedPaths.sort((a, b) => b.salience - a.salience);\n\n\tconst endTime = performance.now();\n\n\t// Compute statistics\n\tconst saliences = rankedPaths.map((p) => p.salience);\n\tconst meanSalience =\n\t\tsaliences.length > 0\n\t\t\t? saliences.reduce((a, b) => a + b, 0) / saliences.length\n\t\t\t: 0;\n\tconst sortedSaliences = [...saliences].sort((a, b) => a - b);\n\tconst mid = Math.floor(sortedSaliences.length / 2);\n\tconst medianSalience =\n\t\tsortedSaliences.length > 0\n\t\t\t? sortedSaliences.length % 2 !== 0\n\t\t\t\t? (sortedSaliences[mid] ?? 0)\n\t\t\t\t: ((sortedSaliences[mid - 1] ?? 0) + (sortedSaliences[mid] ?? 0)) / 2\n\t\t\t: 0;\n\tconst maxSalience =\n\t\tsortedSaliences.length > 0\n\t\t\t? (sortedSaliences[sortedSaliences.length - 1] ?? 0)\n\t\t\t: 0;\n\tconst minSalience =\n\t\tsortedSaliences.length > 0 ? (sortedSaliences[0] ?? 0) : 0;\n\n\treturn {\n\t\tpaths: rankedPaths,\n\t\tstats: {\n\t\t\tpathsRanked: rankedPaths.length,\n\t\t\tmeanSalience,\n\t\t\tmedianSalience,\n\t\t\tmaxSalience,\n\t\t\tminSalience,\n\t\t\tdurationMs: endTime - startTime,\n\t\t},\n\t};\n}\n\n/**\n * Configuration for async PARSE ranking.\n */\nexport interface AsyncPARSEConfig<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> {\n\t/** Async MI function to use (default: jaccardAsync) */\n\treadonly mi?: AsyncMIFunction<N, E>;\n\t/** Minimum epsilon for MI (default: 1e-10) */\n\treadonly epsilon?: number;\n}\n\n/**\n * Rank paths using async PARSE (Path-Aware Ranking via Salience Estimation).\n *\n * Async variant suitable for use with remote or lazy graph data sources.\n * Computes geometric mean of edge MI scores for each path using Promise.all\n * for parallelism, then sorts by salience (highest first).\n *\n * @param graph - Async source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths with statistics\n */\nexport async function parseAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: AsyncPARSEConfig<N, E>,\n): Promise<PARSEResult> {\n\tconst startTime = performance.now();\n\n\tconst { mi = jaccardAsync, epsilon = 1e-10 } = config ?? {};\n\n\tconst rankedPaths: RankedPath[] = [];\n\n\tfor (const path of paths) {\n\t\tconst salience = await computePathSalienceAsync(graph, path, mi, epsilon);\n\t\trankedPaths.push({\n\t\t\t...path,\n\t\t\tsalience,\n\t\t});\n\t}\n\n\t// Sort by salience descending\n\trankedPaths.sort((a, b) => b.salience - a.salience);\n\n\tconst endTime = performance.now();\n\n\t// Compute statistics\n\tconst saliences = rankedPaths.map((p) => p.salience);\n\tconst meanSalience =\n\t\tsaliences.length > 0\n\t\t\t? saliences.reduce((a, b) => a + b, 0) / saliences.length\n\t\t\t: 0;\n\tconst sortedSaliences = [...saliences].sort((a, b) => a - b);\n\tconst mid = Math.floor(sortedSaliences.length / 2);\n\tconst medianSalience =\n\t\tsortedSaliences.length > 0\n\t\t\t? sortedSaliences.length % 2 !== 0\n\t\t\t\t? (sortedSaliences[mid] ?? 0)\n\t\t\t\t: ((sortedSaliences[mid - 1] ?? 0) + (sortedSaliences[mid] ?? 0)) / 2\n\t\t\t: 0;\n\tconst maxSalience =\n\t\tsortedSaliences.length > 0\n\t\t\t? (sortedSaliences[sortedSaliences.length - 1] ?? 0)\n\t\t\t: 0;\n\tconst minSalience =\n\t\tsortedSaliences.length > 0 ? (sortedSaliences[0] ?? 0) : 0;\n\n\treturn {\n\t\tpaths: rankedPaths,\n\t\tstats: {\n\t\t\tpathsRanked: rankedPaths.length,\n\t\t\tmeanSalience,\n\t\t\tmedianSalience,\n\t\t\tmaxSalience,\n\t\t\tminSalience,\n\t\t\tdurationMs: endTime - startTime,\n\t\t},\n\t};\n}\n\n/**\n * Compute salience for a single path asynchronously.\n *\n * Uses geometric mean of edge MI scores for length-unbiased ranking.\n * Edge MI values are computed in parallel via Promise.all.\n */\nasync function computePathSalienceAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tpath: ExpansionPath,\n\tmi: AsyncMIFunction<N, E>,\n\tepsilon: number,\n): Promise<number> {\n\tconst nodes = path.nodes;\n\n\tif (nodes.length < 2) {\n\t\treturn epsilon;\n\t}\n\n\t// Compute MI for each edge in parallel\n\tconst edgeMIs = await Promise.all(\n\t\tnodes.slice(0, -1).map((source, i) => {\n\t\t\tconst target = nodes[i + 1];\n\t\t\tif (target !== undefined) {\n\t\t\t\treturn mi(graph, source, target);\n\t\t\t}\n\t\t\treturn Promise.resolve(epsilon);\n\t\t}),\n\t);\n\n\tlet productMi = 1;\n\tlet edgeCount = 0;\n\n\tfor (const edgeMi of edgeMIs) {\n\t\tproductMi *= Math.max(epsilon, edgeMi);\n\t\tedgeCount++;\n\t}\n\n\tif (edgeCount === 0) {\n\t\treturn epsilon;\n\t}\n\n\t// Geometric mean\n\tconst salience = Math.pow(productMi, 1 / edgeCount);\n\treturn Math.max(epsilon, Math.min(1, salience));\n}\n\n/**\n * Compute salience for a single path.\n *\n * Uses geometric mean of edge MI scores for length-unbiased ranking.\n */\nfunction computePathSalience<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpath: ExpansionPath,\n\tmi: MIFunction<N, E>,\n\tepsilon: number,\n): number {\n\tconst nodes = path.nodes;\n\n\tif (nodes.length < 2) {\n\t\treturn epsilon;\n\t}\n\n\t// Compute MI for each edge\n\tlet productMi = 1;\n\tlet edgeCount = 0;\n\n\tfor (let i = 0; i < nodes.length - 1; i++) {\n\t\tconst source = nodes[i];\n\t\tconst target = nodes[i + 1];\n\n\t\tif (source !== undefined && target !== undefined) {\n\t\t\tconst edgeMi = mi(graph, source, target);\n\t\t\tproductMi *= Math.max(epsilon, edgeMi);\n\t\t\tedgeCount++;\n\t\t}\n\t}\n\n\tif (edgeCount === 0) {\n\t\treturn epsilon;\n\t}\n\n\t// Geometric mean\n\tconst salience = Math.pow(productMi, 1 / edgeCount);\n\treturn Math.max(epsilon, Math.min(1, salience));\n}\n","/**\n * Adamic-Adar index for edge salience.\n *\n * Sum of inverse log degrees of common neighbours:\n * MI(u,v) = Σ_{z ∈ N(u) ∩ N(v)} 1 / log(deg(z) + 1)\n *\n * Range: [0, ∞) - higher values indicate stronger association\n * Normalised to [0, 1] by dividing by max possible value.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { AsyncReadableGraph } from \"../../graph/async-interfaces\";\nimport { neighbourSet, neighbourIntersection } from \"../../utils\";\nimport { collectAsyncIterable } from \"../../async/utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute Adamic-Adar index between neighbourhoods of two nodes.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration\n * @returns Adamic-Adar index (normalised to [0, 1] if configured)\n */\nexport function adamicAdar<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10, normalise = true } = config ?? {};\n\n\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute common neighbours\n\tconst commonNeighbours = neighbourIntersection(\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t);\n\n\t// Sum inverse log degrees of common neighbours\n\tlet score = 0;\n\tfor (const neighbour of commonNeighbours) {\n\t\tconst degree = graph.degree(neighbour);\n\t\tscore += 1 / Math.log(degree + 1);\n\t}\n\n\t// Normalise to [0, 1] if requested\n\tif (normalise && commonNeighbours.size > 0) {\n\t\t// Max possible is when all common neighbours have minimum degree (1)\n\t\t// 1 / log(1 + 1) = 1 / log(2)\n\t\tconst maxScore = commonNeighbours.size / Math.log(2);\n\t\tscore = score / maxScore;\n\t}\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n\n/**\n * Async variant of Adamic-Adar index for use with async graph data sources.\n *\n * Fetches both neighbourhoods concurrently, then fetches degree for each common\n * neighbour to compute the inverse-log-degree weighted sum.\n */\nexport async function adamicAdarAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): Promise<number> {\n\tconst { epsilon = 1e-10, normalise = true } = config ?? {};\n\n\t// Fetch both neighbourhoods in parallel\n\tconst [sourceArr, targetArr] = await Promise.all([\n\t\tcollectAsyncIterable(graph.neighbours(source)),\n\t\tcollectAsyncIterable(graph.neighbours(target)),\n\t]);\n\n\tconst srcSet = new Set(sourceArr.filter((n) => n !== target));\n\tconst tgtSet = new Set(targetArr.filter((n) => n !== source));\n\n\t// Find common neighbours\n\tconst commonNeighbours: NodeId[] = [];\n\tfor (const n of srcSet) {\n\t\tif (tgtSet.has(n)) commonNeighbours.push(n);\n\t}\n\n\tif (commonNeighbours.length === 0) {\n\t\treturn epsilon;\n\t}\n\n\t// Fetch degrees of all common neighbours in parallel\n\tconst degrees = await Promise.all(\n\t\tcommonNeighbours.map((n) => graph.degree(n)),\n\t);\n\n\t// Sum inverse log degrees\n\tlet score = 0;\n\tfor (const degree of degrees) {\n\t\tscore += 1 / Math.log(degree + 1);\n\t}\n\n\t// Normalise to [0, 1] if requested\n\tif (normalise) {\n\t\t// Max possible is when all common neighbours have minimum degree (1)\n\t\t// 1 / log(1 + 1) = 1 / log(2)\n\t\tconst maxScore = commonNeighbours.length / Math.log(2);\n\t\tscore = score / maxScore;\n\t}\n\n\treturn Math.max(epsilon, score);\n}\n","/**\n * Cosine similarity for edge salience.\n *\n * Measures similarity between neighbourhoods using vector cosine:\n * MI(u,v) = |N(u) ∩ N(v)| / (√|N(u)| × √|N(v)|)\n *\n * Range: [0, 1]\n * - 0: No overlap or one node has no neighbours\n * - 1: Identical neighbourhoods of equal size\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { AsyncReadableGraph } from \"../../graph/async-interfaces\";\nimport { neighbourSet, neighbourOverlap } from \"../../utils\";\nimport { collectAsyncIterable } from \"../../async/utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute cosine similarity between neighbourhoods of two nodes.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration\n * @returns Cosine similarity in [0, 1]\n */\nexport function cosine<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute intersection size\n\tconst { intersection } = neighbourOverlap(sourceNeighbours, targetNeighbours);\n\n\t// Compute denominator: √|N(u)| × √|N(v)|\n\tconst denominator =\n\t\tMath.sqrt(sourceNeighbours.size) * Math.sqrt(targetNeighbours.size);\n\n\t// Avoid division by zero\n\tif (denominator === 0) {\n\t\treturn 0;\n\t}\n\n\tconst score = intersection / denominator;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n\n/**\n * Async variant of cosine similarity for use with async graph data sources.\n *\n * Fetches both neighbourhoods concurrently, then applies the same formula.\n */\nexport async function cosineAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): Promise<number> {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Fetch both neighbourhoods in parallel\n\tconst [sourceArr, targetArr] = await Promise.all([\n\t\tcollectAsyncIterable(graph.neighbours(source)),\n\t\tcollectAsyncIterable(graph.neighbours(target)),\n\t]);\n\n\tconst srcSet = new Set(sourceArr.filter((n) => n !== target));\n\tconst tgtSet = new Set(targetArr.filter((n) => n !== source));\n\n\t// Compute intersection size\n\tlet intersection = 0;\n\tfor (const n of srcSet) {\n\t\tif (tgtSet.has(n)) intersection++;\n\t}\n\n\t// Compute denominator: √|N(u)| × √|N(v)|\n\tconst denominator = Math.sqrt(srcSet.size) * Math.sqrt(tgtSet.size);\n\n\t// Avoid division by zero\n\tif (denominator === 0) {\n\t\treturn 0;\n\t}\n\n\tconst score = intersection / denominator;\n\n\treturn Math.max(epsilon, score);\n}\n","/**\n * Sorensen-Dice coefficient for edge salience.\n *\n * Harmonic mean of overlap:\n * MI(u,v) = 2|N(u) ∩ N(v)| / (|N(u)| + |N(v)|)\n *\n * Range: [0, 1]\n * - 0: No shared neighbours\n * - 1: Identical neighbourhoods\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { AsyncReadableGraph } from \"../../graph/async-interfaces\";\nimport { neighbourSet, neighbourOverlap } from \"../../utils\";\nimport { collectAsyncIterable } from \"../../async/utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute Sorensen-Dice similarity between neighbourhoods of two nodes.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration\n * @returns Sorensen-Dice coefficient in [0, 1]\n */\nexport function sorensen<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute intersection size\n\tconst { intersection } = neighbourOverlap(sourceNeighbours, targetNeighbours);\n\n\t// Compute denominator: |N(u)| + |N(v)|\n\tconst denominator = sourceNeighbours.size + targetNeighbours.size;\n\n\t// Avoid division by zero\n\tif (denominator === 0) {\n\t\treturn 0;\n\t}\n\n\tconst score = (2 * intersection) / denominator;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n\n/**\n * Async variant of Sorensen-Dice coefficient for use with async graph data sources.\n *\n * Fetches both neighbourhoods concurrently, then applies the same formula.\n */\nexport async function sorensenAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): Promise<number> {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Fetch both neighbourhoods in parallel\n\tconst [sourceArr, targetArr] = await Promise.all([\n\t\tcollectAsyncIterable(graph.neighbours(source)),\n\t\tcollectAsyncIterable(graph.neighbours(target)),\n\t]);\n\n\tconst srcSet = new Set(sourceArr.filter((n) => n !== target));\n\tconst tgtSet = new Set(targetArr.filter((n) => n !== source));\n\n\t// Compute intersection size\n\tlet intersection = 0;\n\tfor (const n of srcSet) {\n\t\tif (tgtSet.has(n)) intersection++;\n\t}\n\n\t// Compute denominator: |N(u)| + |N(v)|\n\tconst denominator = srcSet.size + tgtSet.size;\n\n\t// Avoid division by zero\n\tif (denominator === 0) {\n\t\treturn 0;\n\t}\n\n\tconst score = (2 * intersection) / denominator;\n\n\treturn Math.max(epsilon, score);\n}\n","/**\n * Resource Allocation index for edge salience.\n *\n * Sum of inverse degrees of common neighbours:\n * MI(u,v) = Σ_{z ∈ N(u) ∩ N(v)} 1 / deg(z)\n *\n * Range: [0, ∞)\n * Assumption: shared neighbours with low degree are more informative than hubs.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { AsyncReadableGraph } from \"../../graph/async-interfaces\";\nimport { neighbourSet, neighbourIntersection } from \"../../utils\";\nimport { collectAsyncIterable } from \"../../async/utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute Resource Allocation index between neighbourhoods of two nodes.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration\n * @returns Resource Allocation index (normalised to [0, 1] if configured)\n */\nexport function resourceAllocation<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10, normalise = true } = config ?? {};\n\n\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute common neighbours\n\tconst commonNeighbours = neighbourIntersection(\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t);\n\n\t// Sum inverse degrees of common neighbours\n\tlet score = 0;\n\tfor (const neighbour of commonNeighbours) {\n\t\tconst degree = graph.degree(neighbour);\n\t\tif (degree > 0) {\n\t\t\tscore += 1 / degree;\n\t\t}\n\t}\n\n\t// Normalise to [0, 1] if requested\n\tif (normalise && commonNeighbours.size > 0) {\n\t\t// Max possible is when all common neighbours have minimum degree (1)\n\t\tconst maxScore = commonNeighbours.size;\n\t\tscore = score / maxScore;\n\t}\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n\n/**\n * Async variant of Resource Allocation index for use with async graph data sources.\n *\n * Fetches both neighbourhoods concurrently, then fetches degree for each common\n * neighbour to compute the inverse-degree weighted sum.\n */\nexport async function resourceAllocationAsync<\n\tN extends NodeData,\n\tE extends EdgeData,\n>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): Promise<number> {\n\tconst { epsilon = 1e-10, normalise = true } = config ?? {};\n\n\t// Fetch both neighbourhoods in parallel\n\tconst [sourceArr, targetArr] = await Promise.all([\n\t\tcollectAsyncIterable(graph.neighbours(source)),\n\t\tcollectAsyncIterable(graph.neighbours(target)),\n\t]);\n\n\tconst srcSet = new Set(sourceArr.filter((n) => n !== target));\n\tconst tgtSet = new Set(targetArr.filter((n) => n !== source));\n\n\t// Find common neighbours\n\tconst commonNeighbours: NodeId[] = [];\n\tfor (const n of srcSet) {\n\t\tif (tgtSet.has(n)) commonNeighbours.push(n);\n\t}\n\n\tif (commonNeighbours.length === 0) {\n\t\treturn epsilon;\n\t}\n\n\t// Fetch degrees of all common neighbours in parallel\n\tconst degrees = await Promise.all(\n\t\tcommonNeighbours.map((n) => graph.degree(n)),\n\t);\n\n\t// Sum inverse degrees\n\tlet score = 0;\n\tfor (const degree of degrees) {\n\t\tif (degree > 0) {\n\t\t\tscore += 1 / degree;\n\t\t}\n\t}\n\n\t// Normalise to [0, 1] if requested\n\tif (normalise) {\n\t\t// Max possible is when all common neighbours have minimum degree (1)\n\t\tconst maxScore = commonNeighbours.length;\n\t\tscore = score / maxScore;\n\t}\n\n\treturn Math.max(epsilon, score);\n}\n","/**\n * Overlap Coefficient for edge salience.\n *\n * Minimum-based neighbourhood overlap:\n * MI(u,v) = |N(u) ∩ N(v)| / min(|N(u)|, |N(v)|)\n *\n * Range: [0, 1]\n * - 0: No shared neighbours or one node has no neighbours\n * - 1: One neighbourhood is a subset of the other\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { AsyncReadableGraph } from \"../../graph/async-interfaces\";\nimport { neighbourSet, neighbourOverlap } from \"../../utils\";\nimport { collectAsyncIterable } from \"../../async/utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute Overlap Coefficient between neighbourhoods of two nodes.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration\n * @returns Overlap Coefficient in [0, 1]\n */\nexport function overlapCoefficient<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute intersection size\n\tconst { intersection } = neighbourOverlap(sourceNeighbours, targetNeighbours);\n\n\t// Compute denominator: min(|N(u)|, |N(v)|)\n\tconst denominator = Math.min(sourceNeighbours.size, targetNeighbours.size);\n\n\t// Avoid division by zero\n\tif (denominator === 0) {\n\t\treturn 0;\n\t}\n\n\tconst score = intersection / denominator;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n\n/**\n * Async variant of Overlap Coefficient for use with async graph data sources.\n *\n * Fetches both neighbourhoods concurrently, then applies the same formula.\n */\nexport async function overlapCoefficientAsync<\n\tN extends NodeData,\n\tE extends EdgeData,\n>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): Promise<number> {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Fetch both neighbourhoods in parallel\n\tconst [sourceArr, targetArr] = await Promise.all([\n\t\tcollectAsyncIterable(graph.neighbours(source)),\n\t\tcollectAsyncIterable(graph.neighbours(target)),\n\t]);\n\n\tconst srcSet = new Set(sourceArr.filter((n) => n !== target));\n\tconst tgtSet = new Set(targetArr.filter((n) => n !== source));\n\n\t// Compute intersection size\n\tlet intersection = 0;\n\tfor (const n of srcSet) {\n\t\tif (tgtSet.has(n)) intersection++;\n\t}\n\n\t// Compute denominator: min(|N(u)|, |N(v)|)\n\tconst denominator = Math.min(srcSet.size, tgtSet.size);\n\n\t// Avoid division by zero\n\tif (denominator === 0) {\n\t\treturn 0;\n\t}\n\n\tconst score = intersection / denominator;\n\n\treturn Math.max(epsilon, score);\n}\n","/**\n * Hub Promoted index for edge salience.\n *\n * Hub-promoting neighbourhood overlap:\n * MI(u,v) = |N(u) ∩ N(v)| / min(deg(u), deg(v))\n *\n * Range: [0, 1]\n * Uses node degrees rather than neighbourhood set sizes.\n * Similar to Overlap Coefficient but normalised by degree.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { AsyncReadableGraph } from \"../../graph/async-interfaces\";\nimport { neighbourSet, neighbourOverlap } from \"../../utils\";\nimport { collectAsyncIterable } from \"../../async/utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute Hub Promoted index between neighbourhoods of two nodes.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration\n * @returns Hub Promoted index in [0, 1]\n */\nexport function hubPromoted<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute intersection size\n\tconst { intersection } = neighbourOverlap(sourceNeighbours, targetNeighbours);\n\n\t// Compute denominator using actual degrees\n\tconst sourceDegree = graph.degree(source);\n\tconst targetDegree = graph.degree(target);\n\tconst denominator = Math.min(sourceDegree, targetDegree);\n\n\t// Avoid division by zero\n\tif (denominator === 0) {\n\t\treturn 0;\n\t}\n\n\tconst score = intersection / denominator;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n\n/**\n * Async variant of Hub Promoted index for use with async graph data sources.\n *\n * Fetches both neighbourhoods and degrees concurrently, then applies the same formula.\n */\nexport async function hubPromotedAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): Promise<number> {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Fetch both neighbourhoods and degrees in parallel\n\tconst [sourceArr, targetArr, sourceDegree, targetDegree] = await Promise.all([\n\t\tcollectAsyncIterable(graph.neighbours(source)),\n\t\tcollectAsyncIterable(graph.neighbours(target)),\n\t\tgraph.degree(source),\n\t\tgraph.degree(target),\n\t]);\n\n\tconst srcSet = new Set(sourceArr.filter((n) => n !== target));\n\tconst tgtSet = new Set(targetArr.filter((n) => n !== source));\n\n\t// Compute intersection size\n\tlet intersection = 0;\n\tfor (const n of srcSet) {\n\t\tif (tgtSet.has(n)) intersection++;\n\t}\n\n\t// Compute denominator using actual degrees\n\tconst denominator = Math.min(sourceDegree, targetDegree);\n\n\t// Avoid division by zero\n\tif (denominator === 0) {\n\t\treturn 0;\n\t}\n\n\tconst score = intersection / denominator;\n\n\treturn Math.max(epsilon, score);\n}\n","/**\n * SCALE (Structural Coherence via Adjacency Lattice Entropy) MI variant.\n *\n * Density-normalised Jaccard, correcting for graph density variation.\n * Formula: MI(u,v) = Jaccard(u,v) / ρ(G)\n *\n * where ρ(G) = 2 * |E| / (|V| * (|V| - 1)) for undirected graphs\n * ρ(G) = |E| / (|V| * (|V| - 1)) for directed graphs\n *\n * Range: [0, ∞) but typically scales with graph density\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { AsyncReadableGraph } from \"../../graph/async-interfaces\";\nimport { computeJaccard } from \"../../utils\";\nimport { collectAsyncIterable } from \"../../async/utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute SCALE MI between two nodes.\n */\nexport function scale<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\tconst { jaccard: jaccardScore } = computeJaccard(graph, source, target);\n\n\t// Compute graph density\n\tconst n = graph.nodeCount;\n\tconst m = graph.edgeCount;\n\n\t// ρ(G) = 2|E| / (|V|(|V|-1)) for undirected; |E| / (|V|(|V|-1)) for directed\n\tconst possibleEdges = n * (n - 1);\n\tconst density =\n\t\tpossibleEdges > 0 ? (graph.directed ? m : 2 * m) / possibleEdges : 0;\n\n\t// Avoid division by zero: if density is 0, fall back to epsilon\n\tif (density === 0) {\n\t\treturn epsilon;\n\t}\n\n\tconst score = jaccardScore / density;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n\n/**\n * Async variant of SCALE MI for use with async graph data sources.\n *\n * Fetches both neighbourhoods, node count, and edge count concurrently.\n */\nexport async function scaleAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): Promise<number> {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Fetch neighbourhoods, node count, and edge count in parallel\n\tconst [sourceArr, targetArr, n, m] = await Promise.all([\n\t\tcollectAsyncIterable(graph.neighbours(source)),\n\t\tcollectAsyncIterable(graph.neighbours(target)),\n\t\tgraph.nodeCount,\n\t\tgraph.edgeCount,\n\t]);\n\n\tconst srcSet = new Set(sourceArr.filter((node) => node !== target));\n\tconst tgtSet = new Set(targetArr.filter((node) => node !== source));\n\n\t// Compute Jaccard from sets\n\tlet intersection = 0;\n\tfor (const node of srcSet) {\n\t\tif (tgtSet.has(node)) intersection++;\n\t}\n\tconst union = srcSet.size + tgtSet.size - intersection;\n\tconst jaccardScore = union > 0 ? intersection / union : 0;\n\n\t// Compute graph density\n\t// ρ(G) = 2|E| / (|V|(|V|-1)) for undirected; |E| / (|V|(|V|-1)) for directed\n\tconst possibleEdges = n * (n - 1);\n\tconst density =\n\t\tpossibleEdges > 0 ? (graph.directed ? m : 2 * m) / possibleEdges : 0;\n\n\t// Avoid division by zero: if density is 0, fall back to epsilon\n\tif (density === 0) {\n\t\treturn epsilon;\n\t}\n\n\tconst score = jaccardScore / density;\n\n\treturn Math.max(epsilon, score);\n}\n","/**\n * SKEW (Structural Kernel Entropy Weighting) MI variant.\n *\n * IDF-style rarity weighting on endpoints, applied to Jaccard base.\n * Formula: MI(u,v) = Jaccard(u,v) * log(N/deg(u)+1) * log(N/deg(v)+1)\n *\n * Range: [0, ∞) but typically [0, 1] for well-connected graphs\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { AsyncReadableGraph } from \"../../graph/async-interfaces\";\nimport { computeJaccard } from \"../../utils\";\nimport { collectAsyncIterable } from \"../../async/utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute SKEW MI between two nodes.\n */\nexport function skew<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\tconst { jaccard: jaccardScore } = computeJaccard(graph, source, target);\n\n\t// Compute IDF-style weights for endpoints\n\tconst N = graph.nodeCount;\n\tconst sourceDegree = graph.degree(source);\n\tconst targetDegree = graph.degree(target);\n\n\tconst sourceIdf = Math.log(N / (sourceDegree + 1));\n\tconst targetIdf = Math.log(N / (targetDegree + 1));\n\n\tconst score = jaccardScore * sourceIdf * targetIdf;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n\n/**\n * Async variant of SKEW MI for use with async graph data sources.\n *\n * Fetches both neighbourhoods, degrees, and node count concurrently.\n */\nexport async function skewAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): Promise<number> {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Fetch neighbourhoods, node count, and endpoint degrees in parallel\n\tconst [sourceArr, targetArr, N, sourceDegree, targetDegree] =\n\t\tawait Promise.all([\n\t\t\tcollectAsyncIterable(graph.neighbours(source)),\n\t\t\tcollectAsyncIterable(graph.neighbours(target)),\n\t\t\tgraph.nodeCount,\n\t\t\tgraph.degree(source),\n\t\t\tgraph.degree(target),\n\t\t]);\n\n\tconst srcSet = new Set(sourceArr.filter((n) => n !== target));\n\tconst tgtSet = new Set(targetArr.filter((n) => n !== source));\n\n\t// Compute Jaccard from sets\n\tlet intersection = 0;\n\tfor (const n of srcSet) {\n\t\tif (tgtSet.has(n)) intersection++;\n\t}\n\tconst union = srcSet.size + tgtSet.size - intersection;\n\tconst jaccardScore = union > 0 ? intersection / union : 0;\n\n\t// Compute IDF-style weights for endpoints\n\tconst sourceIdf = Math.log(N / (sourceDegree + 1));\n\tconst targetIdf = Math.log(N / (targetDegree + 1));\n\n\tconst score = jaccardScore * sourceIdf * targetIdf;\n\n\treturn Math.max(epsilon, score);\n}\n","/**\n * SPAN (Structural Pattern ANalysis) MI variant.\n *\n * Clustering-coefficient penalty, favouring bridge edges.\n * Formula: MI(u,v) = Jaccard(u,v) * (1 - max(cc(u), cc(v)))\n *\n * Nodes with high clustering coefficient are tightly embedded in triangles;\n * edges between such nodes are less likely to be bridge edges. This variant\n * downweights such edges, favouring paths through bridge edges.\n *\n * Range: [0, 1]\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { AsyncReadableGraph } from \"../../graph/async-interfaces\";\nimport { computeJaccard } from \"../../utils\";\nimport { localClusteringCoefficient } from \"../../utils\";\nimport { collectAsyncIterable } from \"../../async/utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute SPAN MI between two nodes.\n */\nexport function span<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\tconst { jaccard: jaccardScore } = computeJaccard(graph, source, target);\n\n\t// Compute clustering coefficients\n\tconst sourceCc = localClusteringCoefficient(graph, source);\n\tconst targetCc = localClusteringCoefficient(graph, target);\n\n\t// Apply bridge penalty: downweight edges between highly-embedded nodes\n\tconst bridgePenalty = 1 - Math.max(sourceCc, targetCc);\n\n\tconst score = jaccardScore * bridgePenalty;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n\n/**\n * Async variant of SPAN MI for use with async graph data sources.\n *\n * Fetches both neighbourhoods concurrently, then computes the clustering\n * coefficient for each endpoint from the collected neighbour arrays.\n */\nexport async function spanAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): Promise<number> {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Fetch both neighbourhoods in parallel\n\tconst [sourceArr, targetArr] = await Promise.all([\n\t\tcollectAsyncIterable(graph.neighbours(source)),\n\t\tcollectAsyncIterable(graph.neighbours(target)),\n\t]);\n\n\tconst srcSet = new Set(sourceArr.filter((n) => n !== target));\n\tconst tgtSet = new Set(targetArr.filter((n) => n !== source));\n\n\t// Compute Jaccard from sets\n\tlet intersection = 0;\n\tfor (const n of srcSet) {\n\t\tif (tgtSet.has(n)) intersection++;\n\t}\n\tconst union = srcSet.size + tgtSet.size - intersection;\n\tconst jaccardScore = union > 0 ? intersection / union : 0;\n\n\t// Compute clustering coefficients for source and target asynchronously.\n\t// CC(v) = 2 * |triangles| / (deg * (deg - 1))\n\t// We need to check which pairs of neighbours are connected.\n\tconst computeClusteringCoefficient = async (\n\t\tnodeId: NodeId,\n\t\tneighbourArr: readonly NodeId[],\n\t): Promise<number> => {\n\t\tconst degree = neighbourArr.length;\n\t\tif (degree < 2) return 0;\n\n\t\t// Check each pair of neighbours for connectivity in parallel\n\t\tconst pairs: [NodeId, NodeId][] = [];\n\t\tfor (let i = 0; i < neighbourArr.length; i++) {\n\t\t\tfor (let j = i + 1; j < neighbourArr.length; j++) {\n\t\t\t\tconst u = neighbourArr[i];\n\t\t\t\tconst v = neighbourArr[j];\n\t\t\t\tif (u !== undefined && v !== undefined) {\n\t\t\t\t\tpairs.push([u, v]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst edgeResults = await Promise.all(\n\t\t\tpairs.flatMap(([u, v]) => [graph.getEdge(u, v), graph.getEdge(v, u)]),\n\t\t);\n\n\t\tlet triangleCount = 0;\n\t\tfor (let i = 0; i < pairs.length; i++) {\n\t\t\t// Each pair produced two edge lookups at indices 2*i and 2*i+1\n\t\t\tif (\n\t\t\t\tedgeResults[2 * i] !== undefined ||\n\t\t\t\tedgeResults[2 * i + 1] !== undefined\n\t\t\t) {\n\t\t\t\ttriangleCount++;\n\t\t\t}\n\t\t}\n\n\t\tconst possibleTriangles = (degree * (degree - 1)) / 2;\n\t\treturn triangleCount / possibleTriangles;\n\t};\n\n\tconst [sourceCc, targetCc] = await Promise.all([\n\t\tcomputeClusteringCoefficient(source, sourceArr),\n\t\tcomputeClusteringCoefficient(target, targetArr),\n\t]);\n\n\t// Apply bridge penalty: downweight edges between highly-embedded nodes\n\tconst bridgePenalty = 1 - Math.max(sourceCc, targetCc);\n\n\tconst score = jaccardScore * bridgePenalty;\n\n\treturn Math.max(epsilon, score);\n}\n","/**\n * ETCH (Edge Topology Coherence via Homophily) MI variant.\n *\n * Edge-type rarity weighting applied to Jaccard base.\n * Formula: MI(u,v) = Jaccard(u,v) * rarity(edgeType(u,v))\n * where rarity(t) = log(|E| / count(edges with type t))\n *\n * Edges of rare types (fewer instances in the graph) receive higher salience,\n * making discoveries across unusual edge relationships more significant.\n *\n * Range: [0, ∞) but typically [0, 1] for well-typed edges\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { AsyncReadableGraph } from \"../../graph/async-interfaces\";\nimport { computeJaccard, countEdgesOfType } from \"../../utils\";\nimport { collectAsyncIterable } from \"../../async/utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute ETCH MI between two nodes.\n */\nexport function etch<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\tconst { jaccard: jaccardScore } = computeJaccard(graph, source, target);\n\n\t// Get edge between source and target\n\tconst edge = graph.getEdge(source, target);\n\n\t// If edge has no type or doesn't exist, fall back to Jaccard\n\tif (edge?.type === undefined) {\n\t\treturn Math.max(epsilon, jaccardScore);\n\t}\n\n\t// Compute edge rarity: log(total edges / edges of this type)\n\tconst edgeTypeCount = countEdgesOfType(graph, edge.type);\n\n\t// Avoid division by zero\n\tif (edgeTypeCount === 0) {\n\t\treturn Math.max(epsilon, jaccardScore);\n\t}\n\n\tconst rarity = Math.log(graph.edgeCount / edgeTypeCount);\n\tconst score = jaccardScore * rarity;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n\n/**\n * Async variant of ETCH MI for use with async graph data sources.\n *\n * Fetches both neighbourhoods and edge data concurrently, then counts\n * edges of the same type by iterating the async edge stream.\n */\nexport async function etchAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): Promise<number> {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Fetch neighbourhoods and the source→target edge concurrently\n\tconst [sourceArr, targetArr, edge] = await Promise.all([\n\t\tcollectAsyncIterable(graph.neighbours(source)),\n\t\tcollectAsyncIterable(graph.neighbours(target)),\n\t\tgraph.getEdge(source, target),\n\t]);\n\n\tconst srcSet = new Set(sourceArr.filter((n) => n !== target));\n\tconst tgtSet = new Set(targetArr.filter((n) => n !== source));\n\n\t// Compute Jaccard from sets\n\tlet intersection = 0;\n\tfor (const n of srcSet) {\n\t\tif (tgtSet.has(n)) intersection++;\n\t}\n\tconst union = srcSet.size + tgtSet.size - intersection;\n\tconst jaccardScore = union > 0 ? intersection / union : 0;\n\n\t// If edge has no type or doesn't exist, fall back to Jaccard\n\tif (edge?.type === undefined) {\n\t\treturn Math.max(epsilon, jaccardScore);\n\t}\n\n\tconst edgeType = edge.type;\n\n\t// Count edges of this type by iterating all edges\n\tlet edgeTypeCount = 0;\n\tlet totalEdges = 0;\n\tfor await (const e of graph.edges()) {\n\t\ttotalEdges++;\n\t\tif (e.type === edgeType) edgeTypeCount++;\n\t}\n\n\t// Avoid division by zero\n\tif (edgeTypeCount === 0) {\n\t\treturn Math.max(epsilon, jaccardScore);\n\t}\n\n\tconst rarity = Math.log(totalEdges / edgeTypeCount);\n\tconst score = jaccardScore * rarity;\n\n\treturn Math.max(epsilon, score);\n}\n","/**\n * NOTCH (Neighbourhood Overlap Topology Coherence via Homophily) MI variant.\n *\n * Node-type rarity weighting applied to Jaccard base.\n * Formula: MI(u,v) = Jaccard(u,v) * rarity(nodeType(u)) * rarity(nodeType(v))\n * where rarity(t) = log(|V| / count(nodes with type t))\n *\n * Paths connecting nodes of rare types (fewer instances in the graph) receive higher\n * salience, making discoveries involving unusual node types more significant.\n *\n * Range: [0, ∞) but typically [0, 1] for well-typed nodes\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { AsyncReadableGraph } from \"../../graph/async-interfaces\";\nimport { computeJaccard, countNodesOfType } from \"../../utils\";\nimport { collectAsyncIterable } from \"../../async/utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute NOTCH MI between two nodes.\n */\nexport function notch<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\tconst { jaccard: jaccardScore } = computeJaccard(graph, source, target);\n\n\t// Get node data\n\tconst sourceNode = graph.getNode(source);\n\tconst targetNode = graph.getNode(target);\n\n\t// If either node lacks a type, fall back to Jaccard\n\tif (sourceNode?.type === undefined || targetNode?.type === undefined) {\n\t\treturn Math.max(epsilon, jaccardScore);\n\t}\n\n\t// Compute node rarity: log(total nodes / nodes of this type)\n\tconst sourceTypeCount = countNodesOfType(graph, sourceNode.type);\n\tconst targetTypeCount = countNodesOfType(graph, targetNode.type);\n\n\t// Avoid division by zero\n\tif (sourceTypeCount === 0 || targetTypeCount === 0) {\n\t\treturn Math.max(epsilon, jaccardScore);\n\t}\n\n\tconst sourceRarity = Math.log(graph.nodeCount / sourceTypeCount);\n\tconst targetRarity = Math.log(graph.nodeCount / targetTypeCount);\n\n\tconst score = jaccardScore * sourceRarity * targetRarity;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\n}\n\n/**\n * Async variant of NOTCH MI for use with async graph data sources.\n *\n * Fetches both neighbourhoods and node data concurrently, then counts\n * nodes of each type by iterating the async node stream.\n */\nexport async function notchAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): Promise<number> {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Fetch neighbourhoods and node data concurrently\n\tconst [sourceArr, targetArr, sourceNode, targetNode] = await Promise.all([\n\t\tcollectAsyncIterable(graph.neighbours(source)),\n\t\tcollectAsyncIterable(graph.neighbours(target)),\n\t\tgraph.getNode(source),\n\t\tgraph.getNode(target),\n\t]);\n\n\tconst srcSet = new Set(sourceArr.filter((n) => n !== target));\n\tconst tgtSet = new Set(targetArr.filter((n) => n !== source));\n\n\t// Compute Jaccard from sets\n\tlet intersection = 0;\n\tfor (const n of srcSet) {\n\t\tif (tgtSet.has(n)) intersection++;\n\t}\n\tconst union = srcSet.size + tgtSet.size - intersection;\n\tconst jaccardScore = union > 0 ? intersection / union : 0;\n\n\t// If either node lacks a type, fall back to Jaccard\n\tif (sourceNode?.type === undefined || targetNode?.type === undefined) {\n\t\treturn Math.max(epsilon, jaccardScore);\n\t}\n\n\tconst sourceType = sourceNode.type;\n\tconst targetType = targetNode.type;\n\n\t// Count nodes of each type and total nodes by iterating all node IDs\n\tlet totalNodes = 0;\n\tlet sourceTypeCount = 0;\n\tlet targetTypeCount = 0;\n\tfor await (const nodeId of graph.nodeIds()) {\n\t\ttotalNodes++;\n\t\tconst node = await graph.getNode(nodeId);\n\t\tif (node?.type === sourceType) sourceTypeCount++;\n\t\tif (node?.type === targetType) targetTypeCount++;\n\t}\n\n\t// Avoid division by zero\n\tif (sourceTypeCount === 0 || targetTypeCount === 0) {\n\t\treturn Math.max(epsilon, jaccardScore);\n\t}\n\n\tconst sourceRarity = Math.log(totalNodes / sourceTypeCount);\n\tconst targetRarity = Math.log(totalNodes / targetTypeCount);\n\n\tconst score = jaccardScore * sourceRarity * targetRarity;\n\n\treturn Math.max(epsilon, score);\n}\n","/**\n * Unified Adaptive MI - combines multiple MI signals dynamically.\n *\n * Adapts to graph structure by weighting different MI components\n * based on structural properties.\n *\n * Three-component weighted sum:\n * - Structural: Jaccard neighbourhood overlap\n * - Degree: Adamic-Adar inverse-log-degree weighting\n * - Overlap: Overlap coefficient (intersection / min degree)\n *\n * Range: [0, 1] - higher values indicate stronger association\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { AsyncReadableGraph } from \"../../graph/async-interfaces\";\nimport { computeJaccard, neighbourOverlap } from \"../../utils\";\nimport { collectAsyncIterable } from \"../../async/utils\";\nimport type { AdaptiveMIConfig } from \"./types\";\nimport { adamicAdar } from \"./adamic-adar\";\nimport { adamicAdarAsync } from \"./adamic-adar\";\n\n/**\n * Compute unified adaptive MI between two connected nodes.\n *\n * Combines structural, degree, and overlap signals with\n * configurable weighting.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration with component weights\n * @returns Adaptive MI score in [0, 1]\n */\nexport function adaptive<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: AdaptiveMIConfig,\n): number {\n\tconst {\n\t\tepsilon = 1e-10,\n\t\tstructuralWeight = 0.4,\n\t\tdegreeWeight = 0.3,\n\t\toverlapWeight = 0.3,\n\t} = config ?? {};\n\n\t// Compute Jaccard and retrieve neighbourhood sets for the overlap coefficient\n\tconst {\n\t\tjaccard: jaccardScore,\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t} = computeJaccard(graph, source, target);\n\n\t// Component 1: Structural similarity (Jaccard)\n\t// Returns 0 only when both neighbourhood sets are empty (union = 0); otherwise applies epsilon floor\n\tconst structural =\n\t\tsourceNeighbours.size === 0 && targetNeighbours.size === 0\n\t\t\t? 0\n\t\t\t: Math.max(epsilon, jaccardScore);\n\n\t// Component 2: Degree-weighted association (Adamic-Adar, normalised)\n\tconst degreeComponent = adamicAdar(graph, source, target, {\n\t\tepsilon,\n\t\tnormalise: true,\n\t});\n\n\t// Component 3: Overlap coefficient\n\tlet overlap: number;\n\tif (sourceNeighbours.size > 0 && targetNeighbours.size > 0) {\n\t\tconst { intersection } = neighbourOverlap(\n\t\t\tsourceNeighbours,\n\t\t\ttargetNeighbours,\n\t\t);\n\t\tconst minDegree = Math.min(sourceNeighbours.size, targetNeighbours.size);\n\t\toverlap = minDegree > 0 ? intersection / minDegree : epsilon;\n\t} else {\n\t\toverlap = epsilon;\n\t}\n\n\t// Normalise weights\n\tconst totalWeight = structuralWeight + degreeWeight + overlapWeight;\n\n\t// Weighted combination\n\tconst score =\n\t\t(structuralWeight * structural +\n\t\t\tdegreeWeight * degreeComponent +\n\t\t\toverlapWeight * overlap) /\n\t\ttotalWeight;\n\n\treturn Math.max(epsilon, Math.min(1, score));\n}\n\n/**\n * Async variant of Adaptive MI for use with async graph data sources.\n *\n * Fetches both neighbourhoods concurrently, then delegates degree-weighted\n * component to the async Adamic-Adar variant.\n */\nexport async function adaptiveAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: AdaptiveMIConfig,\n): Promise<number> {\n\tconst {\n\t\tepsilon = 1e-10,\n\t\tstructuralWeight = 0.4,\n\t\tdegreeWeight = 0.3,\n\t\toverlapWeight = 0.3,\n\t} = config ?? {};\n\n\t// Fetch both neighbourhoods in parallel alongside the Adamic-Adar component\n\tconst [sourceArr, targetArr, degreeComponent] = await Promise.all([\n\t\tcollectAsyncIterable(graph.neighbours(source)),\n\t\tcollectAsyncIterable(graph.neighbours(target)),\n\t\tadamicAdarAsync(graph, source, target, { epsilon, normalise: true }),\n\t]);\n\n\tconst srcSet = new Set(sourceArr.filter((n) => n !== target));\n\tconst tgtSet = new Set(targetArr.filter((n) => n !== source));\n\n\t// Compute Jaccard from sets\n\tlet intersection = 0;\n\tfor (const n of srcSet) {\n\t\tif (tgtSet.has(n)) intersection++;\n\t}\n\tconst union = srcSet.size + tgtSet.size - intersection;\n\tconst jaccardScore = union > 0 ? intersection / union : 0;\n\n\t// Component 1: Structural similarity (Jaccard)\n\tconst structural =\n\t\tsrcSet.size === 0 && tgtSet.size === 0\n\t\t\t? 0\n\t\t\t: Math.max(epsilon, jaccardScore);\n\n\t// Component 3: Overlap coefficient\n\tlet overlap: number;\n\tif (srcSet.size > 0 && tgtSet.size > 0) {\n\t\tconst minDegree = Math.min(srcSet.size, tgtSet.size);\n\t\toverlap = minDegree > 0 ? intersection / minDegree : epsilon;\n\t} else {\n\t\toverlap = epsilon;\n\t}\n\n\t// Normalise weights\n\tconst totalWeight = structuralWeight + degreeWeight + overlapWeight;\n\n\t// Weighted combination\n\tconst score =\n\t\t(structuralWeight * structural +\n\t\t\tdegreeWeight * degreeComponent +\n\t\t\toverlapWeight * overlap) /\n\t\ttotalWeight;\n\n\treturn Math.max(epsilon, Math.min(1, score));\n}\n","/**\n * Shared utilities for baseline ranking methods.\n *\n * @packageDocumentation\n */\n\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineResult, ScoredPath } from \"./types\";\n\n/**\n * Normalise a set of scored paths and return them sorted highest-first.\n *\n * All scores are normalised relative to the maximum observed score.\n * When `includeScores` is false, raw (un-normalised) scores are preserved.\n * Handles degenerate cases: empty input and all-zero scores.\n *\n * @param paths - Original paths in input order\n * @param scored - Paths paired with their computed scores\n * @param method - Method name to embed in the result\n * @param includeScores - When true, normalise scores to [0, 1]; when false, keep raw scores\n * @returns BaselineResult with ranked paths\n */\nexport function normaliseAndRank(\n\tpaths: readonly ExpansionPath[],\n\tscored: readonly { readonly path: ExpansionPath; readonly score: number }[],\n\tmethod: string,\n\tincludeScores: boolean,\n): BaselineResult {\n\tif (scored.length === 0) {\n\t\treturn { paths: [], method };\n\t}\n\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case: all paths get score 0\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({ ...path, score: 0 })),\n\t\t\tmethod,\n\t\t};\n\t}\n\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn { paths: ranked, method };\n}\n","/**\n * Shortest path baseline ranking.\n *\n * Ranks paths by length (shorter = higher score).\n * Score = 1 / length (normalised).\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Rank paths by length (shortest first).\n *\n * Score = 1 / path_length, normalised to [0, 1].\n *\n * @param _graph - Source graph (unused for length ranking)\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (shortest first)\n */\nexport function shortest<N extends NodeData, E extends EdgeData>(\n\t_graph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"shortest\",\n\t\t};\n\t}\n\n\t// Compute raw scores (1 / length)\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map(\n\t\t(path) => ({\n\t\t\tpath,\n\t\t\tscore: 1 / path.nodes.length,\n\t\t}),\n\t);\n\n\treturn normaliseAndRank(paths, scored, \"shortest\", includeScores);\n}\n","/**\n * Degree-Sum baseline ranking.\n *\n * Ranks paths by sum of node degrees.\n * Higher degree nodes may indicate more connected (central) nodes.\n * Score = sum(deg(v) for v in path), normalised to [0, 1].\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Rank paths by sum of node degrees.\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest degree-sum first)\n */\nexport function degreeSum<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"degree-sum\",\n\t\t};\n\t}\n\n\t// Compute raw scores (sum of degrees)\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tlet degreeSum = 0;\n\t\tfor (const nodeId of path.nodes) {\n\t\t\tdegreeSum += graph.degree(nodeId);\n\t\t}\n\t\treturn { path, score: degreeSum };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"degree-sum\", includeScores);\n}\n","/**\n * Widest-Path baseline ranking.\n *\n * Ranks paths by bottleneck similarity (minimum edge salience).\n * Uses Jaccard similarity as the edge salience metric.\n * Score = min(jaccard(u, v) for each edge (u,v) in path).\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { jaccard } from \"../mi/jaccard\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Rank paths by widest bottleneck (minimum edge similarity).\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest bottleneck first)\n */\nexport function widestPath<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"widest-path\",\n\t\t};\n\t}\n\n\t// Compute raw scores (minimum edge similarity per path)\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tif (path.nodes.length < 2) {\n\t\t\t// Single-node path: no edges\n\t\t\treturn { path, score: 1 };\n\t\t}\n\n\t\tlet minSimilarity = Number.POSITIVE_INFINITY;\n\t\tfor (let i = 0; i < path.nodes.length - 1; i++) {\n\t\t\tconst source = path.nodes[i];\n\t\t\tconst target = path.nodes[i + 1];\n\t\t\tif (source === undefined || target === undefined) continue;\n\n\t\t\tconst edgeSimilarity = jaccard(graph, source, target);\n\t\t\tminSimilarity = Math.min(minSimilarity, edgeSimilarity);\n\t\t}\n\n\t\t// If no edges were found, default to 1\n\t\tconst score =\n\t\t\tminSimilarity === Number.POSITIVE_INFINITY ? 1 : minSimilarity;\n\t\treturn { path, score };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"widest-path\", includeScores);\n}\n","/**\n * Jaccard-Arithmetic baseline ranking.\n *\n * Ranks paths by arithmetic mean of edge Jaccard similarities.\n * Contrast with PARSE which uses geometric mean.\n * Score = (1/k) * sum(jaccard(u, v) for each edge (u,v)).\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { jaccard } from \"../mi/jaccard\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Rank paths by arithmetic mean of edge Jaccard similarities.\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest arithmetic mean first)\n */\nexport function jaccardArithmetic<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"jaccard-arithmetic\",\n\t\t};\n\t}\n\n\t// Compute raw scores (arithmetic mean of edge similarities)\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tif (path.nodes.length < 2) {\n\t\t\t// Single-node path: no edges, score = 1\n\t\t\treturn { path, score: 1 };\n\t\t}\n\n\t\tlet similaritySum = 0;\n\t\tlet edgeCount = 0;\n\n\t\tfor (let i = 0; i < path.nodes.length - 1; i++) {\n\t\t\tconst source = path.nodes[i];\n\t\t\tconst target = path.nodes[i + 1];\n\t\t\tif (source === undefined || target === undefined) continue;\n\n\t\t\tconst edgeSimilarity = jaccard(graph, source, target);\n\t\t\tsimilaritySum += edgeSimilarity;\n\t\t\tedgeCount++;\n\t\t}\n\n\t\t// Arithmetic mean\n\t\tconst score = edgeCount > 0 ? similaritySum / edgeCount : 1;\n\t\treturn { path, score };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"jaccard-arithmetic\", includeScores);\n}\n","/**\n * PageRank baseline ranking.\n *\n * Computes PageRank centrality for all nodes, then sums PR values per path.\n * Uses power iteration: r(v) = (1-d)/N + d * sum(r(u)/deg_out(u) for u->v)\n * Parameters: d=0.85 (damping factor), tolerance=1e-6, max 100 iterations.\n * Score = sum(pagerank(v) for v in path), normalised to [0, 1].\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Compute PageRank centrality for all nodes using power iteration.\n *\n * @param graph - Source graph\n * @param damping - Damping factor (default 0.85)\n * @param tolerance - Convergence tolerance (default 1e-6)\n * @param maxIterations - Maximum iterations (default 100)\n * @returns Map of node ID to PageRank value\n */\nfunction computePageRank<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tdamping = 0.85,\n\ttolerance = 1e-6,\n\tmaxIterations = 100,\n): Map<string, number> {\n\tconst nodes = Array.from(graph.nodeIds());\n\tconst n = nodes.length;\n\n\tif (n === 0) {\n\t\treturn new Map();\n\t}\n\n\t// Initialise ranks uniformly\n\tconst ranks = new Map<string, number>();\n\tconst newRanks = new Map<string, number>();\n\tfor (const nodeId of nodes) {\n\t\tranks.set(nodeId, 1 / n);\n\t\tnewRanks.set(nodeId, 0);\n\t}\n\n\t// Power iteration\n\tlet isCurrentRanks = true; // Track which map is current\n\n\tfor (let iteration = 0; iteration < maxIterations; iteration++) {\n\t\tlet maxChange = 0;\n\t\tconst currMap = isCurrentRanks ? ranks : newRanks;\n\t\tconst nextMap = isCurrentRanks ? newRanks : ranks;\n\n\t\tfor (const nodeId of nodes) {\n\t\t\t// Sum contributions from incoming neighbours\n\t\t\tlet incomingSum = 0;\n\n\t\t\tfor (const incomingId of graph.neighbours(nodeId, \"in\")) {\n\t\t\t\tconst incomingRank = currMap.get(incomingId) ?? 0;\n\t\t\t\tconst outDegree = graph.degree(incomingId);\n\t\t\t\tif (outDegree > 0) {\n\t\t\t\t\tincomingSum += incomingRank / outDegree;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// PageRank formula\n\t\t\tconst newRank = (1 - damping) / n + damping * incomingSum;\n\t\t\tnextMap.set(nodeId, newRank);\n\n\t\t\t// Track convergence\n\t\t\tconst oldRank = currMap.get(nodeId) ?? 0;\n\t\t\tmaxChange = Math.max(maxChange, Math.abs(newRank - oldRank));\n\t\t}\n\n\t\t// Check convergence before swapping\n\t\tif (maxChange < tolerance) {\n\t\t\tbreak;\n\t\t}\n\n\t\t// Swap buffers and clear the old current map for next iteration\n\t\tisCurrentRanks = !isCurrentRanks;\n\t\tcurrMap.clear();\n\t}\n\n\treturn isCurrentRanks ? ranks : newRanks;\n}\n\n/**\n * Rank paths by sum of PageRank scores.\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest PageRank sum first)\n */\nexport function pagerank<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"pagerank\",\n\t\t};\n\t}\n\n\t// Compute PageRank\n\tconst ranks = computePageRank(graph);\n\n\t// Score paths by sum of node ranks\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tlet prSum = 0;\n\t\tfor (const nodeId of path.nodes) {\n\t\t\tprSum += ranks.get(nodeId) ?? 0;\n\t\t}\n\t\treturn { path, score: prSum };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"pagerank\", includeScores);\n}\n","/**\n * Betweenness baseline ranking.\n *\n * Computes betweenness centrality using Brandes algorithm O(|V||E|).\n * Score = sum(betweenness(v) for v in path), normalised to [0, 1].\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Compute betweenness centrality for all nodes using Brandes algorithm.\n *\n * @param graph - Source graph\n * @returns Map of node ID to betweenness value\n */\nfunction computeBetweenness<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n): Map<string, number> {\n\tconst nodes = Array.from(graph.nodeIds());\n\tconst betweenness = new Map<string, number>();\n\n\t// Initialise all betweenness to 0\n\tfor (const nodeId of nodes) {\n\t\tbetweenness.set(nodeId, 0);\n\t}\n\n\t// For each node as source\n\tfor (const source of nodes) {\n\t\t// BFS to find shortest paths\n\t\tconst predecessors = new Map<string, string[]>();\n\t\tconst distance = new Map<string, number>();\n\t\tconst sigma = new Map<string, number>();\n\t\tconst queue: string[] = [];\n\n\t\t// Initialise\n\t\tfor (const nodeId of nodes) {\n\t\t\tpredecessors.set(nodeId, []);\n\t\t\tdistance.set(nodeId, -1);\n\t\t\tsigma.set(nodeId, 0);\n\t\t}\n\n\t\tdistance.set(source, 0);\n\t\tsigma.set(source, 1);\n\t\tqueue.push(source);\n\n\t\t// BFS\n\t\tfor (const v of queue) {\n\t\t\tconst vDist = distance.get(v) ?? -1;\n\t\t\tconst neighbours = graph.neighbours(v);\n\n\t\t\tfor (const w of neighbours) {\n\t\t\t\tconst wDist = distance.get(w) ?? -1;\n\n\t\t\t\t// First time seeing w?\n\t\t\t\tif (wDist < 0) {\n\t\t\t\t\tdistance.set(w, vDist + 1);\n\t\t\t\t\tqueue.push(w);\n\t\t\t\t}\n\n\t\t\t\t// Shortest path to w through v?\n\t\t\t\tif (wDist === vDist + 1) {\n\t\t\t\t\tconst wSigma = sigma.get(w) ?? 0;\n\t\t\t\t\tconst vSigma = sigma.get(v) ?? 0;\n\t\t\t\t\tsigma.set(w, wSigma + vSigma);\n\n\t\t\t\t\tconst wPred = predecessors.get(w) ?? [];\n\t\t\t\t\twPred.push(v);\n\t\t\t\t\tpredecessors.set(w, wPred);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Accumulation\n\t\tconst delta = new Map<string, number>();\n\t\tfor (const nodeId of nodes) {\n\t\t\tdelta.set(nodeId, 0);\n\t\t}\n\n\t\t// Process in reverse order of distance\n\t\tconst sorted = [...nodes].sort((a, b) => {\n\t\t\tconst aD = distance.get(a) ?? -1;\n\t\t\tconst bD = distance.get(b) ?? -1;\n\t\t\treturn bD - aD;\n\t\t});\n\n\t\tfor (const w of sorted) {\n\t\t\tif (w === source) continue;\n\n\t\t\tconst wDelta = delta.get(w) ?? 0;\n\t\t\tconst wSigma = sigma.get(w) ?? 0;\n\n\t\t\tconst wPred = predecessors.get(w) ?? [];\n\t\t\tfor (const v of wPred) {\n\t\t\t\tconst vSigma = sigma.get(v) ?? 0;\n\t\t\t\tconst vDelta = delta.get(v) ?? 0;\n\n\t\t\t\tif (wSigma > 0) {\n\t\t\t\t\tdelta.set(v, vDelta + (vSigma / wSigma) * (1 + wDelta));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (w !== source) {\n\t\t\t\tconst current = betweenness.get(w) ?? 0;\n\t\t\t\tbetweenness.set(w, current + wDelta);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn betweenness;\n}\n\n/**\n * Rank paths by sum of betweenness scores.\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest betweenness sum first)\n */\nexport function betweenness<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"betweenness\",\n\t\t};\n\t}\n\n\t// Compute betweenness\n\tconst bcMap = computeBetweenness(graph);\n\n\t// Score paths by sum of betweenness\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tlet bcSum = 0;\n\t\tfor (const nodeId of path.nodes) {\n\t\t\tbcSum += bcMap.get(nodeId) ?? 0;\n\t\t}\n\t\treturn { path, score: bcSum };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"betweenness\", includeScores);\n}\n","/**\n * Katz baseline ranking.\n *\n * Truncated Katz centrality: score(s,t) = sum_{k=1}^{K} beta^k * walks_k(s,t)\n * Parameters: K=5 (truncation depth), beta=0.005 (safe damping < 1/lambda_1).\n * For path scoring: score(P) = katz(P.start, P.end), normalised to [0, 1].\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Compute truncated Katz centrality between two nodes.\n *\n * Uses iterative matrix-vector products to avoid full matrix powers.\n * score(s,t) = sum_{k=1}^{K} beta^k * walks_k(s,t)\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param k - Truncation depth (default 5)\n * @param beta - Attenuation factor (default 0.005)\n * @returns Katz score\n */\nfunction computeKatz<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: string,\n\ttarget: string,\n\tk = 5,\n\tbeta = 0.005,\n): number {\n\tconst nodes = Array.from(graph.nodeIds());\n\tconst nodeToIdx = new Map<string, number>();\n\tnodes.forEach((nodeId, idx) => {\n\t\tnodeToIdx.set(nodeId, idx);\n\t});\n\n\tconst n = nodes.length;\n\tif (n === 0) {\n\t\treturn 0;\n\t}\n\n\tconst sourceIdx = nodeToIdx.get(source);\n\tconst targetIdx = nodeToIdx.get(target);\n\n\tif (sourceIdx === undefined || targetIdx === undefined) {\n\t\treturn 0;\n\t}\n\n\t// Current column of A^depth (number of walks of length depth from each node to target)\n\tlet walks = new Float64Array(n);\n\twalks[targetIdx] = 1; // Base case: walks[target] = 1\n\n\tlet katzScore = 0;\n\n\t// Iterate from depth 1 to k\n\tfor (let depth = 1; depth <= k; depth++) {\n\t\t// Multiply by adjacency matrix: walks_next[i] = sum_j A[i,j] * walks[j]\n\t\tconst walksNext = new Float64Array(n);\n\n\t\tfor (const sourceNode of nodes) {\n\t\t\tconst srcIdx = nodeToIdx.get(sourceNode);\n\t\t\tif (srcIdx === undefined) continue;\n\n\t\t\tconst neighbours = graph.neighbours(sourceNode);\n\t\t\tfor (const neighbourId of neighbours) {\n\t\t\t\tconst nIdx = nodeToIdx.get(neighbourId);\n\t\t\t\tif (nIdx === undefined) continue;\n\n\t\t\t\twalksNext[srcIdx] = (walksNext[srcIdx] ?? 0) + (walks[nIdx] ?? 0);\n\t\t\t}\n\t\t}\n\n\t\t// Add contribution: beta^depth * walks_depth[source]\n\t\tconst walkCount = walksNext[sourceIdx] ?? 0;\n\t\tkatzScore += Math.pow(beta, depth) * walkCount;\n\n\t\twalks = walksNext;\n\t}\n\n\treturn katzScore;\n}\n\n/**\n * Rank paths by Katz centrality between endpoints.\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest Katz score first)\n */\nexport function katz<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"katz\",\n\t\t};\n\t}\n\n\t// Score paths by Katz between endpoints\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tconst source = path.nodes[0];\n\t\tconst target = path.nodes[path.nodes.length - 1];\n\n\t\tif (source === undefined || target === undefined) {\n\t\t\treturn { path, score: 0 };\n\t\t}\n\n\t\tconst katzScore = computeKatz(graph, source, target);\n\t\treturn { path, score: katzScore };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"katz\", includeScores);\n}\n","/**\n * Communicability baseline ranking.\n *\n * Computes communicability between nodes using truncated Taylor series.\n * (e^A)_{s,t} ≈ sum_{k=0}^{15} A^k_{s,t} / k!\n * For path scoring: score(P) = communicability(P.start, P.end), normalised to [0, 1].\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Compute truncated communicability between two nodes.\n *\n * Uses Taylor series expansion: (e^A)_{s,t} ≈ sum_{k=0}^{K} A^k_{s,t} / k!\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param k - Truncation depth (default 15)\n * @returns Communicability score\n */\nfunction computeCommunicability<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: string,\n\ttarget: string,\n\tk = 15,\n): number {\n\tconst nodes = Array.from(graph.nodeIds());\n\tconst nodeToIdx = new Map<string, number>();\n\tnodes.forEach((nodeId, idx) => {\n\t\tnodeToIdx.set(nodeId, idx);\n\t});\n\n\tconst n = nodes.length;\n\tif (n === 0) {\n\t\treturn 0;\n\t}\n\n\tconst sourceIdx = nodeToIdx.get(source);\n\tconst targetIdx = nodeToIdx.get(target);\n\n\tif (sourceIdx === undefined || targetIdx === undefined) {\n\t\treturn 0;\n\t}\n\n\t// Current column of A^depth\n\tlet walks = new Float64Array(n);\n\twalks[targetIdx] = 1; // Base case: walks[target] = 1\n\n\t// Compute sum: sum_{k=0}^{K} A^k_{s,t} / k!\n\tlet commScore = walks[sourceIdx] ?? 0; // k=0 term (identity matrix): A^0 = I, so [I]_{s,t} = delta_{s,t}\n\n\tlet factorial = 1;\n\n\t// Iterate from k=1 to k\n\tfor (let depth = 1; depth <= k; depth++) {\n\t\t// Multiply by adjacency matrix: walks_next[i] = sum_j A[i,j] * walks[j]\n\t\tconst walksNext = new Float64Array(n);\n\n\t\tfor (const fromNode of nodes) {\n\t\t\tconst fromIdx = nodeToIdx.get(fromNode);\n\t\t\tif (fromIdx === undefined) continue;\n\n\t\t\tconst neighbours = graph.neighbours(fromNode);\n\t\t\tfor (const toNodeId of neighbours) {\n\t\t\t\tconst toIdx = nodeToIdx.get(toNodeId);\n\t\t\t\tif (toIdx === undefined) continue;\n\n\t\t\t\twalksNext[fromIdx] = (walksNext[fromIdx] ?? 0) + (walks[toIdx] ?? 0);\n\t\t\t}\n\t\t}\n\n\t\tfactorial *= depth;\n\n\t\t// Add contribution: A^depth[s,t] / k!\n\t\tcommScore += (walksNext[sourceIdx] ?? 0) / factorial;\n\n\t\twalks = walksNext;\n\t}\n\n\treturn commScore;\n}\n\n/**\n * Rank paths by communicability between endpoints.\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest communicability first)\n */\nexport function communicability<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"communicability\",\n\t\t};\n\t}\n\n\t// Score paths by communicability between endpoints\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tconst source = path.nodes[0];\n\t\tconst target = path.nodes[path.nodes.length - 1];\n\n\t\tif (source === undefined || target === undefined) {\n\t\t\treturn { path, score: 0 };\n\t\t}\n\n\t\tconst commScore = computeCommunicability(graph, source, target);\n\t\treturn { path, score: commScore };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"communicability\", includeScores);\n}\n","/**\n * Resistance-Distance baseline ranking.\n *\n * Computes effective resistance via Laplacian pseudoinverse (dense, small graphs only).\n * For path scoring: score(P) = 1 / resistance(P.start, P.end), normalised to [0, 1].\n * Size guard: throws if nodeCount > 5000 (O(n^3) complexity).\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Compute effective resistance between two nodes via Laplacian pseudoinverse.\n *\n * Resistance = L^+_{s,s} + L^+_{t,t} - 2*L^+_{s,t}\n * where L^+ is the pseudoinverse of the Laplacian matrix.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @returns Effective resistance\n */\nfunction computeResistance<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: string,\n\ttarget: string,\n): number {\n\tconst nodes = Array.from(graph.nodeIds());\n\tconst nodeToIdx = new Map<string, number>();\n\tnodes.forEach((nodeId, idx) => {\n\t\tnodeToIdx.set(nodeId, idx);\n\t});\n\n\tconst n = nodes.length;\n\n\tif (n === 0 || n > 5000) {\n\t\tthrow new Error(\n\t\t\t`Cannot compute resistance distance: graph too large (${String(n)} nodes). Maximum 5000.`,\n\t\t);\n\t}\n\n\tconst sourceIdx = nodeToIdx.get(source);\n\tconst targetIdx = nodeToIdx.get(target);\n\n\tif (sourceIdx === undefined || targetIdx === undefined) {\n\t\treturn 0;\n\t}\n\n\t// Build Laplacian matrix: L = D - A\n\tconst L: number[][] = Array.from({ length: n }, () =>\n\t\tArray.from({ length: n }, () => 0),\n\t);\n\n\tfor (let i = 0; i < n; i++) {\n\t\tconst nodeId = nodes[i];\n\t\tif (nodeId === undefined) continue;\n\n\t\tconst degree = graph.degree(nodeId);\n\t\tconst row = L[i];\n\t\tif (row !== undefined) {\n\t\t\trow[i] = degree; // Diagonal\n\t\t}\n\n\t\tconst neighbours = graph.neighbours(nodeId);\n\t\tfor (const neighbourId of neighbours) {\n\t\t\tconst j = nodeToIdx.get(neighbourId);\n\t\t\tif (j !== undefined && row !== undefined) {\n\t\t\t\trow[j] = -1; // Off-diagonal\n\t\t\t}\n\t\t}\n\t}\n\n\t// Compute pseudoinverse using Moore-Penrose approach (simplified)\n\t// For small graphs, use LU decomposition with diagonal adjustment\n\tconst Lpinv = pinv(L);\n\n\t// Resistance = L^+_{s,s} + L^+_{t,t} - 2*L^+_{s,t}\n\tconst resistance =\n\t\t(Lpinv[sourceIdx]?.[sourceIdx] ?? 0) +\n\t\t(Lpinv[targetIdx]?.[targetIdx] ?? 0) -\n\t\t2 * (Lpinv[sourceIdx]?.[targetIdx] ?? 0);\n\n\t// Clamp to positive (numerical stability)\n\treturn Math.max(resistance, 1e-10);\n}\n\n/**\n * Compute Moore-Penrose pseudoinverse of a matrix.\n * Simplified implementation for small dense matrices.\n *\n * @param A - Square matrix\n * @returns Pseudoinverse A^+\n */\nfunction pinv(A: number[][]): number[][] {\n\tconst n = A.length;\n\tif (n === 0) return [];\n\n\t// Create copy for singular value computation\n\tconst M = A.map((row) => [...row]);\n\n\t// Simplified: add small regularisation to diagonal before inversion\n\tconst epsilon = 1e-10;\n\tfor (let i = 0; i < n; i++) {\n\t\tconst row = M[i];\n\t\tif (row !== undefined) {\n\t\t\trow[i] = (row[i] ?? 0) + epsilon;\n\t\t}\n\t}\n\n\t// Gaussian elimination with partial pivoting to compute inverse\n\tconst Minv = gaussianInverse(M);\n\n\treturn Minv;\n}\n\n/**\n * Compute matrix inverse using Gaussian elimination with partial pivoting.\n *\n * @param A - Matrix to invert\n * @returns Inverted matrix\n */\nfunction gaussianInverse(A: number[][]): number[][] {\n\tconst n = A.length;\n\n\t// Create augmented matrix [A | I]\n\tconst aug: number[][] = A.map((row, i) => {\n\t\tconst identity: number[] = Array.from({ length: n }, (_, j) =>\n\t\t\ti === j ? 1 : 0,\n\t\t);\n\t\tconst combined: number[] = [...row, ...identity];\n\t\treturn combined;\n\t});\n\n\t// Forward elimination with partial pivoting\n\tfor (let col = 0; col < n; col++) {\n\t\t// Find pivot\n\t\tlet maxRow = col;\n\t\tfor (let row = col + 1; row < n; row++) {\n\t\t\tconst currentRow = aug[row];\n\t\t\tconst maxRowRef = aug[maxRow];\n\t\t\tif (\n\t\t\t\tcurrentRow !== undefined &&\n\t\t\t\tmaxRowRef !== undefined &&\n\t\t\t\tMath.abs(currentRow[col] ?? 0) > Math.abs(maxRowRef[col] ?? 0)\n\t\t\t) {\n\t\t\t\tmaxRow = row;\n\t\t\t}\n\t\t}\n\n\t\t// Swap rows\n\t\tconst currentCol = aug[col];\n\t\tconst maxRowAug = aug[maxRow];\n\t\tif (currentCol !== undefined && maxRowAug !== undefined) {\n\t\t\taug[col] = maxRowAug;\n\t\t\taug[maxRow] = currentCol;\n\t\t}\n\n\t\t// Scale pivot row\n\t\tconst pivotRow = aug[col];\n\t\tconst pivot = pivotRow?.[col];\n\t\tif (pivot === undefined || Math.abs(pivot) < 1e-12) {\n\t\t\tcontinue; // Skip singular column\n\t\t}\n\n\t\tif (pivotRow !== undefined) {\n\t\t\tfor (let j = col; j < 2 * n; j++) {\n\t\t\t\tpivotRow[j] = (pivotRow[j] ?? 0) / pivot;\n\t\t\t}\n\t\t}\n\n\t\t// Eliminate below and above\n\t\tfor (let row = 0; row < n; row++) {\n\t\t\tif (row === col) continue;\n\n\t\t\tconst eliminationRow = aug[row];\n\t\t\tconst factor = eliminationRow?.[col] ?? 0;\n\t\t\tif (eliminationRow !== undefined && pivotRow !== undefined) {\n\t\t\t\tfor (let j = col; j < 2 * n; j++) {\n\t\t\t\t\teliminationRow[j] =\n\t\t\t\t\t\t(eliminationRow[j] ?? 0) - factor * (pivotRow[j] ?? 0);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Extract inverse (right half of augmented matrix)\n\tconst Ainv: number[][] = [];\n\tfor (let i = 0; i < n; i++) {\n\t\tconst row = aug[i];\n\t\tAinv[i] = (row?.slice(n) ?? []).map((v) => v);\n\t}\n\n\treturn Ainv;\n}\n\n/**\n * Rank paths by reciprocal of resistance distance between endpoints.\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest conductance first)\n */\nexport function resistanceDistance<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: BaselineConfig,\n): BaselineResult {\n\tconst { includeScores = true } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"resistance-distance\",\n\t\t};\n\t}\n\n\t// Check graph size\n\tconst nodeCount = Array.from(graph.nodeIds()).length;\n\tif (nodeCount > 5000) {\n\t\tthrow new Error(\n\t\t\t`Cannot rank paths: graph too large (${String(nodeCount)} nodes). Resistance distance requires O(n^3) computation; maximum 5000 nodes.`,\n\t\t);\n\t}\n\n\t// Score paths by conductance (1 / resistance)\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tconst source = path.nodes[0];\n\t\tconst target = path.nodes[path.nodes.length - 1];\n\n\t\tif (source === undefined || target === undefined) {\n\t\t\treturn { path, score: 0 };\n\t\t}\n\n\t\tconst resistance = computeResistance(graph, source, target);\n\t\t// Score = conductance = 1 / resistance\n\t\tconst score = 1 / resistance;\n\t\treturn { path, score };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"resistance-distance\", includeScores);\n}\n","/**\n * Random-Ranking baseline.\n *\n * Null hypothesis baseline: ranks paths by seeded random scores.\n * Uses deterministic hash for reproducibility.\n * Score = seededRandom(nodes.join(','), seed), normalised to [0, 1].\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Configuration for random ranking.\n */\ninterface RandomRankingConfig extends BaselineConfig {\n\t/** Random seed for deterministic reproducibility */\n\treadonly seed?: number;\n}\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 * Rank paths randomly (null hypothesis baseline).\n *\n * @param _graph - Source graph (unused)\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (randomly ordered)\n */\nexport function randomRanking<N extends NodeData, E extends EdgeData>(\n\t_graph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: RandomRankingConfig,\n): BaselineResult {\n\tconst { includeScores = true, seed = 0 } = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"random\",\n\t\t};\n\t}\n\n\t// Score paths by seeded random hash of node list\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tconst nodesKey = path.nodes.join(\",\");\n\t\tconst score = seededRandom(nodesKey, seed);\n\t\treturn { path, score };\n\t});\n\n\treturn normaliseAndRank(paths, scored, \"random\", includeScores);\n}\n","/**\n * Hitting-time ranking baseline.\n *\n * Ranks paths by the inverse of the expected number of steps\n * in a random walk from source to target.\n *\n * Score = 1 / hittingTime(source, target)\n *\n * Two computation modes:\n * - Approximate: Monte Carlo random walk simulation (default, efficient for large graphs)\n * - Exact: Fundamental matrix approach (for small graphs)\n * - Auto: Automatic mode selection (switches at ~100 nodes)\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { ExpansionPath } from \"../../expansion/types\";\nimport type { BaselineConfig, BaselineResult } from \"./types\";\nimport { normaliseAndRank } from \"./utils\";\n\n/**\n * Configuration for hitting-time ranking.\n */\ninterface HittingTimeConfig extends BaselineConfig {\n\t/** Computation mode: \"exact\", \"approximate\", or \"auto\" (default: \"auto\") */\n\treadonly mode?: \"exact\" | \"approximate\" | \"auto\";\n\t/** Number of Monte Carlo walks for approximate mode (default: 1000) */\n\treadonly walks?: number;\n\t/** Maximum steps per walk (default: 10000) */\n\treadonly maxSteps?: number;\n\t/** Random seed for reproducibility (default: 42) */\n\treadonly seed?: number;\n}\n\n/**\n * Seeded deterministic random number generator (LCG).\n * Suitable for reproducible random walk simulation.\n */\nclass SeededRNG {\n\tprivate state: number;\n\n\tconstructor(seed: number) {\n\t\tthis.state = seed;\n\t}\n\n\t/**\n\t * Generate next pseudorandom value in [0, 1).\n\t */\n\tnext(): number {\n\t\t// Linear congruential generator: standard MINSTD parameters\n\t\tthis.state = (this.state * 1103515245 + 12345) & 0x7fffffff;\n\t\treturn this.state / 0x7fffffff;\n\t}\n}\n\n/**\n * Compute hitting time via Monte Carlo random walk simulation.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param walks - Number of walks to simulate\n * @param maxSteps - Maximum steps per walk\n * @param rng - Seeded RNG instance\n * @returns Average hitting time across walks\n */\nfunction computeHittingTimeApproximate<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: string,\n\ttarget: string,\n\twalks: number,\n\tmaxSteps: number,\n\trng: SeededRNG,\n): number {\n\tif (source === target) {\n\t\treturn 0; // Hitting time from a node to itself is 0\n\t}\n\n\tlet totalSteps = 0;\n\tlet successfulWalks = 0;\n\n\tfor (let w = 0; w < walks; w++) {\n\t\tlet current: string = source;\n\t\tlet steps = 0;\n\n\t\twhile (current !== target && steps < maxSteps) {\n\t\t\tconst neighbours = Array.from(graph.neighbours(current));\n\t\t\tif (neighbours.length === 0) {\n\t\t\t\t// Stuck in sink node\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Uniformly choose next neighbour\n\t\t\tconst nextIdx = Math.floor(rng.next() * neighbours.length);\n\t\t\tconst nextNode = neighbours[nextIdx];\n\t\t\tif (nextNode === undefined) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcurrent = nextNode;\n\t\t\tsteps++;\n\t\t}\n\n\t\tif (current === target) {\n\t\t\ttotalSteps += steps;\n\t\t\tsuccessfulWalks++;\n\t\t}\n\t}\n\n\t// Return average if any walks succeeded\n\tif (successfulWalks > 0) {\n\t\treturn totalSteps / successfulWalks;\n\t}\n\n\t// Return maxSteps as estimate if no walks succeeded\n\treturn maxSteps;\n}\n\n/**\n * Compute hitting time via exact fundamental matrix method.\n *\n * For small graphs, computes exact expected hitting times using\n * the fundamental matrix of the random walk.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @returns Exact hitting time (or approximation if convergence fails)\n */\nfunction computeHittingTimeExact<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: string,\n\ttarget: string,\n): number {\n\tif (source === target) {\n\t\treturn 0;\n\t}\n\n\tconst nodes = Array.from(graph.nodeIds());\n\tconst nodeToIdx = new Map<string, number>();\n\tnodes.forEach((nodeId, idx) => {\n\t\tnodeToIdx.set(nodeId, idx);\n\t});\n\n\tconst n = nodes.length;\n\tconst sourceIdx = nodeToIdx.get(source);\n\tconst targetIdx = nodeToIdx.get(target);\n\n\tif (sourceIdx === undefined || targetIdx === undefined) {\n\t\treturn 0;\n\t}\n\n\t// Build transition matrix P with absorbing target\n\tconst P: number[][] = [];\n\tfor (let i = 0; i < n; i++) {\n\t\tconst row: number[] = [];\n\t\tfor (let j = 0; j < n; j++) {\n\t\t\trow[j] = 0;\n\t\t}\n\t\tP[i] = row;\n\t}\n\n\tfor (const nodeId of nodes) {\n\t\tconst idx = nodeToIdx.get(nodeId);\n\t\tif (idx === undefined) continue;\n\n\t\tconst pRow = P[idx];\n\t\tif (pRow === undefined) continue;\n\n\t\tif (idx === targetIdx) {\n\t\t\t// Target is absorbing state\n\t\t\tpRow[idx] = 1;\n\t\t} else {\n\t\t\tconst neighbours = Array.from(graph.neighbours(nodeId));\n\t\t\tconst degree = neighbours.length;\n\n\t\t\tif (degree > 0) {\n\t\t\t\tfor (const neighbourId of neighbours) {\n\t\t\t\t\tconst nIdx = nodeToIdx.get(neighbourId);\n\t\t\t\t\tif (nIdx !== undefined) {\n\t\t\t\t\t\tpRow[nIdx] = 1 / degree;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Compute fundamental matrix N = (I - Q)^(-1)\n\t// where Q is the submatrix of P excluding the absorbing state\n\tconst transientIndices: number[] = [];\n\n\tfor (let i = 0; i < n; i++) {\n\t\tif (i !== targetIdx) {\n\t\t\ttransientIndices.push(i);\n\t\t}\n\t}\n\n\tconst m = transientIndices.length;\n\tconst Q: number[][] = [];\n\tfor (let i = 0; i < m; i++) {\n\t\tconst row: number[] = [];\n\t\tfor (let j = 0; j < m; j++) {\n\t\t\trow[j] = 0;\n\t\t}\n\t\tQ[i] = row;\n\t}\n\n\tfor (let i = 0; i < m; i++) {\n\t\tconst qRow = Q[i];\n\t\tif (qRow === undefined) continue;\n\t\tconst origI = transientIndices[i];\n\t\tif (origI === undefined) continue;\n\t\tconst pRow = P[origI];\n\t\tif (pRow === undefined) continue;\n\n\t\tfor (let j = 0; j < m; j++) {\n\t\t\tconst origJ = transientIndices[j];\n\t\t\tif (origJ === undefined) continue;\n\t\t\tqRow[j] = pRow[origJ] ?? 0;\n\t\t}\n\t}\n\n\t// Compute I - Q\n\tconst IMQ: number[][] = [];\n\tfor (let i = 0; i < m; i++) {\n\t\tconst row: number[] = [];\n\t\tfor (let j = 0; j < m; j++) {\n\t\t\trow[j] = i === j ? 1 : 0;\n\t\t}\n\t\tIMQ[i] = row;\n\t}\n\n\tfor (let i = 0; i < m; i++) {\n\t\tconst imqRow = IMQ[i];\n\t\tif (imqRow === undefined) continue;\n\t\tconst qRow = Q[i];\n\n\t\tfor (let j = 0; j < m; j++) {\n\t\t\tconst qVal = qRow?.[j] ?? 0;\n\t\t\timqRow[j] = (i === j ? 1 : 0) - qVal;\n\t\t}\n\t}\n\n\t// Invert (I - Q) using Gaussian elimination\n\tconst N = invertMatrix(IMQ);\n\n\tif (N === null) {\n\t\t// Fallback if inversion fails\n\t\treturn 1;\n\t}\n\n\t// Hitting time from source to target = sum of row corresponding to source\n\tconst sourceTransientIdx = transientIndices.indexOf(sourceIdx);\n\tif (sourceTransientIdx < 0) {\n\t\treturn 0; // Source is already the target\n\t}\n\n\tlet hittingTime = 0;\n\tconst row = N[sourceTransientIdx];\n\tif (row !== undefined) {\n\t\tfor (const val of row) {\n\t\t\thittingTime += val;\n\t\t}\n\t}\n\n\treturn hittingTime;\n}\n\n/**\n * Invert a square matrix using Gaussian elimination with partial pivoting.\n *\n * @param matrix - Input matrix (n × n)\n * @returns Inverted matrix, or null if singular\n */\nfunction invertMatrix(matrix: number[][]): number[][] | null {\n\tconst n = matrix.length;\n\tconst aug: number[][] = [];\n\n\t// Create augmented matrix [A | I]\n\tfor (let i = 0; i < n; i++) {\n\t\tconst row: number[] = [];\n\t\tconst matRow = matrix[i];\n\n\t\tfor (let j = 0; j < n; j++) {\n\t\t\trow[j] = matRow?.[j] ?? 0;\n\t\t}\n\t\tfor (let j = 0; j < n; j++) {\n\t\t\trow[n + j] = i === j ? 1 : 0;\n\t\t}\n\t\taug[i] = row;\n\t}\n\n\t// Forward elimination with partial pivoting\n\tfor (let col = 0; col < n; col++) {\n\t\t// Find pivot\n\t\tlet pivotRow = col;\n\t\tconst pivotCol = aug[pivotRow];\n\t\tif (pivotCol === undefined) return null;\n\n\t\tfor (let row = col + 1; row < n; row++) {\n\t\t\tconst currRowVal = aug[row]?.[col] ?? 0;\n\t\t\tconst pivotRowVal = pivotCol[col] ?? 0;\n\t\t\tif (Math.abs(currRowVal) > Math.abs(pivotRowVal)) {\n\t\t\t\tpivotRow = row;\n\t\t\t}\n\t\t}\n\n\t\t// Check for singular matrix\n\t\tconst augPivot = aug[pivotRow];\n\t\tif (augPivot === undefined || Math.abs(augPivot[col] ?? 0) < 1e-10) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Swap rows\n\t\t[aug[col], aug[pivotRow]] = [aug[pivotRow] ?? [], aug[col] ?? []];\n\n\t\t// Scale pivot row\n\t\tconst scaledPivotRow = aug[col];\n\t\tif (scaledPivotRow === undefined) return null;\n\t\tconst pivot = scaledPivotRow[col] ?? 1;\n\n\t\tfor (let j = 0; j < 2 * n; j++) {\n\t\t\tscaledPivotRow[j] = (scaledPivotRow[j] ?? 0) / pivot;\n\t\t}\n\n\t\t// Eliminate column below pivot\n\t\tfor (let row = col + 1; row < n; row++) {\n\t\t\tconst currRow = aug[row];\n\t\t\tif (currRow === undefined) continue;\n\t\t\tconst factor = currRow[col] ?? 0;\n\n\t\t\tfor (let j = 0; j < 2 * n; j++) {\n\t\t\t\tcurrRow[j] = (currRow[j] ?? 0) - factor * (scaledPivotRow[j] ?? 0);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Back substitution\n\tfor (let col = n - 1; col > 0; col--) {\n\t\tconst colRow = aug[col];\n\t\tif (colRow === undefined) return null;\n\n\t\tfor (let row = col - 1; row >= 0; row--) {\n\t\t\tconst currRow = aug[row];\n\t\t\tif (currRow === undefined) continue;\n\n\t\t\tconst factor = currRow[col] ?? 0;\n\t\t\tfor (let j = 0; j < 2 * n; j++) {\n\t\t\t\tcurrRow[j] = (currRow[j] ?? 0) - factor * (colRow[j] ?? 0);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Extract inverse from augmented matrix\n\tconst inv: number[][] = [];\n\tfor (let i = 0; i < n; i++) {\n\t\tconst row: number[] = [];\n\t\tfor (let j = 0; j < n; j++) {\n\t\t\trow[j] = 0;\n\t\t}\n\t\tinv[i] = row;\n\t}\n\n\tfor (let i = 0; i < n; i++) {\n\t\tconst invRow = inv[i];\n\t\tif (invRow === undefined) continue;\n\t\tconst augRow = aug[i];\n\t\tif (augRow === undefined) continue;\n\n\t\tfor (let j = 0; j < n; j++) {\n\t\t\tinvRow[j] = augRow[n + j] ?? 0;\n\t\t}\n\t}\n\n\treturn inv;\n}\n\n/**\n * Rank paths by inverse hitting time between endpoints.\n *\n * @param graph - Source graph\n * @param paths - Paths to rank\n * @param config - Configuration options\n * @returns Ranked paths (highest inverse hitting time first)\n */\nexport function hittingTime<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tpaths: readonly ExpansionPath[],\n\tconfig?: HittingTimeConfig,\n): BaselineResult {\n\tconst {\n\t\tincludeScores = true,\n\t\tmode = \"auto\",\n\t\twalks = 1000,\n\t\tmaxSteps = 10000,\n\t\tseed = 42,\n\t} = config ?? {};\n\n\tif (paths.length === 0) {\n\t\treturn {\n\t\t\tpaths: [],\n\t\t\tmethod: \"hitting-time\",\n\t\t};\n\t}\n\n\t// Choose computation mode\n\tconst nodeCount = Array.from(graph.nodeIds()).length;\n\tconst actualMode =\n\t\tmode === \"auto\" ? (nodeCount < 100 ? \"exact\" : \"approximate\") : mode;\n\n\tconst rng = new SeededRNG(seed);\n\n\t// Score paths by inverse hitting time between endpoints\n\tconst scored: { path: ExpansionPath; score: number }[] = paths.map((path) => {\n\t\tconst source = path.nodes[0];\n\t\tconst target = path.nodes[path.nodes.length - 1];\n\n\t\tif (source === undefined || target === undefined) {\n\t\t\treturn { path, score: 0 };\n\t\t}\n\n\t\tconst ht =\n\t\t\tactualMode === \"exact\"\n\t\t\t\t? computeHittingTimeExact(graph, source, target)\n\t\t\t\t: computeHittingTimeApproximate(\n\t\t\t\t\t\tgraph,\n\t\t\t\t\t\tsource,\n\t\t\t\t\t\ttarget,\n\t\t\t\t\t\twalks,\n\t\t\t\t\t\tmaxSteps,\n\t\t\t\t\t\trng,\n\t\t\t\t\t);\n\n\t\t// Use inverse: shorter hitting time = higher score\n\t\tconst score = ht > 0 ? 1 / ht : 0;\n\t\treturn { path, score };\n\t});\n\n\t// Guard against non-finite scores before normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\tif (!Number.isFinite(maxScore)) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({ ...path, score: 0 })),\n\t\t\tmethod: \"hitting-time\",\n\t\t};\n\t}\n\n\treturn normaliseAndRank(paths, scored, \"hitting-time\", includeScores);\n}\n","/**\n * Ego-network (k-hop neighbourhood) extraction.\n *\n * Extracts the induced subgraph of all nodes within k hops of a centre node.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport { AdjacencyMapGraph } from \"../graph/adjacency-map\";\n\n/**\n * Options for ego-network extraction.\n */\nexport interface EgoNetworkOptions {\n\t/** Number of hops from the centre node. Default: 1. */\n\treadonly hops?: number;\n}\n\n/**\n * Extract the ego-network (k-hop neighbourhood) of a centre node.\n *\n * The ego-network includes all nodes reachable within k hops from the\n * centre node, plus all edges between those nodes (induced subgraph).\n *\n * For directed graphs, the search follows outgoing edges by default.\n * To include incoming edges, use direction 'both' in the underlying traversal.\n *\n * @param graph - The source graph\n * @param centre - The centre node ID\n * @param options - Extraction options\n * @returns An induced subgraph of the k-hop neighbourhood\n * @throws Error if the centre node does not exist in the graph\n *\n * @example\n * ```typescript\n * // 2-hop neighbourhood\n * const ego = extractEgoNetwork(graph, 'A', { hops: 2 });\n * ```\n */\nexport function extractEgoNetwork<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tcentre: NodeId,\n\toptions?: EgoNetworkOptions,\n): AdjacencyMapGraph<N, E> {\n\tconst hops = options?.hops ?? 1;\n\n\tif (!graph.hasNode(centre)) {\n\t\tthrow new Error(`Centre node '${centre}' does not exist in the graph`);\n\t}\n\n\tif (hops < 0) {\n\t\tthrow new Error(`Hops must be non-negative, got ${String(hops)}`);\n\t}\n\n\t// Find all nodes within k hops using BFS\n\tconst nodesInEgoNetwork = new Set<NodeId>([centre]);\n\n\tif (hops > 0) {\n\t\tconst visited = new Set<NodeId>([centre]);\n\t\t// Queue entries: [nodeId, distance from centre]\n\t\tconst queue: [NodeId, number][] = [[centre, 0]];\n\n\t\twhile (queue.length > 0) {\n\t\t\tconst entry = queue.shift();\n\t\t\tif (entry === undefined) break;\n\t\t\tconst [current, distance] = entry;\n\n\t\t\tif (distance < hops) {\n\t\t\t\tfor (const neighbour of graph.neighbours(current)) {\n\t\t\t\t\tif (!visited.has(neighbour)) {\n\t\t\t\t\t\tvisited.add(neighbour);\n\t\t\t\t\t\tnodesInEgoNetwork.add(neighbour);\n\t\t\t\t\t\tqueue.push([neighbour, distance + 1]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Build induced subgraph\n\tconst result = graph.directed\n\t\t? AdjacencyMapGraph.directed<N, E>()\n\t\t: AdjacencyMapGraph.undirected<N, E>();\n\n\t// Add nodes\n\tfor (const nodeId of nodesInEgoNetwork) {\n\t\tconst nodeData = graph.getNode(nodeId);\n\t\tif (nodeData !== undefined) {\n\t\t\tresult.addNode(nodeData);\n\t\t}\n\t}\n\n\t// Add edges between nodes in the ego network\n\tfor (const edge of graph.edges()) {\n\t\tif (\n\t\t\tnodesInEgoNetwork.has(edge.source) &&\n\t\t\tnodesInEgoNetwork.has(edge.target)\n\t\t) {\n\t\t\tresult.addEdge(edge);\n\t\t}\n\t}\n\n\treturn result;\n}\n","/**\n * K-core decomposition algorithm.\n *\n * A k-core is the maximal subgraph where every node has degree at least k.\n * The decomposition is computed by iteratively removing nodes with degree < k.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport { AdjacencyMapGraph } from \"../graph/adjacency-map\";\n\n/**\n * Extract the k-core of a graph.\n *\n * The k-core is the maximal connected subgraph where every node has\n * degree at least k. This is computed using a peeling algorithm that\n * iteratively removes nodes with degree less than k.\n *\n * For undirected graphs, degree counts all adjacent nodes.\n * For directed graphs, degree counts both in- and out-neighbours.\n *\n * @param graph - The source graph\n * @param k - The minimum degree threshold\n * @returns A new graph containing the k-core (may be empty)\n *\n * @example\n * ```typescript\n * // Extract the 3-core (nodes with at least 3 neighbours)\n * const core3 = extractKCore(graph, 3);\n * ```\n */\nexport function extractKCore<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tk: number,\n): AdjacencyMapGraph<N, E> {\n\tif (k < 0) {\n\t\tthrow new Error(`k must be non-negative, got ${String(k)}`);\n\t}\n\n\t// Track remaining nodes and their degrees\n\tconst remaining = new Set<NodeId>();\n\tconst degrees = new Map<NodeId, number>();\n\n\tfor (const nodeId of graph.nodeIds()) {\n\t\tremaining.add(nodeId);\n\t\t// For directed graphs, use total degree (both directions)\n\t\tconst deg = graph.directed\n\t\t\t? graph.degree(nodeId, \"both\")\n\t\t\t: graph.degree(nodeId);\n\t\tdegrees.set(nodeId, deg);\n\t}\n\n\t// Use a queue for nodes to remove (degree < k)\n\tconst toRemove: NodeId[] = [];\n\n\tfor (const [nodeId, deg] of degrees) {\n\t\tif (deg < k) {\n\t\t\ttoRemove.push(nodeId);\n\t\t}\n\t}\n\n\t// Iteratively remove nodes with degree < k\n\twhile (toRemove.length > 0) {\n\t\tconst nodeId = toRemove.shift();\n\t\tif (nodeId === undefined) break;\n\n\t\tif (!remaining.has(nodeId)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tremaining.delete(nodeId);\n\n\t\t// Update degrees of neighbours\n\t\tconst neighbours = graph.directed\n\t\t\t? graph.neighbours(nodeId, \"both\")\n\t\t\t: graph.neighbours(nodeId);\n\n\t\tfor (const neighbour of neighbours) {\n\t\t\tif (remaining.has(neighbour)) {\n\t\t\t\tconst currentDeg = degrees.get(neighbour) ?? 0;\n\t\t\t\tconst newDeg = currentDeg - 1;\n\t\t\t\tdegrees.set(neighbour, newDeg);\n\n\t\t\t\tif (newDeg < k && newDeg === k - 1) {\n\t\t\t\t\t// Only add to queue if crossing below k threshold\n\t\t\t\t\ttoRemove.push(neighbour);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Build the result as an induced subgraph\n\tconst result = graph.directed\n\t\t? AdjacencyMapGraph.directed<N, E>()\n\t\t: AdjacencyMapGraph.undirected<N, E>();\n\n\t// Add remaining nodes\n\tfor (const nodeId of remaining) {\n\t\tconst nodeData = graph.getNode(nodeId);\n\t\tif (nodeData !== undefined) {\n\t\t\tresult.addNode(nodeData);\n\t\t}\n\t}\n\n\t// Add edges between remaining nodes\n\tfor (const edge of graph.edges()) {\n\t\tif (remaining.has(edge.source) && remaining.has(edge.target)) {\n\t\t\tresult.addEdge(edge);\n\t\t}\n\t}\n\n\treturn result;\n}\n","/**\n * K-truss decomposition algorithm.\n *\n * A k-truss is the maximal subgraph where every edge participates in at\n * least k-2 triangles. The 2-truss is the entire graph, the 3-truss\n * requires each edge to be in at least one triangle, etc.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport { AdjacencyMapGraph } from \"../graph/adjacency-map\";\n\n/**\n * Count triangles involving a given edge.\n *\n * For an edge (u, v), count common neighbours of u and v.\n * Each common neighbour w forms a triangle u-v-w.\n *\n * @param graph - The graph\n * @param u - First endpoint\n * @param v - Second endpoint\n * @returns Number of triangles containing the edge (u, v)\n */\nfunction countEdgeTriangles(\n\tgraph: ReadableGraph,\n\tu: NodeId,\n\tv: NodeId,\n): number {\n\tconst uNeighbours = new Set(graph.neighbours(u));\n\tlet count = 0;\n\n\tfor (const w of graph.neighbours(v)) {\n\t\tif (w !== u && uNeighbours.has(w)) {\n\t\t\tcount++;\n\t\t}\n\t}\n\n\treturn count;\n}\n\n/**\n * Extract the k-truss of a graph.\n *\n * The k-truss is the maximal subgraph where every edge participates in\n * at least k-2 triangles. This is computed by iteratively removing edges\n * with fewer than k-2 triangles, then removing isolated nodes.\n *\n * Note: K-truss is typically defined for undirected graphs. For directed\n * graphs, this treats the graph as undirected for triangle counting.\n *\n * @param graph - The source graph\n * @param k - The minimum triangle count threshold (edge must be in >= k-2 triangles)\n * @returns A new graph containing the k-truss (may be empty)\n *\n * @example\n * ```typescript\n * // Extract the 3-truss (edges in at least 1 triangle)\n * const truss3 = extractKTruss(graph, 3);\n * ```\n */\nexport function extractKTruss<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tk: number,\n): AdjacencyMapGraph<N, E> {\n\tif (k < 2) {\n\t\tthrow new Error(`k must be at least 2, got ${String(k)}`);\n\t}\n\n\tconst minTriangles = k - 2;\n\n\t// Build undirected adjacency for triangle counting\n\t// Store as Map<NodeId, Set<NodeId>> for efficient lookup\n\tconst adjacency = new Map<NodeId, Set<NodeId>>();\n\tconst edgeData = new Map<string, E>();\n\tconst remainingEdges = new Set<string>();\n\n\tfor (const nodeId of graph.nodeIds()) {\n\t\tadjacency.set(nodeId, new Set());\n\t}\n\n\t// Build adjacency (treating as undirected)\n\tfor (const edge of graph.edges()) {\n\t\tconst { source, target } = edge;\n\n\t\t// Add to adjacency (both directions)\n\t\tadjacency.get(source)?.add(target);\n\t\tadjacency.get(target)?.add(source);\n\n\t\t// Store edge data with canonical key\n\t\tconst key =\n\t\t\tsource < target ? `${source}::${target}` : `${target}::${source}`;\n\t\tedgeData.set(key, edge);\n\t\tremainingEdges.add(key);\n\t}\n\n\t// Compute initial triangle counts for each edge\n\tconst triangleCounts = new Map<string, number>();\n\tconst edgesToRemove: string[] = [];\n\n\tfor (const key of remainingEdges) {\n\t\tconst edge = edgeData.get(key);\n\t\tif (edge !== undefined) {\n\t\t\tconst count = countEdgeTriangles(graph, edge.source, edge.target);\n\t\t\ttriangleCounts.set(key, count);\n\t\t\tif (count < minTriangles) {\n\t\t\t\tedgesToRemove.push(key);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Iteratively remove edges with insufficient triangles\n\twhile (edgesToRemove.length > 0) {\n\t\tconst edgeKey = edgesToRemove.shift();\n\t\tif (edgeKey === undefined) break;\n\n\t\tif (!remainingEdges.has(edgeKey)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tremainingEdges.delete(edgeKey);\n\t\tconst edge = edgeData.get(edgeKey);\n\n\t\tif (edge === undefined) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { source, target } = edge;\n\n\t\t// Remove from adjacency\n\t\tadjacency.get(source)?.delete(target);\n\t\tadjacency.get(target)?.delete(source);\n\n\t\t// Find triangles that were broken and update counts\n\t\t// Common neighbours form triangles (source, target, neighbour)\n\t\tconst sourceNeighbours = adjacency.get(source);\n\t\tif (sourceNeighbours !== undefined) {\n\t\t\tfor (const w of adjacency.get(target) ?? []) {\n\t\t\t\tif (sourceNeighbours.has(w)) {\n\t\t\t\t\t// Triangle (source, target, w) is broken\n\t\t\t\t\t// Update triangle counts for edges (source, w) and (target, w)\n\t\t\t\t\tconst keySw = source < w ? `${source}::${w}` : `${w}::${source}`;\n\t\t\t\t\tconst keyTw = target < w ? `${target}::${w}` : `${w}::${target}`;\n\n\t\t\t\t\tfor (const keyToUpdate of [keySw, keyTw]) {\n\t\t\t\t\t\tif (remainingEdges.has(keyToUpdate)) {\n\t\t\t\t\t\t\tconst currentCount = triangleCounts.get(keyToUpdate) ?? 0;\n\t\t\t\t\t\t\tconst newCount = currentCount - 1;\n\t\t\t\t\t\t\ttriangleCounts.set(keyToUpdate, newCount);\n\n\t\t\t\t\t\t\tif (newCount < minTriangles && newCount === minTriangles - 1) {\n\t\t\t\t\t\t\t\tedgesToRemove.push(keyToUpdate);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Determine which nodes are still connected by remaining edges\n\tconst nodesWithEdges = new Set<NodeId>();\n\tfor (const key of remainingEdges) {\n\t\tconst edge = edgeData.get(key);\n\t\tif (edge !== undefined) {\n\t\t\tnodesWithEdges.add(edge.source);\n\t\t\tnodesWithEdges.add(edge.target);\n\t\t}\n\t}\n\n\t// Build the result\n\tconst result = graph.directed\n\t\t? AdjacencyMapGraph.directed<N, E>()\n\t\t: AdjacencyMapGraph.undirected<N, E>();\n\n\t// Add nodes that have at least one remaining edge\n\tfor (const nodeId of nodesWithEdges) {\n\t\tconst nodeData = graph.getNode(nodeId);\n\t\tif (nodeData !== undefined) {\n\t\t\tresult.addNode(nodeData);\n\t\t}\n\t}\n\n\t// Add remaining edges\n\tfor (const key of remainingEdges) {\n\t\tconst edge = edgeData.get(key);\n\t\tif (\n\t\t\tedge !== undefined &&\n\t\t\tresult.hasNode(edge.source) &&\n\t\t\tresult.hasNode(edge.target)\n\t\t) {\n\t\t\tresult.addEdge(edge);\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Compute the truss number for each edge.\n *\n * The truss number of an edge is the largest k such that the edge\n * belongs to the k-truss.\n *\n * @param graph - The source graph\n * @returns Map from edge key (canonical \"u::v\") to truss number\n *\n * @example\n * ```typescript\n * const trussNumbers = computeTrussNumbers(graph);\n * const edgeKey = 'A::B'; // where A < B lexicographically\n * console.log(`Edge A-B is in the ${trussNumbers.get(edgeKey)}-truss`);\n * ```\n */\nexport function computeTrussNumbers<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n): Map<string, number> {\n\t// Build adjacency and edge tracking\n\tconst adjacency = new Map<NodeId, Set<NodeId>>();\n\tconst edgeData = new Map<string, E>();\n\tconst remainingEdges = new Set<string>();\n\n\tfor (const nodeId of graph.nodeIds()) {\n\t\tadjacency.set(nodeId, new Set());\n\t}\n\n\tfor (const edge of graph.edges()) {\n\t\tconst { source, target } = edge;\n\t\tadjacency.get(source)?.add(target);\n\t\tadjacency.get(target)?.add(source);\n\n\t\tconst key =\n\t\t\tsource < target ? `${source}::${target}` : `${target}::${source}`;\n\t\tedgeData.set(key, edge);\n\t\tremainingEdges.add(key);\n\t}\n\n\t// Compute initial triangle counts\n\tconst triangleCounts = new Map<string, number>();\n\tfor (const key of remainingEdges) {\n\t\tconst edge = edgeData.get(key);\n\t\tif (edge !== undefined) {\n\t\t\ttriangleCounts.set(\n\t\t\t\tkey,\n\t\t\t\tcountEdgeTriangles(graph, edge.source, edge.target),\n\t\t\t);\n\t\t}\n\t}\n\n\t// Result map\n\tconst trussNumbers = new Map<string, number>();\n\n\t// Process edges in order of triangle count\n\tconst edgesByTriangleCount = new Map<number, Set<string>>();\n\n\tfor (const [key, count] of triangleCounts) {\n\t\tif (!edgesByTriangleCount.has(count)) {\n\t\t\tedgesByTriangleCount.set(count, new Set());\n\t\t}\n\t\tedgesByTriangleCount.get(count)?.add(key);\n\t}\n\n\t// Process from lowest triangle count upwards\n\tconst sortedCounts = [...edgesByTriangleCount.keys()].sort((a, b) => a - b);\n\n\tfor (const currentCount of sortedCounts) {\n\t\tconst bucket = edgesByTriangleCount.get(currentCount);\n\t\tif (bucket === undefined) continue;\n\n\t\twhile (bucket.size > 0) {\n\t\t\tconst edgeKey = bucket.values().next().value;\n\t\t\tif (edgeKey === undefined) break;\n\t\t\tbucket.delete(edgeKey);\n\n\t\t\tif (!remainingEdges.has(edgeKey)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Truss number is triangle count + 2\n\t\t\tconst trussNumber = currentCount + 2;\n\t\t\ttrussNumbers.set(edgeKey, trussNumber);\n\t\t\tremainingEdges.delete(edgeKey);\n\n\t\t\tconst edge = edgeData.get(edgeKey);\n\t\t\tif (edge === undefined) continue;\n\n\t\t\tconst { source, target } = edge;\n\n\t\t\t// Remove from adjacency\n\t\t\tadjacency.get(source)?.delete(target);\n\t\t\tadjacency.get(target)?.delete(source);\n\n\t\t\t// Update triangle counts for affected edges\n\t\t\tconst sourceNeighbours = adjacency.get(source);\n\t\t\tif (sourceNeighbours !== undefined) {\n\t\t\t\tfor (const w of adjacency.get(target) ?? []) {\n\t\t\t\t\tif (sourceNeighbours.has(w)) {\n\t\t\t\t\t\tconst keySw = source < w ? `${source}::${w}` : `${w}::${source}`;\n\t\t\t\t\t\tconst keyTw = target < w ? `${target}::${w}` : `${w}::${target}`;\n\n\t\t\t\t\t\tfor (const keyToUpdate of [keySw, keyTw]) {\n\t\t\t\t\t\t\tif (remainingEdges.has(keyToUpdate)) {\n\t\t\t\t\t\t\t\tconst oldCount = triangleCounts.get(keyToUpdate) ?? 0;\n\t\t\t\t\t\t\t\tconst newCount = oldCount - 1;\n\t\t\t\t\t\t\t\ttriangleCounts.set(keyToUpdate, newCount);\n\n\t\t\t\t\t\t\t\t// Move to new bucket\n\t\t\t\t\t\t\t\tedgesByTriangleCount.get(oldCount)?.delete(keyToUpdate);\n\t\t\t\t\t\t\t\tif (!edgesByTriangleCount.has(newCount)) {\n\t\t\t\t\t\t\t\t\tedgesByTriangleCount.set(newCount, new Set());\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tedgesByTriangleCount.get(newCount)?.add(keyToUpdate);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn trussNumbers;\n}\n","/**\n * Motif enumeration algorithms.\n *\n * Motifs are small recurring subgraph patterns. This module provides\n * enumeration and counting of motifs of size 3 and 4.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\n\n/**\n * Result of a motif census operation.\n */\nexport interface MotifCensus {\n\t/** Map from motif type identifier to count */\n\treadonly counts: ReadonlyMap<string, number>;\n\t/** Optional map from motif type to node instances */\n\treadonly instances?: ReadonlyMap<string, readonly NodeId[][]>;\n}\n\n/**\n * Canonicalise an edge pattern for hashing.\n *\n * Returns a canonical string representation of a small graph pattern.\n */\nfunction canonicalisePattern(\n\tnodeCount: number,\n\tedges: readonly (readonly [number, number])[],\n): string {\n\t// For small graphs (3-4 nodes), we enumerate all permutations\n\t// and return the lexicographically smallest edge list\n\n\tconst permutations = getPermutations(nodeCount);\n\tlet minPattern: string | null = null;\n\n\tfor (const perm of permutations) {\n\t\t// Transform edges according to permutation\n\t\tconst transformedEdges = edges\n\t\t\t.map(([u, v]) => {\n\t\t\t\tconst pu = perm[u] ?? -1;\n\t\t\t\tconst pv = perm[v] ?? -1;\n\t\t\t\tif (pu < 0 || pv < 0) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\treturn pu < pv\n\t\t\t\t\t? `${String(pu)}-${String(pv)}`\n\t\t\t\t\t: `${String(pv)}-${String(pu)}`;\n\t\t\t})\n\t\t\t.filter((edge): edge is string => edge !== undefined)\n\t\t\t.sort()\n\t\t\t.join(\",\");\n\n\t\tif (minPattern === null || transformedEdges < minPattern) {\n\t\t\tminPattern = transformedEdges;\n\t\t}\n\t}\n\n\treturn minPattern ?? \"\";\n}\n\n/**\n * Generate all permutations of [0, n-1].\n */\nfunction getPermutations(n: number): number[][] {\n\tif (n === 0) return [[]];\n\tif (n === 1) return [[0]];\n\n\tconst result: number[][] = [];\n\tconst arr = Array.from({ length: n }, (_, i) => i);\n\n\tfunction permute(start: number): void {\n\t\tif (start === n - 1) {\n\t\t\tresult.push([...arr]);\n\t\t\treturn;\n\t\t}\n\n\t\tfor (let i = start; i < n; i++) {\n\t\t\tconst startVal = arr[start];\n\t\t\tconst iVal = arr[i];\n\t\t\tif (startVal === undefined || iVal === undefined) continue;\n\t\t\tarr[start] = iVal;\n\t\t\tarr[i] = startVal;\n\t\t\tpermute(start + 1);\n\t\t\tarr[start] = startVal;\n\t\t\tarr[i] = iVal;\n\t\t}\n\t}\n\n\tpermute(0);\n\treturn result;\n}\n\n/**\n * Enumerate all 3-node motifs in the graph.\n *\n * A 3-node motif (triad) can be one of 4 isomorphism classes for undirected graphs:\n * - Empty: no edges\n * - 1-edge: single edge\n * - 2-star: two edges sharing a node (path of length 2)\n * - Triangle: three edges (complete graph K3)\n *\n * For directed graphs, there are 16 isomorphism classes.\n *\n * @param graph - The source graph\n * @param includeInstances - Whether to include node instances in the result\n * @returns Motif census with counts and optionally instances\n */\nfunction enumerate3NodeMotifs<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tincludeInstances: boolean,\n): MotifCensus {\n\tconst counts = new Map<string, number>();\n\tconst instances = includeInstances\n\t\t? new Map<string, NodeId[][]>()\n\t\t: undefined;\n\n\tconst nodeList = [...graph.nodeIds()];\n\tconst n = nodeList.length;\n\n\t// Iterate over all triples of nodes\n\tfor (let i = 0; i < n; i++) {\n\t\tconst ni = nodeList[i];\n\t\tif (ni === undefined) continue;\n\t\tfor (let j = i + 1; j < n; j++) {\n\t\t\tconst nj = nodeList[j];\n\t\t\tif (nj === undefined) continue;\n\t\t\tfor (let k = j + 1; k < n; k++) {\n\t\t\t\tconst nk = nodeList[k];\n\t\t\t\tif (nk === undefined) continue;\n\n\t\t\t\tconst nodes: [NodeId, NodeId, NodeId] = [ni, nj, nk];\n\t\t\t\tconst edges: [number, number][] = [];\n\n\t\t\t\t// Check all 3 possible edges\n\t\t\t\tconst edgeChecks: [number, number][] = [\n\t\t\t\t\t[0, 1],\n\t\t\t\t\t[0, 2],\n\t\t\t\t\t[1, 2],\n\t\t\t\t];\n\n\t\t\t\tfor (const [u, v] of edgeChecks) {\n\t\t\t\t\tconst nu = nodes[u];\n\t\t\t\t\tconst nv = nodes[v];\n\t\t\t\t\tif (nu === undefined || nv === undefined) continue;\n\n\t\t\t\t\tif (graph.getEdge(nu, nv) !== undefined) {\n\t\t\t\t\t\tedges.push([u, v]);\n\t\t\t\t\t} else if (!graph.directed && graph.getEdge(nv, nu) !== undefined) {\n\t\t\t\t\t\tedges.push([u, v]);\n\t\t\t\t\t} else if (graph.directed && graph.getEdge(nv, nu) !== undefined) {\n\t\t\t\t\t\t// For directed graphs, store directed edge\n\t\t\t\t\t\tedges.push([v, u]);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst pattern = canonicalisePattern(3, edges);\n\t\t\t\tconst count = counts.get(pattern) ?? 0;\n\t\t\t\tcounts.set(pattern, count + 1);\n\n\t\t\t\tif (includeInstances && instances !== undefined) {\n\t\t\t\t\tif (!instances.has(pattern)) {\n\t\t\t\t\t\tinstances.set(pattern, []);\n\t\t\t\t\t}\n\t\t\t\t\tconst patternInstances = instances.get(pattern);\n\t\t\t\t\tif (patternInstances !== undefined) {\n\t\t\t\t\t\tpatternInstances.push([ni, nj, nk]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (instances !== undefined) {\n\t\treturn { counts, instances };\n\t}\n\treturn { counts };\n}\n\n/**\n * Enumerate all 4-node motifs in the graph.\n *\n * A 4-node motif can be one of 11 isomorphism classes for undirected graphs\n * (ranging from empty to complete K4), or many more for directed graphs.\n *\n * @param graph - The source graph\n * @param includeInstances - Whether to include node instances in the result\n * @returns Motif census with counts and optionally instances\n */\nfunction enumerate4NodeMotifs<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tincludeInstances: boolean,\n): MotifCensus {\n\tconst counts = new Map<string, number>();\n\tconst instances = includeInstances\n\t\t? new Map<string, NodeId[][]>()\n\t\t: undefined;\n\n\tconst nodeList = [...graph.nodeIds()];\n\tconst n = nodeList.length;\n\n\t// Iterate over all quadruples of nodes\n\tfor (let i = 0; i < n; i++) {\n\t\tconst ni = nodeList[i];\n\t\tif (ni === undefined) continue;\n\t\tfor (let j = i + 1; j < n; j++) {\n\t\t\tconst nj = nodeList[j];\n\t\t\tif (nj === undefined) continue;\n\t\t\tfor (let k = j + 1; k < n; k++) {\n\t\t\t\tconst nk = nodeList[k];\n\t\t\t\tif (nk === undefined) continue;\n\t\t\t\tfor (let l = k + 1; l < n; l++) {\n\t\t\t\t\tconst nl = nodeList[l];\n\t\t\t\t\tif (nl === undefined) continue;\n\n\t\t\t\t\tconst nodes: [NodeId, NodeId, NodeId, NodeId] = [ni, nj, nk, nl];\n\t\t\t\t\tconst edges: [number, number][] = [];\n\n\t\t\t\t\t// Check all 6 possible edges\n\t\t\t\t\tconst edgeChecks: [number, number][] = [\n\t\t\t\t\t\t[0, 1],\n\t\t\t\t\t\t[0, 2],\n\t\t\t\t\t\t[0, 3],\n\t\t\t\t\t\t[1, 2],\n\t\t\t\t\t\t[1, 3],\n\t\t\t\t\t\t[2, 3],\n\t\t\t\t\t];\n\n\t\t\t\t\tfor (const [u, v] of edgeChecks) {\n\t\t\t\t\t\tconst nu = nodes[u];\n\t\t\t\t\t\tconst nv = nodes[v];\n\t\t\t\t\t\tif (nu === undefined || nv === undefined) continue;\n\n\t\t\t\t\t\tif (graph.getEdge(nu, nv) !== undefined) {\n\t\t\t\t\t\t\tedges.push([u, v]);\n\t\t\t\t\t\t} else if (!graph.directed && graph.getEdge(nv, nu) !== undefined) {\n\t\t\t\t\t\t\tedges.push([u, v]);\n\t\t\t\t\t\t} else if (graph.directed && graph.getEdge(nv, nu) !== undefined) {\n\t\t\t\t\t\t\t// For directed graphs, store directed edge\n\t\t\t\t\t\t\tedges.push([v, u]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst pattern = canonicalisePattern(4, edges);\n\t\t\t\t\tconst count = counts.get(pattern) ?? 0;\n\t\t\t\t\tcounts.set(pattern, count + 1);\n\n\t\t\t\t\tif (includeInstances && instances !== undefined) {\n\t\t\t\t\t\tif (!instances.has(pattern)) {\n\t\t\t\t\t\t\tinstances.set(pattern, []);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst patternInstances = instances.get(pattern);\n\t\t\t\t\t\tif (patternInstances !== undefined) {\n\t\t\t\t\t\t\tpatternInstances.push([ni, nj, nk, nl]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (instances !== undefined) {\n\t\treturn { counts, instances };\n\t}\n\treturn { counts };\n}\n\n/**\n * Human-readable names for common 3-node motifs.\n */\nconst MOTIF_3_NAMES: ReadonlyMap<string, string> = new Map([\n\t[\"\", \"empty\"], // No edges\n\t[\"0-1\", \"1-edge\"], // Single edge\n\t[\"0-1,0-2\", \"2-star\"], // Path of length 2 (V-shape)\n\t[\"0-1,1-2\", \"path-3\"], // Path of length 2 (alternative)\n\t[\"0-1,0-2,1-2\", \"triangle\"], // Complete K3\n]);\n\n/**\n * Human-readable names for common 4-node motifs.\n */\nconst MOTIF_4_NAMES: ReadonlyMap<string, string> = new Map([\n\t[\"\", \"empty\"],\n\t[\"0-1\", \"1-edge\"],\n\t[\"0-1,0-2\", \"2-star\"],\n\t[\"0-1,0-2,0-3\", \"3-star\"],\n\t[\"0-1,0-2,1-2\", \"triangle\"], // K3 + isolated\n\t[\"0-1,0-2,1-2,2-3\", \"paw\"], // Triangle with tail\n\t[\"0-1,0-2,2-3\", \"path-4\"], // Path of length 3\n\t[\"0-1,0-2,1-3,2-3\", \"4-cycle\"], // Cycle C4\n\t[\"0-1,0-2,1-2,0-3,1-3\", \"diamond\"], // K4 minus one edge\n\t[\"0-1,0-2,0-3,1-2,1-3,2-3\", \"K4\"], // Complete graph\n]);\n\n/**\n * Enumerate motifs of a given size in the graph.\n *\n * This function counts all occurrences of each distinct motif type\n * (isomorphism class) in the graph. For graphs with many nodes,\n * 4-motif enumeration can be expensive (O(n^4) worst case).\n *\n * @param graph - The source graph\n * @param size - Motif size (3 or 4 nodes)\n * @returns Motif census with counts per motif type\n *\n * @example\n * ```typescript\n * // Count all triangles and other 3-node patterns\n * const census3 = enumerateMotifs(graph, 3);\n * console.log(`Triangles: ${census3.counts.get('0-1,0-2,1-2')}`);\n *\n * // Count 4-node patterns\n * const census4 = enumerateMotifs(graph, 4);\n * ```\n */\nexport function enumerateMotifs<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsize: 3 | 4,\n): MotifCensus {\n\t// Don't include instances by default for efficiency\n\treturn size === 3\n\t\t? enumerate3NodeMotifs(graph, false)\n\t\t: enumerate4NodeMotifs(graph, false);\n}\n\n/**\n * Enumerate motifs with optional instance tracking.\n *\n * @param graph - The source graph\n * @param size - Motif size (3 or 4 nodes)\n * @param includeInstances - Whether to include node instances\n * @returns Motif census with counts and optionally instances\n */\nexport function enumerateMotifsWithInstances<\n\tN extends NodeData,\n\tE extends EdgeData,\n>(\n\tgraph: ReadableGraph<N, E>,\n\tsize: 3 | 4,\n\tincludeInstances: boolean,\n): MotifCensus {\n\treturn size === 3\n\t\t? enumerate3NodeMotifs(graph, includeInstances)\n\t\t: enumerate4NodeMotifs(graph, includeInstances);\n}\n\n/**\n * Get a human-readable name for a motif pattern.\n *\n * @param pattern - The canonical pattern string\n * @param size - Motif size (3 or 4 nodes)\n * @returns A human-readable name, or the pattern itself if unknown\n */\nexport function getMotifName(pattern: string, size: 3 | 4): string {\n\tconst names = size === 3 ? MOTIF_3_NAMES : MOTIF_4_NAMES;\n\treturn names.get(pattern) ?? pattern;\n}\n","/**\n * Induced subgraph extraction.\n *\n * Extracts a subgraph containing exactly the specified nodes and all\n * edges between them from the original graph.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport { AdjacencyMapGraph } from \"../graph/adjacency-map\";\n\n/**\n * Extract the induced subgraph containing exactly the specified nodes.\n *\n * The induced subgraph includes all nodes from the input set that exist\n * in the original graph, plus all edges where both endpoints are in the set.\n *\n * @param graph - The source graph\n * @param nodes - Set of node IDs to include in the subgraph\n * @returns A new graph containing the induced subgraph\n *\n * @example\n * ```typescript\n * const subgraph = extractInducedSubgraph(graph, new Set(['A', 'B', 'C']));\n * ```\n */\nexport function extractInducedSubgraph<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tnodes: ReadonlySet<NodeId>,\n): AdjacencyMapGraph<N, E> {\n\tconst result = graph.directed\n\t\t? AdjacencyMapGraph.directed<N, E>()\n\t\t: AdjacencyMapGraph.undirected<N, E>();\n\n\t// Add nodes that exist in both the set and the graph\n\tfor (const nodeId of nodes) {\n\t\tconst nodeData = graph.getNode(nodeId);\n\t\tif (nodeData !== undefined) {\n\t\t\tresult.addNode(nodeData);\n\t\t}\n\t}\n\n\t// Add edges where both endpoints exist in the result\n\tfor (const edge of graph.edges()) {\n\t\tif (result.hasNode(edge.source) && result.hasNode(edge.target)) {\n\t\t\tresult.addEdge(edge);\n\t\t}\n\t}\n\n\treturn result;\n}\n","/**\n * Filtered subgraph extraction.\n *\n * Extracts a subgraph based on predicate functions for nodes and edges.\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport { AdjacencyMapGraph } from \"../graph/adjacency-map\";\n\n/**\n * Options for filtering a subgraph.\n */\nexport interface FilterOptions<N extends NodeData, E extends EdgeData> {\n\t/** Predicate to filter nodes. Return true to include the node. */\n\treadonly nodePredicate?: (node: N) => boolean;\n\t/** Predicate to filter edges. Return true to include the edge. */\n\treadonly edgePredicate?: (edge: E) => boolean;\n\t/** Whether to remove nodes that become isolated after edge filtering. Default: false. */\n\treadonly removeIsolated?: boolean;\n}\n\n/**\n * Extract a filtered subgraph based on node and edge predicates.\n *\n * Nodes are first filtered by the node predicate (if provided).\n * Edges are then filtered by the edge predicate (if provided), and only\n * retained if both endpoints pass the node predicate.\n *\n * @param graph - The source graph\n * @param options - Filter options specifying node/edge predicates\n * @returns A new graph containing only nodes and edges that pass the predicates\n *\n * @example\n * ```typescript\n * // Extract subgraph of high-weight nodes\n * const filtered = filterSubgraph(graph, {\n * nodePredicate: (node) => (node.weight ?? 0) > 0.5,\n * removeIsolated: true\n * });\n * ```\n */\nexport function filterSubgraph<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\toptions?: FilterOptions<N, E>,\n): AdjacencyMapGraph<N, E> {\n\tconst {\n\t\tnodePredicate,\n\t\tedgePredicate,\n\t\tremoveIsolated = false,\n\t} = options ?? {};\n\n\tconst result = graph.directed\n\t\t? AdjacencyMapGraph.directed<N, E>()\n\t\t: AdjacencyMapGraph.undirected<N, E>();\n\n\t// Track which nodes were added\n\tconst includedNodes = new Set<NodeId>();\n\n\t// Add nodes that pass the predicate\n\tfor (const nodeId of graph.nodeIds()) {\n\t\tconst nodeData = graph.getNode(nodeId);\n\t\tif (nodeData !== undefined) {\n\t\t\tif (nodePredicate === undefined || nodePredicate(nodeData)) {\n\t\t\t\tresult.addNode(nodeData);\n\t\t\t\tincludedNodes.add(nodeId);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Add edges that pass both endpoint and edge predicates\n\tfor (const edge of graph.edges()) {\n\t\tif (!includedNodes.has(edge.source) || !includedNodes.has(edge.target)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (edgePredicate === undefined || edgePredicate(edge)) {\n\t\t\tresult.addEdge(edge);\n\t\t}\n\t}\n\n\t// Remove isolated nodes if requested\n\tif (removeIsolated) {\n\t\tconst isolatedNodes: NodeId[] = [];\n\t\tfor (const nodeId of result.nodeIds()) {\n\t\t\tif (result.degree(nodeId) === 0) {\n\t\t\t\tisolatedNodes.push(nodeId);\n\t\t\t}\n\t\t}\n\t\tfor (const nodeId of isolatedNodes) {\n\t\t\tresult.removeNode(nodeId);\n\t\t}\n\t}\n\n\treturn result;\n}\n"],"mappings":"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"}
|