The core API is defined by AgentBasedModel, Space, AbstractAgent and step!, which are described in the Tutorial page. The functionality described here builds on top of the core API.

Agent information and retrieval

space_neighbors(position, model::ABM, r) → ids

Return the ids of the agents neighboring the given position (which must match type with the spatial structure of the model). r is the radius to search for agents.

For DiscreteSpaces r must be integer and defines higher degree neighbors. For example, for r=2 include first and second degree neighbors, that is, neighbors and neighbors of neighbors.

For ContinuousSpace, r is real number and finds all neighbors within distance r (based on the space's metric).

The node_neighbors keyword neighbor_type can also be used here to restrict the spatial search on directed graphs.

space_neighbors(agent::AbstractAgent, model::ABM [, r]) → ids

Call space_neighbors(agent.pos, model, r) but exclude the given agent from the neighbors.

nextid(model::ABM) → id

Return a valid id for creating a new agent with it.


Model-Agent interaction

The following API is mostly universal across all types of Space. Only some specific methods are exclusive to a specific type of space, but we think this is clear from the documentation strings (if not, please open an issue!).

add_agent!(agent::AbstractAgent [, position], model::ABM) → agent

Add the agent to the position in the space and to the list of agents. If position is not given, the agent is added to a random position. The agent's position is always updated to match position, and therefore for add_agent! the position of the agent is meaningless. Use add_agent_pos! to use the agent's position.

add_agent!([pos,] model::ABM, args...; kwargs...)

Create and add a new agent to the model by constructing an agent of the type of the model. Propagate all extra positional arguments and keyword arguemts to the agent constructor.

Notice that this function takes care of setting the agent's id and position and thus args... and kwargs... are propagated to other fields the agent has.

Optionally provide a position to add the agent to as first argument.


using Agents
mutable struct Agent <: AbstractAgent
Agent(id; w, k) = Agent(id, w, k) # keyword constructor
model = ABM(Agent, GraphSpace(complete_digraph(5)))

add_agent!(model, 1, 0.5, true) # incorrect: id is set internally
add_agent!(model, 0.5, true) # correct: weight becomes 0.5
add_agent!(5, model, 0.5, true) # add at node 5
add_agent!(model; w = 0.5, k =true) # use keywords: weight becomes 0.5
add_agent_pos!(agent::AbstractAgent, model::ABM) → agent

Add the agent to the model at the agent's own position.

add_agent_single!(agent::A, model::ABM{A, <: DiscreteSpace}) → agent

Add agent to a random node in the space while respecting a maximum one agent per node. This function throws a warning if no empty nodes remain.

add_agent_single!(model::ABM{A, <: DiscreteSpace}, properties...; kwargs...)

Same as add_agent!(model, properties...) but ensures that it adds an agent into a node with no other agents (does nothing if no such node exists).

move_agent!(agent::A, model::ABM{A, ContinuousSpace}, dt = 1.0)

Propagate the agent forwards one step according to its velocity, after updating the agent's velocity (see ContinuousSpace). Also take care of periodic boundary conditions.

For this continuous space version of move_agent!, the "evolution algorithm" is a trivial Euler scheme with dt the step size, i.e. the agent position is updated as agent.pos += agent.vel * dt.

move_agent!(agent::A, model::ABM{A, ContinuousSpace}, vel::NTuple{D, N}, dt = 1.0)

Propagate the agent forwards one step according to vel and the model's space, with dt as the time step. (update_vel! is not used)

move_agent!(agent::A [, pos], model::ABM{A, <: DiscreteSpace}) → agent

Add agentID to the new position pos (or a random one if pos is not given) in the model and remove it from the old position (also update the agent to have the new position). pos must be the appropriate position type depending on the space type.

move_agent_single!(agent::AbstractAgent, model::ABM) → agent

Move agent to a random node while respecting a maximum of one agent per node. If there are no empty nodes, the agent wont move. Only valid for non-continuous spaces.

kill_agent!(agent::AbstractAgent, model::ABM)

Remove an agent from model, and from the space if the model has a space.


Kill all the agents of the model.

genocide!(model::ABM, n::Int)

Kill the agents of the model whose IDs are larger than n.

genocide!(model::ABM, f::Function)

Kill all agents where the function f(agent) returns true.

sample!(model::ABM, n [, weight]; kwargs...)

Replace the agents of the model with a random sample of the current agents with size n.

Optionally, provide a weight: Symbol (agent field) or function (input agent out put number) to weight the sampling. This means that the higher the weight of the agent, the higher the probability that this agent will be chosen in the new sampling.


  • replace = true : whether sampling is performed with replacement, i.e. all agents can

be chosen more than once.

  • rng = GLOBAL_RNG : a random number generator to perform the sampling with.

See the Wright-Fisher example in the documentation for an application of sample!.


Discrete space exclusives

node_neighbors(node, model::ABM{A, <:DiscreteSpace}, r = 1) → nodes

Return all nodes that are neighbors to the given node, which can be an Int for GraphSpace, or a NTuple{Int} for GridSpace.

node_neighbors(agent, model::ABM{A, <:DiscreteSpace}, r = 1) → nodes

Same as above, but uses agent.pos as node.

Keyword argument neighbor_type=:default can be used to select differing neighbors depending on the underlying graph type.

  • :default returns neighbors of a vertex. If graph is directed, this is equivalent

to :out. Using any of the following options on an undirected graph is the equivalent to :default.

  • :all returns both :in and :out neighbors.
  • :in returns incoming vertex neighbors.
  • :out returns outgoing vertex neighbors.

Return the number of nodes (vertices) in the model space.

get_node_contents(node, model)

Return the ids of agents in the node of the model's space (which is an integer for GraphSpace and a tuple for GridSpace).

get_node_contents(agent::AbstractAgent, model)

Return all agents' ids in the same node as the agent (including the agent's own id).

get_node_agents(x, model)

Same as get_node_contents(x, model) but directly returns the list of agents instead of just the list of IDs.

isempty(node::Int, model::ABM)

Return true if there are no agents in node.

NodeIterator(model) → iterator

Create an iterator that returns node coordinates, if the space is a grid, or otherwise node number, and the agent IDs in each node.

nodes(model; by = :id) -> ns

Return a vector of the node ids of the model that you can iterate over. The ns are sorted depending on by:

  • :id - just sorted by their number
  • :random - randomly sorted
  • :population - nodes are sorted depending on how many agents they accommodate. The more populated nodes are first.

Continuous space exclusives

interacting_pairs(model, r, method; scheduler = model.scheduler)

Return an iterator that yields unique pairs of agents (a1, a2) that are close neighbors to each other, within some interaction radius r.

This function is usefully combined with model_step!, when one wants to perform some pairwise interaction across all pairs of close agents once (and does not want to trigger the event twice, both with a1 and with a2, which is unavoidable when using agent_step!).

The argument method provides three pairing scenarios

  • :all: return every pair of agents that are within radius r of each other, not only the nearest ones.
  • :nearest: agents are only paired with their true nearest neighbor (existing within radius r). Each agent can only belong to one pair, therefore if two agents share the same nearest neighbor only one of them (sorted by id) will be paired.
  • :scheduler: agents are scanned according to the given keyword scheduler (by default the model's scheduler), and each scanned agent is paired to its nearest neighbor. Similar to :nearest, each agent can belong to only one pair. This functionality is useful e.g. when you want some agents to be paired "guaranteed", even if some other agents might be nearest to each other.
  • :types: For mixed agent models only. Return every pair of agents within radius r (similar to :all), only capturing pairs of differing types. For example, a model of Union{Sheep,Wolf} will only return pairs of (Sheep, Wolf). In the case of multiple agent types, e.g. Union{Sheep, Wolf, Grass}, skipping pairings that involve Grass, can be achived by a scheduler that doesn't schedule Grass types, i.e.: scheduler = [a.id for a in allagents(model) of !(a isa Grass)].
nearest_neighbor(agent, model, r) → nearest

Return the agent that has the closest distance to given agent, according to the space's metric. Valid only in continuous space. Return nothing if no agent is within distance r.

elastic_collision!(a, b, f = nothing)

Resolve a (hypothetical) elastic collision between the two agents a, b. They are assumed to be disks of equal size touching tangentially. Their velocities (field vel) are adjusted for an elastic collision happening between them. This function works only for two dimensions. Notice that collision only happens if both disks face each other, to avoid collision-after-collision.

If f is a Symbol, then the agent property f, e.g. :mass, is taken as a mass to weight the two agents for the collision. By default no weighting happens.

One of the two agents can have infinite "mass", and then acts as an immovable object that specularly reflects the other agent. In this case of course momentum is not conserved, but kinetic energy is still conserved.


Index the database underlying the ContinuousSpace of the model.

This can drastically improve performance for finding neighboring agents, but adding new data can become slower because after each addition, index needs to be called again.

Lack of index won't be noticed for small databases. Only use it when you have many agents and not many additions of agents.

update_space!(model::ABM{A, ContinuousSpace}, agent)

Update the internal representation of continuous space to match the new position of the agent (useful in custom move_agent functions).


Data collection

The central simulation function is run!, which is mentioned in our Tutorial. But there are other functions that are related to simulations listed here.

collect_agent_data!(df, model, properties, step = 0)

Collect and add agent data into df (see run! for the dispatch rules of properties). step is given because the step number information is not known.

aggname(k, agg) → name

Return the name of the column of the aggregated data with key k and aggregating function agg.

paramscan(parameters, initialize; kwargs...)

Run the model with all the parameter value combinations given in parameters while initializing the model with initialize. This function uses DrWatson's dict_list internally. This means that every entry of parameters that is a Vector, contains many parameters and thus is scanned. All other entries of parameters that are not Vectors are not expanded in the scan. Keys of parameters should be of type Symbol.

initialize is a function that creates an ABM. It should accept keyword arguments, of which all values in parameters should be a subset. This means parameters can take both model and agent constructor properties.


All the following keywords are propagated into run!. Defaults are also listed for convenience: agent_step! = dummystep, n = 1, when = 1:n, model_step! = dummystep, step0::Bool = true, parallel::Bool = false, replicates::Int = 0. Keyword arguments such as adata and mdata are also propagated.

The following keywords modify the paramscan function:

include_constants::Bool=false determines whether constant parameters should be included in the output DataFrame.

progress::Bool = true whether to show the progress of simulations.


For example, the core loop of run! is just

df_agent = init_agent_dataframe(model, adata)
df_model = init_model_dataframe(model, mdata)

s = 0
while until(s, n, model)
  if should_we_collect(s, model, when)
      collect_agent_data!(df_agent, model, adata, s)
  if should_we_collect(s, model, when_model)
      collect_model_data!(df_model, model, mdata, s)
  step!(model, agent_step!, model_step!, 1)
  s += 1
return df_agent, df_model

(here until and should_we_collect are internal functions)


The schedulers of Agents.jl have a very simple interface. All schedulers are functions, that take as an input the ABM and return an iterator over agent IDs. Notice that this iterator can be a "true" iterator or can be just a standard vector of IDs. You can define your own scheduler according to this API and use it when making an AgentBasedModel.


Activate all agents once per step in the order dictated by the agent's container, which is arbitrary (the keys sequence of a dictionary). This is the fastest way to activate all agents once per step.


Activate agents once per step in a random order. Different random ordering is used at each different step.


At each step, activate the agents in an order dictated by their property, with agents with greater property acting first. property is a Symbol, which just dictates which field the agents to compare.

by_type(shuffle_types::Bool, shuffle_agents::Bool)

Useful only for mixed agent models using Union types.

  • Setting shuffle_types = true groups by agent type, but randomizes the type order.

Otherwise returns agents grouped in order of appearance in the Union.

  • shuffle_agents = true randomizes the order of agents within each group, false returns

the default order of the container (equivalent to fastest).

by_type((C, B, A), shuffle_agents::Bool)

Activate agents by type in specified order (since Unions are not order preserving). shuffle_agents = true randomizes the order of agents within each group.



Plotting functionality comes from AgentsPlots, which uses Plots.jl. You need to install both AgentsPlots, as well as a plotting backend (we use GR) to use the following functions.

The version of AgentsPlots is:

using Pkg
Status `~/build/JuliaDynamics/Agents.jl/docs/Project.toml`
  [7820620d] AgentsPlots v0.3.0 #master (https://github.com/JuliaDynamics/AgentsPlots.jl.git)
plotabm(model::ABM{A, <: ContinuousSpace}; ac, as, am, kwargs...)
plotabm(model::ABM{A, <: DiscreteSpace}; ac, as, am, kwargs...)

Plot the model as a scatter-plot, by configuring the agent shape, color and size via the keywords ac, as, am. These keywords can be constants, or they can be functions, each accepting an agent and outputting a valid value for color/shape/size.

The keyword scheduler = model.scheduler decides the plotting order of agents (which matters only if there is overlap).

The keyword offset is a function with argument offest(a::Agent). It targets scenarios where multiple agents existin within a grid cell as it adds an offset (same type as agent.pos) to the plotted agent position.

All other keywords are propagated into Plots.scatter and the plot is returned.

plotabm(model::ABM{A, <: GraphSpace}; ac, as, am, kwargs...)

This function is the same as plotabm for ContinuousSpace, but here the three key functions ac, as, am do not get an agent as an input but a vector of agents at each node of the graph. Their output is the same.

Here as defaults to length. Internally, the graphplot recipe is used, and all other kwargs... are propagated there.