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
JavaScript
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