UNPKG

@graphty/algorithms

Version:

Graph algorithms library for browser environments implemented in TypeScript

146 lines 5.44 kB
/** * Calculate HITS hub and authority scores for all nodes in the graph * Uses iterative method with normalization */ export function hits(graph, options = {}) { const { maxIterations = 100, tolerance = 1e-6, normalized = true, } = options; const hubs = {}; const authorities = {}; const nodes = Array.from(graph.nodes()); const nodeIds = nodes.map((node) => node.id); if (nodeIds.length === 0) { return { hubs, authorities }; } // Initialize scores let currentHubs = new Map(); let currentAuthorities = new Map(); let previousHubs = new Map(); let previousAuthorities = new Map(); // Start with uniform distribution const initialValue = 1.0 / Math.sqrt(nodeIds.length); for (const nodeId of nodeIds) { const key = nodeId.toString(); currentHubs.set(key, initialValue); currentAuthorities.set(key, initialValue); } // Iterative computation for (let iteration = 0; iteration < maxIterations; iteration++) { previousHubs = new Map(currentHubs); previousAuthorities = new Map(currentAuthorities); // Update authority scores // Authority score = sum of hub scores of nodes pointing to it const newAuthorities = new Map(); for (const nodeId of nodeIds) { let authorityScore = 0; const inNeighbors = Array.from(graph.inNeighbors(nodeId)); for (const inNeighbor of inNeighbors) { const neighborKey = inNeighbor.toString(); authorityScore += previousHubs.get(neighborKey) ?? 0; } newAuthorities.set(nodeId.toString(), authorityScore); } // Update hub scores // Hub score = sum of authority scores of nodes it points to const newHubs = new Map(); for (const nodeId of nodeIds) { let hubScore = 0; const outNeighbors = Array.from(graph.outNeighbors(nodeId)); for (const outNeighbor of outNeighbors) { const neighborKey = outNeighbor.toString(); hubScore += previousAuthorities.get(neighborKey) ?? 0; } newHubs.set(nodeId.toString(), hubScore); } // Normalize authority scores let authNorm = 0; for (const value of Array.from(newAuthorities.values())) { authNorm += value * value; } authNorm = Math.sqrt(authNorm); if (authNorm > 0) { for (const [nodeId, value] of Array.from(newAuthorities)) { newAuthorities.set(nodeId, value / authNorm); } } // Normalize hub scores let hubNorm = 0; for (const value of Array.from(newHubs.values())) { hubNorm += value * value; } hubNorm = Math.sqrt(hubNorm); if (hubNorm > 0) { for (const [nodeId, value] of Array.from(newHubs)) { newHubs.set(nodeId, value / hubNorm); } } currentAuthorities = newAuthorities; currentHubs = newHubs; // Check for convergence let maxDiff = 0; for (const [nodeId, value] of currentHubs) { const prevValue = previousHubs.get(nodeId) ?? 0; const diff = Math.abs(value - prevValue); maxDiff = Math.max(maxDiff, diff); } for (const [nodeId, value] of currentAuthorities) { const prevValue = previousAuthorities.get(nodeId) ?? 0; const diff = Math.abs(value - prevValue); maxDiff = Math.max(maxDiff, diff); } if (maxDiff < tolerance) { break; } } // Prepare results for (const [nodeId, value] of currentHubs) { hubs[nodeId] = value; } for (const [nodeId, value] of currentAuthorities) { authorities[nodeId] = value; } // The algorithm already normalizes to unit L2 norm during iterations // Additional max normalization is only applied if explicitly requested if (!normalized) { // If normalization is explicitly disabled, normalize to [0, 1] range let maxHub = 0; let maxAuth = 0; for (const value of Object.values(hubs)) { maxHub = Math.max(maxHub, value); } for (const value of Object.values(authorities)) { maxAuth = Math.max(maxAuth, value); } if (maxHub > 0) { for (const nodeId of Object.keys(hubs)) { const hubValue = hubs[nodeId]; if (hubValue !== undefined) { hubs[nodeId] = hubValue / maxHub; } } } if (maxAuth > 0) { for (const nodeId of Object.keys(authorities)) { const authValue = authorities[nodeId]; if (authValue !== undefined) { authorities[nodeId] = authValue / maxAuth; } } } } return { hubs, authorities }; } /** * Calculate HITS scores for a specific node */ export function nodeHITS(graph, nodeId, options = {}) { if (!graph.hasNode(nodeId)) { throw new Error(`Node ${String(nodeId)} not found in graph`); } const result = hits(graph, options); const key = nodeId.toString(); return { hub: result.hubs[key] ?? 0, authority: result.authorities[key] ?? 0, }; } //# sourceMappingURL=hits.js.map