graphwise 1.4.0 → 1.4.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../src/expansion/base.ts","../../src/expansion/dome.ts","../../src/expansion/edge.ts","../../src/expansion/hae.ts","../../src/expansion/pipe.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/ranking/parse.ts","../../src/ranking/mi/adamic-adar.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/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/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 * BASE (Bidirectional Adaptive Seed Expansion) engine.\n *\n * Core algorithm implementing priority-ordered bidirectional expansion\n * with frontier collision detection for path discovery.\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 { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport { PriorityQueue } from \"../structures/priority-queue\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionPath,\n\tExpansionStats,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\n\n/**\n * Internal queue entry for frontier expansion.\n */\ninterface QueueEntry {\n\tnodeId: NodeId;\n\tfrontierIndex: number;\n\tpredecessor: NodeId | null;\n}\n\n/**\n * Default priority function - degree-ordered (DOME).\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 * Run BASE expansion algorithm.\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 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 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. This allows the seed to be properly expanded.\n\t\tpredecessors[i]?.set(seedNode, null);\n\n\t\tconst context = createPriorityContext(\n\t\t\tgraph,\n\t\t\tseedNode,\n\t\t\ti,\n\t\t\tvisitedByFrontier,\n\t\t\t[],\n\t\t\t0,\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 allVisited = new Set<NodeId>();\n\tconst sampledEdges = new Set<string>();\n\tconst discoveredPaths: ExpansionPath[] = [];\n\tlet iterations = 0;\n\tlet edgesTraversed = 0;\n\tlet termination: ExpansionStats[\"termination\"] = \"exhausted\";\n\n\t// Main expansion loop\n\tconst continueExpansion = (): boolean => {\n\t\tif (maxIterations > 0 && iterations >= maxIterations) {\n\t\t\ttermination = \"limit\";\n\t\t\treturn false;\n\t\t}\n\t\tif (maxNodes > 0 && allVisited.size >= maxNodes) {\n\t\t\ttermination = \"limit\";\n\t\t\treturn false;\n\t\t}\n\t\tif (maxPaths > 0 && discoveredPaths.length >= maxPaths) {\n\t\t\ttermination = \"limit\";\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t};\n\n\twhile (continueExpansion()) {\n\t\t// Find frontier with 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 - 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\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 path\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\n\t\tconst neighbours = graph.neighbours(nodeId);\n\t\tfor (const neighbour of neighbours) {\n\t\t\tedgesTraversed++;\n\n\t\t\t// Track sampled edge\n\t\t\tconst edgeKey =\n\t\t\t\tnodeId < neighbour\n\t\t\t\t\t? `${nodeId}::${neighbour}`\n\t\t\t\t\t: `${neighbour}::${nodeId}`;\n\t\t\tsampledEdges.add(edgeKey);\n\n\t\t\t// Skip if already visited by this frontier\n\t\t\tconst frontierVisited = visitedByFrontier[activeFrontier];\n\t\t\tif (frontierVisited === undefined || frontierVisited.has(neighbour)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst context = createPriorityContext(\n\t\t\t\tgraph,\n\t\t\t\tneighbour,\n\t\t\t\tactiveFrontier,\n\t\t\t\tvisitedByFrontier,\n\t\t\t\tdiscoveredPaths,\n\t\t\t\titerations + 1,\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 tuples\n\tconst edgeTuples = new Set<readonly [NodeId, NodeId]>();\n\tfor (const edgeKey of sampledEdges) {\n\t\tconst parts = edgeKey.split(\"::\");\n\t\tif (parts.length === 2) {\n\t\t\tconst source = parts[0];\n\t\t\tconst target = parts[1];\n\t\t\tif (source !== undefined && target !== undefined) {\n\t\t\t\tedgeTuples.add([source, target] as const);\n\t\t\t}\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 priority context for a node.\n */\nfunction createPriorityContext<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tnodeId: NodeId,\n\tfrontierIndex: number,\n\tvisitedByFrontier: readonly Map<NodeId, number>[],\n\tdiscoveredPaths: readonly ExpansionPath[],\n\titeration: number,\n): PriorityContext<N, E> {\n\tconst combinedVisited = new Map<NodeId, number>();\n\tfor (const frontierMap of visitedByFrontier) {\n\t\tfor (const [id, idx] of frontierMap) {\n\t\t\tcombinedVisited.set(id, idx);\n\t\t}\n\t}\n\n\tconst allVisited = new Set<NodeId>(combinedVisited.keys());\n\n\treturn {\n\t\tgraph,\n\t\tdegree: graph.degree(nodeId),\n\t\tfrontierIndex,\n\t\tvisitedByFrontier: combinedVisited,\n\t\tallVisited,\n\t\tdiscoveredPaths,\n\t\titeration,\n\t};\n}\n\n/**\n * Reconstruct path from collision point.\n */\nfunction 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 result for early termination.\n */\nfunction emptyResult(algorithm: string, 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,\n\t\t\ttermination: \"exhausted\",\n\t\t},\n\t};\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 * 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\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 * Priority function using local type entropy.\n * Lower values = higher priority (expanded first).\n */\nfunction edgePriority<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// Collect neighbour types\n\tconst neighbourTypes: string[] = [];\n\tfor (const neighbour of neighbours) {\n\t\tconst node = graph.getNode(neighbour);\n\t\tneighbourTypes.push(node?.type ?? \"default\");\n\t}\n\n\t// Compute local type entropy (normalised Shannon entropy)\n\tconst entropy = localTypeEntropy(neighbourTypes);\n\n\t// Priority = 1 / (entropy + ε) * log(degree + 1)\n\t// High entropy (diverse types) → lower priority (expanded sooner)\n\treturn (1 / (entropy + EPSILON)) * Math.log(context.degree + 1);\n}\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 base(graph, seeds, {\n\t\t...config,\n\t\tpriority: edgePriority,\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 * 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 = new Set(graph.neighbours(nodeId));\n\n\t// Count how many neighbours have been visited by OTHER frontiers\n\tlet pathPotential = 0;\n\tfor (const [visitedId, frontierIdx] of context.visitedByFrontier) {\n\t\t// Only count if visited by a different frontier\n\t\tif (frontierIdx !== context.frontierIndex && neighbours.has(visitedId)) {\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 * 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\";\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// Initialise salience counts from existing paths\n\t\t\tfor (const path of context.discoveredPaths) {\n\t\t\t\tfor (const node of path.nodes) {\n\t\t\t\t\tsalienceCounts.set(node, (salienceCounts.get(node) ?? 0) + 1);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Update salience counts for newly discovered paths\n\t\tif (pathCount > lastPathCount) {\n\t\t\tfor (let i = lastPathCount; i < pathCount; i++) {\n\t\t\t\tconst path = context.discoveredPaths[i];\n\t\t\t\tif (path !== undefined) {\n\t\t\t\t\tfor (const node of path.nodes) {\n\t\t\t\t\t\tsalienceCounts.set(node, (salienceCounts.get(node) ?? 0) + 1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastPathCount = pathCount;\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 { neighbourSet, neighbourOverlap } 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\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 and union\n\tconst { intersection, union } = neighbourOverlap(\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t);\n\n\t// Avoid division by zero\n\tif (union === 0) {\n\t\treturn 0;\n\t}\n\n\tconst score = intersection / union;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\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/**\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\ttotalMI += jaccard(graph, nodeId, fromNodeId);\n\t\t\ttotalMI += jaccard(graph, 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\";\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 existing paths\n\t\t\tfor (const path of context.discoveredPaths) {\n\t\t\t\tfor (const node of path.nodes) {\n\t\t\t\t\tsalienceCounts.set(node, (salienceCounts.get(node) ?? 0) + 1);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Update salience counts for newly discovered paths in phase 2\n\t\tif (inPhase2 && pathCount > lastPathCount) {\n\t\t\tfor (let i = lastPathCount; i < pathCount; i++) {\n\t\t\t\tconst path = context.discoveredPaths[i];\n\t\t\t\tif (path !== undefined) {\n\t\t\t\t\tfor (const node of path.nodes) {\n\t\t\t\t\t\tsalienceCounts.set(node, (salienceCounts.get(node) ?? 0) + 1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastPathCount = pathCount;\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 = new Set(graph.neighbours(nodeId));\n\t\tlet pathPotential = 0;\n\n\t\tfor (const [visitedId, frontierIdx] of context.visitedByFrontier) {\n\t\t\tif (\n\t\t\t\tfrontierIdx !== context.frontierIndex &&\n\t\t\t\tnodeNeighbours.has(visitedId)\n\t\t\t) {\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\";\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 - MI(source, neighbour)\n * Higher 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 graph = context.graph;\n\tconst frontierIndex = context.frontierIndex;\n\n\t// Get the seed node for this frontier\n\t// We need to find the predecessor to compute MI\n\tlet maxMi = 0;\n\n\t// Compute average MI to all visited nodes in this frontier\n\tlet totalMi = 0;\n\tlet count = 0;\n\n\tfor (const [visitedId, idx] of context.visitedByFrontier) {\n\t\tif (idx === frontierIndex && visitedId !== nodeId) {\n\t\t\tconst edgeMi = mi(graph, visitedId, nodeId);\n\t\t\ttotalMi += edgeMi;\n\t\t\tcount++;\n\t\t\tif (edgeMi > maxMi) {\n\t\t\t\tmaxMi = edgeMi;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Use average MI (higher = more important = lower priority value)\n\tconst avgMi = count > 0 ? totalMi / count : 0;\n\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\";\n\n/**\n * PIPE priority function.\n *\n * Priority = 1 / (1 + bridge_score)\n * Bridge score = neighbourhood overlap with other frontiers\n * Higher bridge score = more likely to be on paths = explored first\n */\nfunction warpPriority<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\tconst graph = context.graph;\n\tconst currentFrontier = context.frontierIndex;\n\tconst nodeNeighbours = new Set(graph.neighbours(nodeId));\n\n\t// Count how many neighbours are visited by other frontiers\n\tlet bridgeScore = 0;\n\n\tfor (const [visitedId, frontierIdx] of context.visitedByFrontier) {\n\t\tif (frontierIdx !== currentFrontier && nodeNeighbours.has(visitedId)) {\n\t\t\tbridgeScore++;\n\t\t}\n\t}\n\n\t// Also consider discovered paths - nodes on existing paths are valuable\n\tfor (const path of context.discoveredPaths) {\n\t\tif (path.nodes.includes(nodeId)) {\n\t\t\tbridgeScore += 2; // Bonus for being on discovered paths\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\";\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 * SAGE priority function.\n *\n * Combines degree with salience:\n * Priority = (1 - w) * degree + w * (1 - avg_salience)\n * Lower values = higher 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 graph = context.graph;\n\tconst degree = context.degree;\n\tconst frontierIndex = context.frontierIndex;\n\n\t// Compute average salience to visited nodes in this frontier\n\tlet totalSalience = 0;\n\tlet count = 0;\n\n\tfor (const [visitedId, idx] of context.visitedByFrontier) {\n\t\tif (idx === frontierIndex && visitedId !== nodeId) {\n\t\t\ttotalSalience += mi(graph, visitedId, nodeId);\n\t\t\tcount++;\n\t\t}\n\t}\n\n\tconst avgSalience = count > 0 ? totalSalience / count : 0;\n\n\t// Combine degree with salience\n\t// Lower priority value = expanded first\n\t// High salience should lower priority value\n\tconst degreeComponent = (1 - salienceWeight) * 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\";\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 priority function (phase 2).\n *\n * Uses learned MI threshold to prioritise high-MI edges.\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 graph = context.graph;\n\tconst frontierIndex = context.frontierIndex;\n\n\t// Compute average MI to visited nodes\n\tlet totalMi = 0;\n\tlet count = 0;\n\n\tfor (const [visitedId, idx] of context.visitedByFrontier) {\n\t\tif (idx === frontierIndex && visitedId !== nodeId) {\n\t\t\ttotalMi += mi(graph, visitedId, nodeId);\n\t\t\tcount++;\n\t\t}\n\t}\n\n\tconst avgMi = count > 0 ? totalMi / count : 0;\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\";\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 * Compute bridge score (how many other frontiers visit neighbours).\n */\nfunction bridgeScore<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\tconst currentFrontier = context.frontierIndex;\n\tconst nodeNeighbours = new Set(context.graph.neighbours(nodeId));\n\n\tlet score = 0;\n\tfor (const [visitedId, idx] of context.visitedByFrontier) {\n\t\tif (idx !== currentFrontier && nodeNeighbours.has(visitedId)) {\n\t\t\tscore++;\n\t\t}\n\t}\n\n\treturn score;\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 = bridgeScore(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 -degree; // Negative to prioritise low degree\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 * 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 * 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 { neighbourSet, neighbourOverlap } 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\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute Jaccard\n\tconst { intersection, union } = neighbourOverlap(\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t);\n\tconst jaccard = union > 0 ? intersection / union : 0;\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 = jaccard / 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 { neighbourSet, neighbourOverlap } 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\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute Jaccard\n\tconst { intersection, union } = neighbourOverlap(\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t);\n\tconst jaccard = union > 0 ? intersection / union : 0;\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 = jaccard * 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 { neighbourSet, neighbourOverlap } 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\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute Jaccard\n\tconst { intersection, union } = neighbourOverlap(\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t);\n\tconst jaccard = union > 0 ? intersection / union : 0;\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 = jaccard * 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 { neighbourSet, neighbourOverlap, 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\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute Jaccard\n\tconst { intersection, union } = neighbourOverlap(\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t);\n\tconst jaccard = union > 0 ? intersection / union : 0;\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, jaccard);\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, jaccard);\n\t}\n\n\tconst rarity = Math.log(graph.edgeCount / edgeTypeCount);\n\tconst score = jaccard * 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 { neighbourSet, neighbourOverlap, 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\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute Jaccard\n\tconst { intersection, union } = neighbourOverlap(\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t);\n\tconst jaccard = union > 0 ? intersection / union : 0;\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, jaccard);\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, jaccard);\n\t}\n\n\tconst sourceRarity = Math.log(graph.nodeCount / sourceTypeCount);\n\tconst targetRarity = Math.log(graph.nodeCount / targetTypeCount);\n\n\tconst score = jaccard * 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 { neighbourSet, neighbourOverlap } from \"../../utils\";\nimport type { AdaptiveMIConfig } from \"./types\";\nimport { jaccard } from \"./jaccard\";\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// Component 1: Structural similarity (Jaccard)\n\tconst structural = jaccard(graph, source, target, { epsilon });\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\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\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 * 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, ScoredPath } from \"./types\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Normalise and sort\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score / maxScore,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn {\n\t\tpaths: ranked,\n\t\tmethod: \"shortest\",\n\t};\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, ScoredPath } from \"./types\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"degree-sum\",\n\t\t};\n\t}\n\n\t// Normalise and sort\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 {\n\t\tpaths: ranked,\n\t\tmethod: \"degree-sum\",\n\t};\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, ScoredPath } from \"./types\";\nimport { jaccard } from \"../mi/jaccard\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"widest-path\",\n\t\t};\n\t}\n\n\t// Normalise and sort\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score / maxScore,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn {\n\t\tpaths: ranked,\n\t\tmethod: \"widest-path\",\n\t};\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, ScoredPath } from \"./types\";\nimport { jaccard } from \"../mi/jaccard\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"jaccard-arithmetic\",\n\t\t};\n\t}\n\n\t// Normalise and sort\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 {\n\t\tpaths: ranked,\n\t\tmethod: \"jaccard-arithmetic\",\n\t};\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, ScoredPath } from \"./types\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"pagerank\",\n\t\t};\n\t}\n\n\t// Normalise and sort\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score / maxScore,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn {\n\t\tpaths: ranked,\n\t\tmethod: \"pagerank\",\n\t};\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, ScoredPath } from \"./types\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"betweenness\",\n\t\t};\n\t}\n\n\t// Normalise and sort\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score / maxScore,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn {\n\t\tpaths: ranked,\n\t\tmethod: \"betweenness\",\n\t};\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, ScoredPath } from \"./types\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"katz\",\n\t\t};\n\t}\n\n\t// Normalise and sort\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score / maxScore,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn {\n\t\tpaths: ranked,\n\t\tmethod: \"katz\",\n\t};\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, ScoredPath } from \"./types\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"communicability\",\n\t\t};\n\t}\n\n\t// Normalise and sort\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score / maxScore,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn {\n\t\tpaths: ranked,\n\t\tmethod: \"communicability\",\n\t};\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, ScoredPath } from \"./types\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"resistance-distance\",\n\t\t};\n\t}\n\n\t// Normalise and sort\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score / maxScore,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn {\n\t\tpaths: ranked,\n\t\tmethod: \"resistance-distance\",\n\t};\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, ScoredPath } from \"./types\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case (very unlikely)\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"random\",\n\t\t};\n\t}\n\n\t// Normalise and sort\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score / maxScore,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn {\n\t\tpaths: ranked,\n\t\tmethod: \"random\",\n\t};\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.ts","../../src/expansion/dome.ts","../../src/expansion/edge.ts","../../src/expansion/hae.ts","../../src/expansion/pipe.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/ranking/parse.ts","../../src/ranking/mi/adamic-adar.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/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/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 * BASE (Bidirectional Adaptive Seed Expansion) engine.\n *\n * Core algorithm implementing priority-ordered bidirectional expansion\n * with frontier collision detection for path discovery.\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 { NodeId, NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport { PriorityQueue } from \"../structures/priority-queue\";\nimport type {\n\tSeed,\n\tExpansionResult,\n\tExpansionPath,\n\tExpansionStats,\n\tExpansionConfig,\n\tPriorityContext,\n} from \"./types\";\n\n/**\n * Internal queue entry for frontier expansion.\n */\ninterface QueueEntry {\n\tnodeId: NodeId;\n\tfrontierIndex: number;\n\tpredecessor: NodeId | null;\n}\n\n/**\n * Default priority function - degree-ordered (DOME).\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 * Run BASE expansion algorithm.\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 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. This allows the seed to be properly expanded.\n\t\tpredecessors[i]?.set(seedNode, null);\n\t\tcombinedVisited.set(seedNode, i);\n\t\tallVisited.add(seedNode);\n\n\t\tconst context = createPriorityContext(\n\t\t\tgraph,\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);\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\t// Main expansion loop\n\tconst continueExpansion = (): boolean => {\n\t\tif (maxIterations > 0 && iterations >= maxIterations) {\n\t\t\ttermination = \"limit\";\n\t\t\treturn false;\n\t\t}\n\t\tif (maxNodes > 0 && allVisited.size >= maxNodes) {\n\t\t\ttermination = \"limit\";\n\t\t\treturn false;\n\t\t}\n\t\tif (maxPaths > 0 && discoveredPaths.length >= maxPaths) {\n\t\t\ttermination = \"limit\";\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t};\n\n\twhile (continueExpansion()) {\n\t\t// Find frontier with 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 - 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 path\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\n\t\tconst neighbours = graph.neighbours(nodeId);\n\t\tfor (const neighbour of neighbours) {\n\t\t\tedgesTraversed++;\n\n\t\t\t// Track sampled edge\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 frontierVisited = visitedByFrontier[activeFrontier];\n\t\t\tif (frontierVisited === undefined || frontierVisited.has(neighbour)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst context = createPriorityContext(\n\t\t\t\tgraph,\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);\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 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 priority context for a node.\n */\nfunction createPriorityContext<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tnodeId: NodeId,\n\tfrontierIndex: number,\n\tcombinedVisited: ReadonlyMap<NodeId, number>,\n\tallVisited: ReadonlySet<NodeId>,\n\tdiscoveredPaths: readonly ExpansionPath[],\n\titeration: number,\n): PriorityContext<N, E> {\n\treturn {\n\t\tgraph,\n\t\tdegree: graph.degree(nodeId),\n\t\tfrontierIndex,\n\t\tvisitedByFrontier: combinedVisited,\n\t\tallVisited,\n\t\tdiscoveredPaths,\n\t\titeration,\n\t};\n}\n\n/**\n * Reconstruct path from collision point.\n */\nfunction 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 result for early termination.\n */\nfunction emptyResult(algorithm: string, 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,\n\t\t\ttermination: \"exhausted\",\n\t\t},\n\t};\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 * 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\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 * Priority function using local type entropy.\n * Lower values = higher priority (expanded first).\n */\nfunction edgePriority<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// Collect neighbour types\n\tconst neighbourTypes: string[] = [];\n\tfor (const neighbour of neighbours) {\n\t\tconst node = graph.getNode(neighbour);\n\t\tneighbourTypes.push(node?.type ?? \"default\");\n\t}\n\n\t// Compute local type entropy (normalised Shannon entropy)\n\tconst entropy = localTypeEntropy(neighbourTypes);\n\n\t// Priority = 1 / (entropy + ε) * log(degree + 1)\n\t// High entropy (diverse types) → lower priority (expanded sooner)\n\treturn (1 / (entropy + EPSILON)) * Math.log(context.degree + 1);\n}\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 base(graph, seeds, {\n\t\t...config,\n\t\tpriority: edgePriority,\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 * 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 * 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\";\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\tfor (let i = lastPathCount; i < pathCount; i++) {\n\t\t\t\tconst path = context.discoveredPaths[i];\n\t\t\t\tif (path !== undefined) {\n\t\t\t\t\tfor (const node of path.nodes) {\n\t\t\t\t\t\tsalienceCounts.set(node, (salienceCounts.get(node) ?? 0) + 1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastPathCount = pathCount;\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 { neighbourSet, neighbourOverlap } 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\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 and union\n\tconst { intersection, union } = neighbourOverlap(\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t);\n\n\t// Avoid division by zero\n\tif (union === 0) {\n\t\treturn 0;\n\t}\n\n\tconst score = intersection / union;\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, score);\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\";\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 existing paths\n\t\t\tfor (const path of context.discoveredPaths) {\n\t\t\t\tfor (const node of path.nodes) {\n\t\t\t\t\tsalienceCounts.set(node, (salienceCounts.get(node) ?? 0) + 1);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Update salience counts for newly discovered paths in phase 2\n\t\tif (inPhase2 && pathCount > lastPathCount) {\n\t\t\tfor (let i = lastPathCount; i < pathCount; i++) {\n\t\t\t\tconst path = context.discoveredPaths[i];\n\t\t\t\tif (path !== undefined) {\n\t\t\t\t\tfor (const node of path.nodes) {\n\t\t\t\t\t\tsalienceCounts.set(node, (salienceCounts.get(node) ?? 0) + 1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastPathCount = pathCount;\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\";\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 - MI(source, neighbour)\n * Higher 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 graph = context.graph;\n\tconst frontierIndex = context.frontierIndex;\n\n\t// Get the seed node for this frontier\n\t// We need to find the predecessor to compute MI\n\tlet maxMi = 0;\n\n\t// Compute average MI to all visited nodes in this frontier\n\tlet totalMi = 0;\n\tlet count = 0;\n\n\tfor (const [visitedId, idx] of context.visitedByFrontier) {\n\t\tif (idx === frontierIndex && visitedId !== nodeId) {\n\t\t\tconst edgeMi = mi(graph, visitedId, nodeId);\n\t\t\ttotalMi += edgeMi;\n\t\t\tcount++;\n\t\t\tif (edgeMi > maxMi) {\n\t\t\t\tmaxMi = edgeMi;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Use average MI (higher = more important = lower priority value)\n\tconst avgMi = count > 0 ? totalMi / count : 0;\n\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\";\n\n/**\n * PIPE priority function.\n *\n * Priority = 1 / (1 + bridge_score)\n * Bridge score = neighbourhood overlap with other frontiers\n * Higher bridge score = more likely to be on paths = explored first\n */\nfunction warpPriority<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\tconst graph = context.graph;\n\tconst currentFrontier = context.frontierIndex;\n\tconst nodeNeighbours = new Set(graph.neighbours(nodeId));\n\n\t// Count how many neighbours are visited by other frontiers\n\tlet bridgeScore = 0;\n\n\tfor (const [visitedId, frontierIdx] of context.visitedByFrontier) {\n\t\tif (frontierIdx !== currentFrontier && nodeNeighbours.has(visitedId)) {\n\t\t\tbridgeScore++;\n\t\t}\n\t}\n\n\t// Also consider discovered paths - nodes on existing paths are valuable\n\tfor (const path of context.discoveredPaths) {\n\t\tif (path.nodes.includes(nodeId)) {\n\t\t\tbridgeScore += 2; // Bonus for being on discovered paths\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\";\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 * SAGE priority function.\n *\n * Combines degree with salience:\n * Priority = (1 - w) * degree + w * (1 - avg_salience)\n * Lower values = higher 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 graph = context.graph;\n\tconst degree = context.degree;\n\tconst frontierIndex = context.frontierIndex;\n\n\t// Compute average salience to visited nodes in this frontier\n\tlet totalSalience = 0;\n\tlet count = 0;\n\n\tfor (const [visitedId, idx] of context.visitedByFrontier) {\n\t\tif (idx === frontierIndex && visitedId !== nodeId) {\n\t\t\ttotalSalience += mi(graph, visitedId, nodeId);\n\t\t\tcount++;\n\t\t}\n\t}\n\n\tconst avgSalience = count > 0 ? totalSalience / count : 0;\n\n\t// Combine degree with salience\n\t// Lower priority value = expanded first\n\t// High salience should lower priority value\n\tconst degreeComponent = (1 - salienceWeight) * 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\";\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 priority function (phase 2).\n *\n * Uses learned MI threshold to prioritise high-MI edges.\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 graph = context.graph;\n\tconst frontierIndex = context.frontierIndex;\n\n\t// Compute average MI to visited nodes\n\tlet totalMi = 0;\n\tlet count = 0;\n\n\tfor (const [visitedId, idx] of context.visitedByFrontier) {\n\t\tif (idx === frontierIndex && visitedId !== nodeId) {\n\t\t\ttotalMi += mi(graph, visitedId, nodeId);\n\t\t\tcount++;\n\t\t}\n\t}\n\n\tconst avgMi = count > 0 ? totalMi / count : 0;\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\";\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 * Compute bridge score (how many other frontiers visit neighbours).\n */\nfunction bridgeScore<N extends NodeData, E extends EdgeData>(\n\tnodeId: string,\n\tcontext: PriorityContext<N, E>,\n): number {\n\tconst currentFrontier = context.frontierIndex;\n\tconst nodeNeighbours = new Set(context.graph.neighbours(nodeId));\n\n\tlet score = 0;\n\tfor (const [visitedId, idx] of context.visitedByFrontier) {\n\t\tif (idx !== currentFrontier && nodeNeighbours.has(visitedId)) {\n\t\t\tscore++;\n\t\t}\n\t}\n\n\treturn score;\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 = bridgeScore(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 -degree; // Negative to prioritise low degree\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 * 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 * 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 { neighbourSet, neighbourOverlap } 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\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute Jaccard\n\tconst { intersection, union } = neighbourOverlap(\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t);\n\tconst jaccard = union > 0 ? intersection / union : 0;\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 = jaccard / 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 { neighbourSet, neighbourOverlap } 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\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute Jaccard\n\tconst { intersection, union } = neighbourOverlap(\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t);\n\tconst jaccard = union > 0 ? intersection / union : 0;\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 = jaccard * 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 { neighbourSet, neighbourOverlap } 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\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute Jaccard\n\tconst { intersection, union } = neighbourOverlap(\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t);\n\tconst jaccard = union > 0 ? intersection / union : 0;\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 = jaccard * 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 { neighbourSet, neighbourOverlap, 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\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute Jaccard\n\tconst { intersection, union } = neighbourOverlap(\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t);\n\tconst jaccard = union > 0 ? intersection / union : 0;\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, jaccard);\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, jaccard);\n\t}\n\n\tconst rarity = Math.log(graph.edgeCount / edgeTypeCount);\n\tconst score = jaccard * 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 { neighbourSet, neighbourOverlap, 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\t// Get neighbourhoods, excluding opposite endpoint\n\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\n\t// Compute Jaccard\n\tconst { intersection, union } = neighbourOverlap(\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t);\n\tconst jaccard = union > 0 ? intersection / union : 0;\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, jaccard);\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, jaccard);\n\t}\n\n\tconst sourceRarity = Math.log(graph.nodeCount / sourceTypeCount);\n\tconst targetRarity = Math.log(graph.nodeCount / targetTypeCount);\n\n\tconst score = jaccard * 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 { neighbourSet, neighbourOverlap } from \"../../utils\";\nimport type { AdaptiveMIConfig } from \"./types\";\nimport { jaccard } from \"./jaccard\";\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// Component 1: Structural similarity (Jaccard)\n\tconst structural = jaccard(graph, source, target, { epsilon });\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\tconst sourceNeighbours = neighbourSet(graph, source, target);\n\tconst targetNeighbours = neighbourSet(graph, target, source);\n\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 * 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, ScoredPath } from \"./types\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Normalise and sort\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score / maxScore,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn {\n\t\tpaths: ranked,\n\t\tmethod: \"shortest\",\n\t};\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, ScoredPath } from \"./types\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"degree-sum\",\n\t\t};\n\t}\n\n\t// Normalise and sort\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 {\n\t\tpaths: ranked,\n\t\tmethod: \"degree-sum\",\n\t};\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, ScoredPath } from \"./types\";\nimport { jaccard } from \"../mi/jaccard\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"widest-path\",\n\t\t};\n\t}\n\n\t// Normalise and sort\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score / maxScore,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn {\n\t\tpaths: ranked,\n\t\tmethod: \"widest-path\",\n\t};\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, ScoredPath } from \"./types\";\nimport { jaccard } from \"../mi/jaccard\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"jaccard-arithmetic\",\n\t\t};\n\t}\n\n\t// Normalise and sort\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 {\n\t\tpaths: ranked,\n\t\tmethod: \"jaccard-arithmetic\",\n\t};\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, ScoredPath } from \"./types\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"pagerank\",\n\t\t};\n\t}\n\n\t// Normalise and sort\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score / maxScore,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn {\n\t\tpaths: ranked,\n\t\tmethod: \"pagerank\",\n\t};\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, ScoredPath } from \"./types\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"betweenness\",\n\t\t};\n\t}\n\n\t// Normalise and sort\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score / maxScore,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn {\n\t\tpaths: ranked,\n\t\tmethod: \"betweenness\",\n\t};\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, ScoredPath } from \"./types\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"katz\",\n\t\t};\n\t}\n\n\t// Normalise and sort\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score / maxScore,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn {\n\t\tpaths: ranked,\n\t\tmethod: \"katz\",\n\t};\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, ScoredPath } from \"./types\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"communicability\",\n\t\t};\n\t}\n\n\t// Normalise and sort\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score / maxScore,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn {\n\t\tpaths: ranked,\n\t\tmethod: \"communicability\",\n\t};\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, ScoredPath } from \"./types\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"resistance-distance\",\n\t\t};\n\t}\n\n\t// Normalise and sort\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score / maxScore,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn {\n\t\tpaths: ranked,\n\t\tmethod: \"resistance-distance\",\n\t};\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, ScoredPath } from \"./types\";\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\t// Find max for normalisation\n\tconst maxScore = Math.max(...scored.map((s) => s.score));\n\n\t// Handle zero-max case (very unlikely)\n\tif (maxScore === 0) {\n\t\treturn {\n\t\t\tpaths: paths.map((path) => ({\n\t\t\t\t...path,\n\t\t\t\tscore: 0,\n\t\t\t})),\n\t\t\tmethod: \"random\",\n\t\t};\n\t}\n\n\t// Normalise and sort\n\tconst ranked: ScoredPath[] = scored\n\t\t.map(({ path, score }) => ({\n\t\t\t...path,\n\t\t\tscore: includeScores ? score / maxScore : score / maxScore,\n\t\t}))\n\t\t.sort((a, b) => b.score - a.score);\n\n\treturn {\n\t\tpaths: ranked,\n\t\tmethod: \"random\",\n\t};\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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"}