UNPKG

@graphty/algorithms

Version:

Graph algorithms library for browser environments implemented in TypeScript

202 lines 7.14 kB
/** * Calculate closeness centrality for all nodes */ export function closenessCentrality(graph, options = {}) { const nodes = Array.from(graph.nodes()).map((node) => node.id); const centrality = {}; for (const sourceNode of nodes) { centrality[String(sourceNode)] = nodeClosenessCentrality(graph, sourceNode, options); } return centrality; } /** * Calculate closeness centrality for a specific node */ export function nodeClosenessCentrality(graph, node, options = {}) { if (!graph.hasNode(node)) { throw new Error(`Node ${String(node)} not found in graph`); } const distances = singleSourceShortestPathLengths(graph, node, options.cutoff); if (distances.size <= 1) { return 0; // No other nodes reachable } let centrality = 0; if (options.harmonic) { // Harmonic centrality: sum of reciprocals of distances for (const [targetNode, distance] of Array.from(distances)) { if (targetNode !== node && distance > 0) { centrality += 1 / distance; } } } else { // Standard closeness: reciprocal of sum of distances let totalDistance = 0; let reachableNodes = 0; for (const [targetNode, distance] of Array.from(distances)) { if (targetNode !== node) { totalDistance += distance; reachableNodes++; } } if (totalDistance > 0) { centrality = 1 / totalDistance; // Wasserman and Faust normalization for disconnected graphs if (options.normalized) { const n = Array.from(graph.nodes()).length; centrality = centrality * reachableNodes / (n - 1); } } } // Normalization for harmonic centrality if (options.harmonic && options.normalized) { const n = Array.from(graph.nodes()).length; if (n > 1) { centrality = centrality / (n - 1); } } return centrality; } /** * Single-source shortest path lengths using BFS (for unweighted graphs) */ function singleSourceShortestPathLengths(graph, source, cutoff) { const distances = new Map(); const visited = new Set(); const queue = []; // Initialize queue.push({ node: source, distance: 0 }); visited.add(source); distances.set(source, 0); while (queue.length > 0) { const current = queue.shift(); if (!current) { break; } // Skip if beyond cutoff if (cutoff !== undefined && current.distance >= cutoff) { continue; } // Explore neighbors for (const neighbor of Array.from(graph.neighbors(current.node))) { if (!visited.has(neighbor)) { const newDistance = current.distance + 1; // Skip if beyond cutoff if (cutoff !== undefined && newDistance > cutoff) { continue; } visited.add(neighbor); distances.set(neighbor, newDistance); queue.push({ node: neighbor, distance: newDistance }); } } } return distances; } /** * Calculate weighted closeness centrality using Dijkstra's algorithm */ export function weightedClosenessCentrality(graph, options = {}) { const nodes = Array.from(graph.nodes()).map((node) => node.id); const centrality = {}; for (const sourceNode of nodes) { centrality[String(sourceNode)] = nodeWeightedClosenessCentrality(graph, sourceNode, options); } return centrality; } /** * Calculate weighted closeness centrality for a specific node using Dijkstra */ export function nodeWeightedClosenessCentrality(graph, node, options = {}) { if (!graph.hasNode(node)) { throw new Error(`Node ${String(node)} not found in graph`); } const distances = dijkstraDistances(graph, node, options.cutoff); if (distances.size <= 1) { return 0; // No other nodes reachable } let centrality = 0; if (options.harmonic) { // Harmonic centrality: sum of reciprocals of distances for (const [targetNode, distance] of Array.from(distances)) { if (targetNode !== node && distance > 0 && distance < Infinity) { centrality += 1 / distance; } } } else { // Standard closeness: reciprocal of sum of distances let totalDistance = 0; let reachableNodes = 0; for (const [targetNode, distance] of Array.from(distances)) { if (targetNode !== node && distance < Infinity) { totalDistance += distance; reachableNodes++; } } if (totalDistance > 0) { centrality = 1 / totalDistance; // Wasserman and Faust normalization for disconnected graphs if (options.normalized) { const n = Array.from(graph.nodes()).length; centrality = centrality * reachableNodes / (n - 1); } } } // Normalization for harmonic centrality if (options.harmonic && options.normalized) { const n = Array.from(graph.nodes()).length; if (n > 1) { centrality = centrality / (n - 1); } } return centrality; } /** * Single-source shortest path distances using Dijkstra's algorithm */ function dijkstraDistances(graph, source, cutoff) { const distances = new Map(); const visited = new Set(); const pq = []; // Initialize distances for (const node of Array.from(graph.nodes())) { distances.set(node.id, node.id === source ? 0 : Infinity); } pq.push({ node: source, distance: 0 }); while (pq.length > 0) { // Simple priority queue (could be optimized with a proper heap) pq.sort((a, b) => a.distance - b.distance); const current = pq.shift(); if (!current || visited.has(current.node)) { continue; } visited.add(current.node); // Skip if beyond cutoff if (cutoff !== undefined && current.distance > cutoff) { continue; } // Explore neighbors for (const neighbor of Array.from(graph.neighbors(current.node))) { if (visited.has(neighbor)) { continue; } const edge = graph.getEdge(current.node, neighbor); if (!edge) { continue; } const edgeWeight = edge.weight ?? 1; const tentativeDistance = current.distance + edgeWeight; const currentDistance = distances.get(neighbor) ?? Infinity; if (tentativeDistance < currentDistance) { distances.set(neighbor, tentativeDistance); // Skip if beyond cutoff if (cutoff === undefined || tentativeDistance <= cutoff) { pq.push({ node: neighbor, distance: tentativeDistance }); } } } } return distances; } //# sourceMappingURL=closeness.js.map