UNPKG

agentscape

Version:

Agentscape is a library for creating agent-based simulations. It provides a simple API for defining agents and their behavior, and for defining the environment in which the agents interact. Agentscape is designed to be flexible and extensible, allowing

220 lines 7.82 kB
export class GraphEdge { constructor(source, target, weight, context) { this.source = source; this.target = target; this.weight = weight !== null && weight !== void 0 ? weight : 1; this.context = context; } get reverse() { return new GraphEdge(this.target, this.source, this.weight); } } export class GraphNode { constructor(id, context) { this.id = id; this.edges = new Set(); this.context = context; } get degree() { const hashEdge = (sourceId, targetId) => { const [minId, maxId] = sourceId < targetId ? [sourceId, targetId] : [targetId, sourceId]; return `${minId}-${maxId}`; }; const edgeHashes = new Set(); for (const edge of this.edges) { edgeHashes.add(hashEdge(this.id, edge.target.id)); } return edgeHashes.size; } get neighbors() { return Array.from(this.edges).map(edge => edge.target); } addEdge(target, weight = 1, context) { const edge1 = new GraphEdge(this, target, weight, context); const edge2 = new GraphEdge(target, this, weight, context); // Add edge in both directions for undirected graph this.edges.add(edge1); target.edges.add(edge2); } removeEdge(target) { this.edges = new Set([...this.edges].filter(edge => edge.target !== target)); } hasEdge(target) { return [...this.edges].some(edge => edge.target === target); } getEdge(target) { return [...this.edges].find(edge => edge.target === target); } } export default class Graph { constructor() { this.nodes = new Map(); } static fromNodes(nodes) { const graph = new Graph(); for (const node of nodes) { graph.nodes.set(node.id, node); } return graph; } *[Symbol.iterator]() { for (const node of this.nodes) { yield node; } } forEachNode(callback) { let i = 0; for (const node of this.nodes.values()) { callback(node, i); i++; } } forEachEdge(callback) { let i = 0; for (const node of this.nodes.values()) { for (const edge of node.edges) { callback(edge, i); i++; } } } nodeCount() { return this.nodes.size; } edgeCount() { return Array.from(this.nodes.values()).reduce((acc, node) => acc + node.degree, 0); } asArray() { return Array.from(this.nodes.values()); } createNode(id, context) { if (this.nodes.has(id)) { return this.nodes.get(id); } const node = new GraphNode(id, context); this.nodes.set(id, node); return node; } addNode(node) { this.nodes.set(node.id, node); return node; } getNode(id) { return this.nodes.get(id); } hasNode(id) { return this.nodes.has(id); } createEdge(source, target, weight = 1, context) { source.addEdge(target, weight, context); target.addEdge(source, weight, context); // Add edge in both directions for undirected graph } removeEdge(source, target) { source.removeEdge(target); target.removeEdge(source); // Remove edge in both directions for undirected graph } hasEdge(source, target) { return source.hasEdge(target); } getEdge(source, target) { return source.getEdge(target); } getEdgesFromNode(node) { return [...node.edges].filter(edge => edge.source === node); } getEdgesToNode(node) { return [...node.edges].filter(edge => edge.target === node); } adjacencyMatrix() { const nodeIds = Array.from(this.nodes.keys()); const numNodes = nodeIds.length; const matrix = Array(numNodes).fill(null).map(() => Array(numNodes).fill(0)); for (let i = 0; i < numNodes; i++) { const sourceId = nodeIds[i]; const source = this.getNode(sourceId); if (source) { for (let j = 0; j < numNodes; j++) { const targetId = nodeIds[j]; if (source.hasEdge(this.getNode(targetId))) { const edge = source.getEdge(this.getNode(targetId)); matrix[i][j] = edge ? edge.weight : 1; } else { matrix[i][j] = 0; } } } } return matrix; } /** * Gives the connectivity metrics of the graph. * * **Alpha**: The higher the alpha index, the more a network is connected. Trees and simple networks will have a value of 0. A value of 1 indicates a completely connected network. * * **Beta**: Trees and simple networks have Beta value of less than one. A connected network with one cycle has a value of 1. * * **Gamma**: Considers the relationship between the number of observed links and the number of possible links. The value of gamma is between 0 and 1 where a value of 1 indicates a completely connected network. */ getConnectivityMetrics() { const networkEdges = this.edgeCount(); const networkNodes = this.nodeCount(); const alpha = (networkEdges - networkNodes + 1) / (2 * networkNodes - 5); const beta = networkEdges / networkNodes; const gamma = networkEdges / (3 * (networkNodes - 2)); return { alpha, beta, gamma }; } /** * Returns the shortest path between two nodes in the graph using Dijkstra's algorithm. */ shortestPath(source, target, metric = edge => edge.weight) { var _a, _b; const unvisited = new Set(this.nodes.values()); const distances = new Map(); const previous = new Map(); distances.set(source, 0); while (unvisited.size > 0) { // Find the node with the smallest distance let current = undefined; let smallestDistance = Infinity; for (const node of unvisited) { const distance = (_a = distances.get(node)) !== null && _a !== void 0 ? _a : Infinity; if (distance < smallestDistance) { smallestDistance = distance; current = node; } } // If the smallest distance is Infinity, we are stuck if (current === undefined || smallestDistance === Infinity) { break; } unvisited.delete(current); // If we reached the target, reconstruct the path if (current === target) { const path = []; while (current !== undefined) { path.unshift(current); current = previous.get(current); } return path; } // Update distances for neighbors for (const neighbor of current.neighbors) { if (!unvisited.has(neighbor)) { continue; } const edge = current.getEdge(neighbor); if (!edge) { continue; } const distance = distances.get(current) + metric(edge); if (distance < ((_b = distances.get(neighbor)) !== null && _b !== void 0 ? _b : Infinity)) { distances.set(neighbor, distance); previous.set(neighbor, current); } } } // If we exit the loop without finding the target, return an empty path return []; } } //# sourceMappingURL=Graph.js.map