UNPKG

@fluxgraph/knowledge

Version:

A flexible, database-agnostic knowledge graph implementation for TypeScript

305 lines 9.63 kB
// src/algorithms/index.ts var GraphAlgorithms = class { graph; constructor(graph) { this.graph = graph; } /** * Calculate degree centrality for a node * Degree centrality is the number of edges connected to a node */ async degreeCentrality(nodeId) { const result = await this.graph.queryRelated(nodeId, { depth: 1 }); return result.edges.length; } /** * Calculate degree centrality for all nodes */ async allDegreeCentrality() { const centrality = /* @__PURE__ */ new Map(); const nodes = await this.graph.queryByType("CUSTOM", { limit: 1e3 }); for (const node of nodes.nodes) { const degree = await this.degreeCentrality(node.id); centrality.set(node.id, degree); } return centrality; } /** * Find all paths between two nodes up to a maximum length */ async findAllPaths(fromNodeId, toNodeId, maxLength = 5) { const paths = []; const visited = /* @__PURE__ */ new Set(); const currentPath = []; const currentEdges = []; const dfs = async (currentNodeId, depth) => { if (depth > maxLength) return; if (currentNodeId === toNodeId) { const nodeObjects = await Promise.all( [...currentPath, currentNodeId].map((id) => this.graph.getNode(id)) ); paths.push({ nodes: nodeObjects.filter((n) => n !== null), edges: [...currentEdges], length: currentPath.length }); return; } if (visited.has(currentNodeId)) return; visited.add(currentNodeId); currentPath.push(currentNodeId); const result = await this.graph.queryRelated(currentNodeId, { depth: 1, direction: "out" }); for (const edge of result.edges) { if (edge.fromNodeId === currentNodeId) { currentEdges.push(edge); await dfs(edge.toNodeId, depth + 1); currentEdges.pop(); } } currentPath.pop(); visited.delete(currentNodeId); }; await dfs(fromNodeId, 0); return paths; } /** * Detect cycles in the graph */ async detectCycles() { const cycles = []; const visited = /* @__PURE__ */ new Set(); const recursionStack = /* @__PURE__ */ new Set(); const path = []; const pathEdges = []; const dfs = async (nodeId) => { visited.add(nodeId); recursionStack.add(nodeId); path.push(nodeId); const result = await this.graph.queryRelated(nodeId, { depth: 1, direction: "out" }); for (const edge of result.edges) { if (edge.fromNodeId === nodeId) { pathEdges.push(edge); if (!visited.has(edge.toNodeId)) { if (await dfs(edge.toNodeId)) { return true; } } else if (recursionStack.has(edge.toNodeId)) { const cycleStartIndex = path.indexOf(edge.toNodeId); cycles.push({ nodes: path.slice(cycleStartIndex), edges: pathEdges.slice(cycleStartIndex) }); } pathEdges.pop(); } } path.pop(); recursionStack.delete(nodeId); return false; }; const nodes = await this.graph.queryByType("CUSTOM", { limit: 1e3 }); for (const node of nodes.nodes) { if (!visited.has(node.id)) { await dfs(node.id); } } return cycles; } /** * Find connected components in the graph */ async findConnectedComponents() { const components = []; const visited = /* @__PURE__ */ new Set(); const dfs = async (nodeId, component) => { if (visited.has(nodeId)) return; visited.add(nodeId); component.add(nodeId); const result = await this.graph.queryRelated(nodeId, { depth: 1, direction: "both" }); for (const node of result.nodes) { if (node.id !== nodeId && !visited.has(node.id)) { await dfs(node.id, component); } } }; const nodes = await this.graph.queryByType("CUSTOM", { limit: 1e4 }); for (const node of nodes.nodes) { if (!visited.has(node.id)) { const component = /* @__PURE__ */ new Set(); await dfs(node.id, component); components.push(component); } } return components; } /** * Calculate PageRank for all nodes * Simplified implementation - real PageRank would need iterative computation */ async pageRank(damping = 0.85, iterations = 10) { const pageRank2 = /* @__PURE__ */ new Map(); const nodes = await this.graph.queryByType("CUSTOM", { limit: 1e4 }); const nodeIds = nodes.nodes.map((n) => n.id); const N = nodeIds.length; for (const nodeId of nodeIds) { pageRank2.set(nodeId, 1 / N); } for (let iter = 0; iter < iterations; iter++) { const newPageRank = /* @__PURE__ */ new Map(); for (const nodeId of nodeIds) { let rank = (1 - damping) / N; const result = await this.graph.queryRelated(nodeId, { depth: 1, direction: "in" }); for (const edge of result.edges) { if (edge.toNodeId === nodeId) { const sourceRank = pageRank2.get(edge.fromNodeId) || 0; const sourceOutDegree = await this.degreeCentrality(edge.fromNodeId); rank += damping * (sourceRank / Math.max(sourceOutDegree, 1)); } } newPageRank.set(nodeId, rank); } for (const [nodeId, rank] of newPageRank) { pageRank2.set(nodeId, rank || 0); } } return pageRank2; } /** * Find nodes that form a clique (fully connected subgraph) */ async findCliques(minSize = 3) { const cliques = []; const nodes = await this.graph.queryByType("CUSTOM", { limit: 1e3 }); const nodeIds = nodes.nodes.map((n) => n.id); const bronKerbosch = async (R, P, X) => { if (P.size === 0 && X.size === 0) { if (R.size >= minSize) { cliques.push(new Set(R)); } return; } for (const v of P) { const neighbors = /* @__PURE__ */ new Set(); const result = await this.graph.queryRelated(v, { depth: 1 }); for (const node of result.nodes) { if (node.id !== v) { neighbors.add(node.id); } } const newR = new Set(R); newR.add(v); const newP = /* @__PURE__ */ new Set(); for (const p of P) { if (neighbors.has(p)) newP.add(p); } const newX = /* @__PURE__ */ new Set(); for (const x of X) { if (neighbors.has(x)) newX.add(x); } await bronKerbosch(newR, newP, newX); P.delete(v); X.add(v); } }; await bronKerbosch(/* @__PURE__ */ new Set(), new Set(nodeIds), /* @__PURE__ */ new Set()); return cliques; } /** * Calculate clustering coefficient for a node */ async clusteringCoefficient(nodeId) { const result = await this.graph.queryRelated(nodeId, { depth: 1 }); const neighbors = result.nodes.filter((n) => n.id !== nodeId); if (neighbors.length < 2) return 0; let triangles = 0; const possibleTriangles = neighbors.length * (neighbors.length - 1) / 2; for (let i = 0; i < neighbors.length; i++) { for (let j = i + 1; j < neighbors.length; j++) { const neighborI = neighbors[i]; const neighborJ = neighbors[j]; if (!neighborI || !neighborJ) continue; const edges = await this.graph.getEdgesBetween( neighborI.id, neighborJ.id ); if (edges.length > 0) { triangles++; } } } return triangles / possibleTriangles; } /** * Find communities using label propagation */ async detectCommunities() { const communities = /* @__PURE__ */ new Map(); const nodes = await this.graph.queryByType("CUSTOM", { limit: 1e3 }); let communityId = 0; for (const node of nodes.nodes) { communities.set(node.id, communityId++); } let changed = true; let iterations = 0; const maxIterations = 10; while (changed && iterations < maxIterations) { changed = false; iterations++; for (const node of nodes.nodes) { const result = await this.graph.queryRelated(node.id, { depth: 1 }); const labelCounts = /* @__PURE__ */ new Map(); for (const neighbor of result.nodes) { if (neighbor.id !== node.id) { const label = communities.get(neighbor.id); if (label !== void 0) { labelCounts.set(label, (labelCounts.get(label) || 0) + 1); } } } if (labelCounts.size > 0) { const maxLabel = Array.from(labelCounts.entries()).reduce((a, b) => a[1] > b[1] ? a : b)[0]; if (communities.get(node.id) !== maxLabel) { communities.set(node.id, maxLabel); changed = true; } } } } return communities; } }; async function degreeCentrality(graph, nodeId) { const algorithms = new GraphAlgorithms(graph); return algorithms.degreeCentrality(nodeId); } async function findShortestPath(graph, fromNodeId, toNodeId) { return graph.findShortestPath(fromNodeId, toNodeId); } async function detectCycles(graph) { const algorithms = new GraphAlgorithms(graph); return algorithms.detectCycles(); } async function pageRank(graph) { const algorithms = new GraphAlgorithms(graph); return algorithms.pageRank(); } export { GraphAlgorithms, degreeCentrality, detectCycles, findShortestPath, pageRank }; //# sourceMappingURL=index.mjs.map