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

249 lines 7.99 kB
import RandomGenerator from '../numbers/RandomGenerator'; export default class AgentSet { /** * Generates an AgentSet from an array of agents. */ static fromArray(agents) { const agentSet = new AgentSet(); for (const agent of agents) { agentSet.add(agent); } return agentSet; } /** * Generates an AgentSet from a Set of agents. */ static fromSet(agents) { const agentSet = new AgentSet(); for (const agent of agents) { agentSet.add(agent); } return agentSet; } /** * Generates an AgentSet from a factory function. * The factory function is called `count` times with an sequential index * and a non-sequential random seed. The function should return an Agent. */ static fromFactory(count, agentFactory, options) { var _a; const randomSeed = (_a = options === null || options === void 0 ? void 0 : options.randomSeed) !== null && _a !== void 0 ? _a : Date.now() ^ (Math.random() * 0x100000000); const rng = new RandomGenerator(randomSeed); const agentSet = new AgentSet(); for (let i = 0; i < count; i++) { rng.jump(); const seed = rng.uniformInt(0, 0x100000000); agentSet.add(agentFactory(i, seed)); } return agentSet; } constructor() { this.agents = new Set(); } *[Symbol.iterator]() { for (const agent of this.agents) { yield agent; } } toArray() { return [...this.agents]; } /** * Runs the 'act' method on all agents in the set. */ step(world, agentSets, tick) { for (const agent of this.agents) { agent.act(world, agentSets, tick); } } /** * Inserts an agent into the set. * Optionally inserts the agent into the grid. */ add(agent, world) { this.agents.add(agent); if (world) { world.insertAgent(agent); } } /** * Removes an agent from the set. */ remove(agent) { this.agents.delete(agent); } /** * Iterates over all agents in the set. */ forEach(callback) { let i = 0; for (const agent of this.agents) { callback(agent, i); i++; } } /** * Maps a callback function over all agents in the set. */ map(callback) { let i = 0; const values = []; for (const agent of this.agents) { values.push(callback(agent, i)); i++; } return values; } /** * Gets the ith agent in the set. */ get(index) { return [...this.agents][index]; } /* * Reduces the set of agents to a single value via a callback function. */ reduce(callback, initialValue) { return [...this.agents].reduce(callback, initialValue); } /** * Finds the Agent with the minimum value in the set of agents. * * Returns undefined if the set is empty. */ minimizeBy(fn) { if (this.agents.size === 0) { return undefined; } return [...this.agents].reduce((a, b) => fn(a) < fn(b) ? a : b); } /** * Finds the Agent with the maximum value in the set of agents. * * Returns undefined if the set is empty. * * @since 1.0.0 */ maximizeBy(fn) { if (this.agents.size === 0) { return undefined; } return [...this.agents].reduce((a, b) => fn(a) > fn(b) ? a : b); } /** * Returns the number of agents in the set. */ get size() { return this.agents.size; } /** * Removes all dead agents from the set. * Note: This is handled automatically by the model. */ cullDeadAgents() { this.agents = new Set([...this.agents].filter(agent => agent.isAlive)); } /** * Gets the agent at a location on a grid. */ getAgentAt(location) { return [...this.agents].find(agent => agent.position[0] === location[0] && agent.position[1] === location[1]); } /** * Gets the nearest agent to a location. Returns undefined if the set is empty. */ getNearestAgentAt(world, location, options) { if (this.agents.size === 0) { return undefined; } const metric = (options === null || options === void 0 ? void 0 : options.metric) || 'euclidean'; let nearestAgent; let nearestDistance = Infinity; for (const agent of this.agents) { const distance = this.distance(world, location, agent.position.components, metric); if (distance < nearestDistance) { nearestDistance = distance; nearestAgent = agent; } } return nearestAgent; } filter(predicate) { return AgentSet.fromArray([...this.agents].filter(predicate)); } findFirst(predicate) { return [...this.agents].find(predicate); } sortBy(compareFn) { return AgentSet.fromArray([...this.agents].sort(compareFn)); } slice(start, end) { return AgentSet.fromArray([...this.agents].slice(start, end)); } /** * Gets all agents within a certain range of a location. */ getWithinRange(world, location, range, options) { const metric = (options === null || options === void 0 ? void 0 : options.metric) || 'euclidean'; return AgentSet.fromArray([...this.agents] .filter(agent => this.distance(world, location, agent.position.components, metric) <= range)); } /** * Gets the mean position of all agents in the set. */ getCentroid() { const sum = this.reduce((acc, agent) => { acc[0] += agent.position.x; acc[1] += agent.position.y; return acc; }, [0, 0]); return [sum[0] / this.size, sum[1] / this.size]; } /** * Picks a single agent at random from the set. * Optionally provide a list of agents to exclude from the selection. * * Returns undefined if the set is empty. */ random(rng, options) { const exclude = (options === null || options === void 0 ? void 0 : options.exclude) || []; const candidates = [...this.agents].filter(agent => !exclude.includes(agent)); if (candidates.length === 0) { return undefined; } return rng.pickRandom(candidates); } /** * Picks a random sample (without replacement) of agents from the set. * Optionally provide a list of agents to exclude from the sample. */ randomSample(rng, count, options) { const exclude = (options === null || options === void 0 ? void 0 : options.exclude) || []; const candidates = [...this.agents].filter(agent => !exclude.includes(agent)); const sample = rng.pickRandomArray(candidates, count); return AgentSet.fromArray(sample); } distance(world, location1, location2, metric) { if (world.boundaryCondition === 'finite') { if (metric === 'euclidean') { return Math.sqrt((location1[0] - location2[0]) ** 2 + (location1[1] - location2[1]) ** 2); } else { return Math.abs(location1[0] - location2[0]) + Math.abs(location1[1] - location2[1]); } } if (world.boundaryCondition === 'periodic') { const dx = Math.abs(location1[0] - location2[0]); const dy = Math.abs(location1[1] - location2[1]); const x = Math.min(dx, world.width - dx); const y = Math.min(dy, world.height - dy); if (metric === 'euclidean') { return Math.sqrt(x ** 2 + y ** 2); } else { return x + y; } } } } //# sourceMappingURL=AgentSet.js.map