UNPKG

@graphty/algorithms

Version:

Graph algorithms library for browser environments implemented in TypeScript

192 lines 7.14 kB
import { bfsWithPathCounting } from "../traversal/bfs-variants.js"; /** * Run single-source shortest path computation using BFS */ function brandesSingleSource(graph, source, optimized) { const result = bfsWithPathCounting(graph, source, optimized !== undefined ? { optimized } : {}); return { stack: result.stack, predecessors: result.predecessors, sigma: result.sigma, distance: result.distances, }; } /** * Accumulate betweenness contributions from a single source */ function accumulateBetweenness(result, source, centrality, options) { const { stack, predecessors, sigma } = result; const delta = new Map(); // Initialize delta for (const node of stack) { delta.set(node, 0); } // Accumulation - back-propagation of dependencies // Process nodes in reverse BFS order for (let i = stack.length - 1; i >= 0; i--) { const w = stack[i]; if (!w) { continue; } const wPreds = predecessors.get(w) ?? []; const wSigma = sigma.get(w) ?? 0; const wDelta = delta.get(w) ?? 0; for (const v of wPreds) { const vSigma = sigma.get(v) ?? 0; const vDelta = delta.get(v) ?? 0; if (vSigma > 0 && wSigma > 0) { let contribution = (vSigma / wSigma) * (1 + wDelta); // Apply endpoints option: when false, exclude endpoint contributions if (!options.endpoints) { // For standard betweenness, don't count paths that only involve endpoints const isTargetEndpoint = wPreds.length === 0 && w !== source; if (isTargetEndpoint) { contribution = 0; // Exclude endpoint contributions } } delta.set(v, vDelta + contribution); } } if (w !== source) { const currentCentrality = centrality[String(w)] ?? 0; centrality[String(w)] = currentCentrality + wDelta; } } } /** * Accumulate edge betweenness contributions from a single source */ function accumulateEdgeBetweenness(result, source, edgeCentrality, options) { const { stack, predecessors, sigma } = result; const delta = new Map(); // Initialize delta for (const node of stack) { delta.set(node, 0); } // Accumulation for edges // Process nodes in reverse BFS order for (let i = stack.length - 1; i >= 0; i--) { const w = stack[i]; if (!w) { continue; } const wPreds = predecessors.get(w) ?? []; const wSigma = sigma.get(w) ?? 0; const wDelta = delta.get(w) ?? 0; for (const v of wPreds) { const vSigma = sigma.get(v) ?? 0; if (vSigma > 0 && wSigma > 0) { let edgeContribution = (vSigma / wSigma) * (1 + wDelta); // Apply endpoints option for edge betweenness if (!options.endpoints) { const isTargetEndpoint = wPreds.length === 0 && w !== source; if (isTargetEndpoint) { edgeContribution = 0; // Exclude endpoint contributions } } // Update edge centrality const edgeKey = `${String(v)}-${String(w)}`; const currentEdgeCentrality = edgeCentrality.get(edgeKey) ?? 0; edgeCentrality.set(edgeKey, currentEdgeCentrality + edgeContribution); // Update node delta const vDelta = delta.get(v) ?? 0; delta.set(v, vDelta + edgeContribution); } } } } /** * Apply normalization to centrality values */ function normalizeCentrality(centrality, nodeCount, isDirected) { const n = nodeCount; const normalizationFactor = isDirected ? (n - 1) * (n - 2) : ((n - 1) * (n - 2)) / 2; if (normalizationFactor > 0) { for (const key in centrality) { const value = centrality[key]; if (value !== undefined) { centrality[key] = value / normalizationFactor; } } } } /** * Calculate betweenness centrality for all nodes using Brandes' algorithm */ export function betweennessCentrality(graph, options = {}) { const nodes = Array.from(graph.nodes()).map((node) => node.id); const centrality = {}; // Initialize centrality scores for (const nodeId of nodes) { centrality[String(nodeId)] = 0; } // Brandes' algorithm - run from each source for (const source of nodes) { const result = brandesSingleSource(graph, source, options.optimized); accumulateBetweenness(result, source, centrality, options); } // For undirected graphs, divide by 2 (each shortest path is counted twice) if (!graph.isDirected) { for (const nodeId of nodes) { const key = String(nodeId); const value = centrality[key]; if (value !== undefined) { centrality[key] = value / 2; } } } // Normalization if (options.normalized) { normalizeCentrality(centrality, nodes.length, graph.isDirected); } return centrality; } /** * Calculate betweenness centrality for a specific node */ export function nodeBetweennessCentrality(graph, targetNode, options = {}) { if (!graph.hasNode(targetNode)) { throw new Error(`Node ${String(targetNode)} not found in graph`); } const allCentralities = betweennessCentrality(graph, options); return allCentralities[String(targetNode)] ?? 0; } /** * Calculate edge betweenness centrality */ export function edgeBetweennessCentrality(graph, options = {}) { const nodes = Array.from(graph.nodes()).map((node) => node.id); const edgeCentrality = new Map(); // Initialize edge centrality scores for (const edge of Array.from(graph.edges())) { const edgeKey = `${String(edge.source)}-${String(edge.target)}`; edgeCentrality.set(edgeKey, 0); } // Modified Brandes' algorithm for edge betweenness for (const source of nodes) { const result = brandesSingleSource(graph, source, options.optimized); accumulateEdgeBetweenness(result, source, edgeCentrality, options); } // For undirected graphs, divide by 2 (each shortest path is counted twice) if (!graph.isDirected) { for (const [edgeKey, centrality] of Array.from(edgeCentrality)) { edgeCentrality.set(edgeKey, centrality / 2); } } // Normalization for edges if (options.normalized) { const n = nodes.length; const normalizationFactor = graph.isDirected ? (n - 1) * (n - 2) : ((n - 1) * (n - 2)) / 2; if (normalizationFactor > 0) { for (const [edgeKey, centrality] of Array.from(edgeCentrality)) { edgeCentrality.set(edgeKey, centrality / normalizationFactor); } } } return edgeCentrality; } //# sourceMappingURL=betweenness.js.map