@fluxgraph/knowledge
Version:
A flexible, database-agnostic knowledge graph implementation for TypeScript
305 lines • 9.63 kB
JavaScript
// 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