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