The API of Agents.jl is defined on top of the fundamental structures AgentBasedModel, Space, AbstractAgent which are described in the Tutorial page. In this page we list the remaining API functions, which constitute the bulk of Agents.jl functionality.

@agents macro

The @agent macro makes defining agent types within Agents.jl simple.

@agent YourAgentType{X, Y} AgentSupertype begin
    # etc...

Create a struct for your agents which includes the mandatory fields required to operate in a particular space. Depending on the space of your model, the AgentSupertype is chosen appropriately from GraphAgent, GridAgent, ContinuousAgent.



@agent Person{T} GridAgent{2} begin

will in fact create an agent appropriate for using with 2-dimensional GridSpace

mutable struct Person{T} <: AbstractAgent
    pos::NTuple{2, Int}

Combine with @agent to create an agent type for D-dimensional GraphSpace. It attributes the fields id::Int, pos::NTuple{D,Int} to the start of the agent type.


Combine with @agent to create an agent type for D-dimensional ContinuousSpace. It attributes the fields id::Int, pos::NTuple{D,Float64}, vel::NTuple{D,Float64} to the start of the agent type.


Combine with @agent to create an agent type for OpenStreetMapSpace. It attributes the fields id::Int, pos::Tuple{Int,Int,Float64}, route::Vector{Int}, destination::Tuple{Int,Int,Float64} to the start of the agent type.


Agent/model retrieval and access

getindex(model::ABM, id::Integer)

Return an agent given its ID.

getproperty(model::ABM, :prop)

Return a property with name :prop from the current model, assuming the model properties are either a dictionary with key type Symbol or a Julia struct. For example, if a model has the set of properties Dict(:weight => 5, :current => false), retrieving these values can be obtained via model.weight.

The property names :agents, :space, :scheduler, :properties, :maxid are internals and should not be accessed by the user.

seed!(model [, seed])

Reseed the random number pool of the model with the given seed or a random one, when using a pseudo-random number generator like MersenneTwister.

random_agent(model) → agent

Return a random agent from the model.

random_agent(model, condition) → agent

Return a random agent from the model that satisfies condition(agent) == true. The function generates a random permutation of agent IDs and iterates through them. If no agent satisfies the condition, nothing is returned instead.


Return an iterator over all agent IDs of the model.


Available spaces

Here we list the spaces that are available "out of the box" from Agents.jl. To create your own, see Creating a new space type.

Discrete spaces


Create a GraphSpace instance that is underlined by an arbitrary graph from LightGraphs.jl. The position type for this space is Int, use GraphAgent for convenience. The underlying graph can be altered using add_node! and rem_node!.

GraphSpace represents a space where each node (i.e. position) of a graph can hold an arbitrary amount of agents, and each agent can move between the nodes of the graph. An example of its usage can be found in SIR model for the spread of COVID-19. If you want to model social networks, where each agent is equivalent with a node of a graph, you're better of using nothing (or other spaces) as the model space, and using a graph from LightGraphs.jl directly in the model parameters, as shown in the Social networks with LightGraphs.jl integration example.

GridSpace(d::NTuple{D, Int}; periodic = true, metric = :chebyshev)

Create a GridSpace that has size given by the tuple d, having D ≥ 1 dimensions. Optionally decide whether the space will be periodic and what will be the distance metric used, which decides the behavior of e.g. nearby_ids. The position type for this space is NTuple{D, Int}, use GridAgent for convenience. In our examples we typically use Dims{D} instead of NTuple{D, Int} (they are equivalent). Valid positions have indices in the range 1:d[i] for the ith dimension.

:chebyshev metric means that the r-neighborhood of a position are all positions within the hypercube having side length of 2*floor(r) and being centered in the origin position.

:euclidean metric means that the r-neighborhood of a position are all positions whose cartesian indices have Euclidean distance ≤ r from the cartesian index of the given position.

An example using GridSpace is the Forest fire model.


Continuous spaces

ContinuousSpace(extent::NTuple{D, <:Real}, spacing = min(extent...)/10; kwargs...)

Create a D-dimensional ContinuousSpace in range 0 to (but not including) extent. spacing configures the compartment spacing that the space is divided in, in order to accelerate nearest neighbor functions like nearby_ids. All dimensions in extent must be completely divisible by spacing (i.e. no fractional remainder). Your agent positions (field pos) must be of type NTuple{D, <:Real}, use ContinuousAgent for convenience. In addition it is useful for agents to have a field vel::NTuple{D, <:Real} to use in conjunction with move_agent!.

The keyword periodic = true configures whether the space is periodic or not. If set to false an error will occur if an agent's position exceeds the boundary.

The keyword argument update_vel! is a function, update_vel!(agent, model) that updates the agent's velocity before the agent has been moved, see move_agent!. You can of course change the agents' velocities during the agent interaction, the update_vel! functionality targets spatial force fields acting on the agents individually (e.g. some magnetic field). By default no update is done this way. If you use update_vel!, the agent type must have a field vel::NTuple{D, <:Real}.

There is no "best" choice for the value of spacing. If you need optimal performance it's advised to set up a benchmark over a range of choices. The value matters most when searching for neighbors. In Models.flocking for example, an optimal value for spacing is 66% of the search distance.

OpenStreetMapSpace(path::AbstractString; kwargs...)

Create a space residing on the Open Street Map (OSM) file provided via path.

The abbreviation OSMSpace may be used interchangeably.

Much of the functionality of this space is provided by interfacing with OpenStreetMapX.jl, for example the two keyword arguments use_cache = false and trim_to_connected_graph = true can be passed into the OpenStreetMapX.get_map_data function.

For details on how to obtain an OSM file for your use case, consult the OpenStreetMapX.jl README. We provide a variable TEST_MAP to use as a path for testing.

This space represents the underlying map as a continuous entity choosing accuracy over performance. An example of its usage can be found in Zombie Outbreak.

If your solution can tolerate routes to and from intersections only, a faster implementation can be achieved by using the graph representation of your map provided by OpenStreetMapX.jl. For tips on how to implement this, see our integration example: Social networks with LightGraphs.jl.

The OSMAgent

The base properties for an agent residing on an OSMSpace are as follows:

mutable struct OSMAgent <: AbstractAgent

Current position and destination tuples are represented as (start intersection index, finish intersection index, distance travelled in meters). The route is an ordered list of intersections, providing a path to reach destination.

Further details can be found in OSMAgent.


There are two ways to generate a route, depending on the situation.

  1. osm_plan_route, which provides :shortest and :fastest paths (with the option of a return_trip) between intersections or positions.
  2. osm_random_route!, choses a new destination an plans a new path to it; overriding the current route (if any).

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 these are described further below in this page.

Adding agents

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

Add the agent to the model in the given position. If pos 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.

The type of pos must match the underlying space position type.

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

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. Optionally provide a position to add the agent to as first argument, which must match the space position type.

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 (see example below).

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

Use this version for mixed agent models, with A the agent type you wish to create (to be called as A(id, pos, args...; kwargs...)), because it is otherwise not possible to deduce a constructor for A.


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

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

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

nextid(model::ABM) → id

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

random_position(model) → pos

Return a random position in the model's space (always with appropriate Type).


Moving agents

move_agent!(agent [, pos], model::ABM) → agent

Move agent to the given position, or to a random one if a position is not given. pos must have the appropriate position type depending on the space type.

The agent's position is updated to match pos after the move.

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

Propagate the agent forwards one step according to its velocity, after updating the agent's velocity (if configured, 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. If you want to move the agent to a specified position, do move_agent!(agent, pos, model).

move_agent!(agent, model::ABM{<:OpenStreetMapSpace}, distance::Real)

Move an agent by distance in meters along its planned route.

walk!(agent, direction::NTuple, model; ifempty = false)

Move agent in the given direction respecting periodic boundary conditions. If periodic = false, agents will walk to, but not exceed the boundary value. Possible on both GridSpace and ContinuousSpaces.

The dimensionality of direction must be the same as the space. GridSpace asks for Int, and ContinuousSpace for Float64 vectors, describing the walk distance in each direction. direction = (2, -3) is an example of a valid direction on a GridSpace, which moves the agent to the right 2 positions and down 3 positions. Velocity is ignored for this opreation in ContinuousSpace.


  • ifempty will check that the target position is unnocupied and only move if that's true. Available only on GridSpace.

Example usage in Battle Royale.

walk!(agent, rand, model)

Invoke a random walk by providing the rand function in place of distance. For GridSpace, the walk will cover ±1 positions in all directions, ContinuousSpace will reside within [-1, 1].


Removing agents

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

Remove an agent from the model.


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.

Example usage in Wright-Fisher model of evolution.


Discrete space exclusives

positions(model::ABM{<:DiscreteSpace}) → ns

Return an iterator over all positions of a model with a discrete space.

positions(model::ABM{<:DiscreteSpace}, by::Symbol) → ns

Return all positions of a model with a discrete space, sorting them using the argument by which can be:

  • :random - randomly sorted
  • :population - positions are sorted depending on how many agents they accommodate. The more populated positions are first.
ids_in_position(position, model::ABM{<:DiscreteSpace})
ids_in_position(agent, model::ABM{<:DiscreteSpace})

Return the ids of agents in the position corresponding to position or position of agent.

agents_in_position(position, model::ABM{<:DiscreteSpace})
agents_in_position(agent, model::ABM{<:DiscreteSpace})

Return the agents in the position corresponding to position or position of agent.

fill_space!([A ,] model::ABM{<:DiscreteSpace,A}, args...; kwargs...)
fill_space!([A ,] model::ABM{<:DiscreteSpace,A}, f::Function; kwargs...)

Add one agent to each position in the model's space. Similarly with add_agent!, the function creates the necessary agents and the args...; kwargs... are propagated into agent creation. If instead of args... a function f is provided, then args = f(pos) is the result of applying f where pos is each position (tuple for grid, index for graph).

An optional first argument is an agent type to be created, and targets mixed agent models where the agent constructor cannot be deduced (since it is a union).

Example usage in Daisyworld.


Return true if there are any positions in the model without agents.


Return a random position without any agents, or nothing if no such positions exist.

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

Add the agent to a random position in the space while respecting a maximum of one agent per position. This function does nothing if there aren't any empty positions.

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

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

move_agent_single!(agent, model::ABM{<:DiscreteSpace}) → agentt

Move agent to a random position while respecting a maximum of one agent per position. If there are no empty positions, the agent won't move.

isempty(position, model::ABM{<:DiscreteSpace})

Return true if there are no agents in position.


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 distance, then by next id in scheduler) will be paired.
  • :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(model) = (a.id for a in allagents(model) if !(a isa Grass)).

Example usage in Bacterial Growth.

nearest_neighbor(agent, model::ABM{<:ContinuousSpace}, r) → nearest

Return the agent that has the closest distance to given agent. 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.

Example usage in Continuous space social distancing for COVID-19.


OpenStreetMap space exclusives

osm_latlon(pos, model)
osm_latlon(agent, model)

Return (latitude, longitude) of current road or intersection position.

osm_intersection(latlon::Tuple{Float64,Float64}, model::ABM{<:OpenStreetMapSpace})

Returns the nearest intersection position to (latitude, longitude). Quicker, but less precise than osm_road.

osm_road(latlon::Tuple{Float64,Float64}, model::ABM{<:OpenStreetMapSpace})

Returns a location on a road nearest to (latitude, longitude). Slower, but more precise than osm_intersection.


Similar to random_position, but rather than providing only intersections, this method returns a location somewhere on a road heading in a random direction.

osm_plan_route(start, finish, model::ABM{<:OpenStreetMapSpace};
               by = :shortest, return_trip = false, kwargs...)

Generate a list of intersections between start and finish points on the map. start and finish can either be intersections (Int) or positions (Tuple{Int,Int,Float64}).

When either point is a position, the associated intersection index will be removed from the route to avoid double counting.

Route is planned via the shortest path by default (by = :shortest), but can also be planned by = :fastest. Road speeds are needed for this method which can be passed in via extra keyword arguments. Consult the OpenStreetMapX documentation for more details.

If return_trip = true, a route will be planned from start -> finish -> start.

osm_random_route!(agent, model::ABM{<:OpenStreetMapSpace})

Selects a random destination and plans a route from the agent's current position. Will overwrite any current route.

osm_road_length(start::Int, finish::Int, model)
osm_road_length(pos::Tuple{Int,Int,Float64}, model)

Return the road length (in meters) between two intersections given by intersection ids.

osm_map_coordinates(agent, model::ABM{OpenStreetMapSpace})

Return a set of coordinates for an agent on the underlying map. Useful for plotting.


Graph space exclusives

add_edge!(model::ABM{<: GraphSpace}, n::Int, m::Int)

Add a new edge (relationship between two positions) to the graph. Returns a boolean, true if the operation was succesful.

add_node!(model::ABM{<: GraphSpace})

Add a new node (i.e. possible position) to the model's graph and return it. You can connect this new node with existing ones using add_edge!.

rem_node!(model::ABM{<: GraphSpace}, n::Int)

Remove node (i.e. position) n from the model's graph. All agents in that node are killed.

Warning: LightGraphs.jl (and thus Agents.jl) swaps the index of the last node with that of the one to be removed, while every other node remains as is. This means that when doing rem_node!(n, model) the last node becomes the n-th node while the previous n-th node (and all its edges and agents) are deleted.


Local area

nearby_ids(position, model::ABM, r; kwargs...) → ids

Return an iterable of the ids of the agents within "radius" r of the given position (which must match type with the spatial structure of the model).

What the "radius" means depends on the space type:

  • GraphSpace: the degree of neighbors in the graph (thus r is always an integer). For example, for r=2 include first and second degree neighbors.
  • GridSpace, ContinuousSpace: Either Chebyshev (also called Moore) or Euclidean distance, in the space of cartesian indices.
  • GridSpace can also take a tuple argument, e.g. r = (5, 2) for a 2D space, which

extends 5 positions in the x direction and 2 in the y. Only possible with Chebyshev spaces.

  • OpenStreetMapSpace: r is equivalent with distance (in meters) neeeded to be travelled according to existing roads in order to reach given position.


Keyword arguments are space-specific. For GraphSpace the keyword neighbor_type=:default can be used to select differing neighbors depending on the underlying graph directionality type.

  • :default returns neighbors of a vertex (position). If graph is directed, this is equivalent to :out. For undirected graphs, all options are equivalent to :out.
  • :all returns both :in and :out neighbors.
  • :in returns incoming vertex neighbors.
  • :out returns outgoing vertex neighbors.

For ContinuousSpace, the keyword exact=false controls whether the found neighbors are exactly accurate or approximate (with approximate always being a strict over-estimation), see ContinuousSpace.

nearby_ids(agent::AbstractAgent, model::ABM, r=1)

Same as nearby_ids(agent.pos, model, r) but the iterable excludes the given agent's id.

nearby_ids(pos, model::ABM{<:GridSpace}, r::Vector{Tuple{Int,UnitRange{Int}}})

Return an iterable of ids over specified dimensions of space with fine grained control of distances from pos using each value of r via the (dimension, range) pattern.

Note: Only available for use with non-periodic chebyshev grids.

Example, with a GridSpace((100, 100, 10)): r = [(1, -1:1), (3, 1:2)] searches dimension 1 one step either side of the current position (as well as the current position) and the third dimension searches two positions above current.

For a complete tutorial on how to use this method, see Battle Royale.

nearby_agents(agent, model::ABM, args...; kwargs...) -> agent

Return an iterable of the agents near the position of the given agent.

The value of the argument r and possible keywords operate identically to nearby_ids.

nearby_positions(position, model::ABM, r=1; kwargs...) → positions

Return an iterable of all positions within "radius" r of the given position (which excludes given position). The position must match type with the spatial structure of the model.

The value of r and possible keywords operate identically to nearby_ids.

This function only makes sense for discrete spaces with a finite amount of positions.

nearby_positions(position, model::ABM{<:OpenStreetMapSpace}; kwargs...) → positions

For OpenStreetMapSpace this means "nearby intersections" and operates directly on the underlying graph of the OSM, providing the intersection nodes nearest to the given position.

nearby_positions(agent::AbstractAgent, model::ABM, r=1)

Same as nearby_positions(agent.pos, model, r).

edistance(a, b, model::ABM)

Return the euclidean distance between a and b (either agents or agent positions), respecting periodic boundary conditions (if in use). Works with any space where it makes sense: currently GridSpace and ContinuousSpace.

Example usage in the Flock model.


A note on iteration

Most iteration in Agents.jl is dynamic and lazy, when possible, for performance reasons.

Dynamic means that when iterating over the result of e.g. the ids_in_position function, the iterator will be affected by actions that would alter its contents. Specifically, imagine the scenario

using Agents
mutable struct Agent <: AbstractAgent
    pos::NTuple{4, Int}

model = ABM(Agent, GridSpace((5, 5, 5, 5)))
add_agent!((1, 1, 1, 1), model)
add_agent!((1, 1, 1, 1), model)
add_agent!((2, 1, 1, 1), model)
for id in ids_in_position((1, 1, 1, 1), model)
    kill_agent!(id, model)
2-element Array{Int64,1}:

You will notice that only 1 agent got killed. This is simply because the final state of the iteration of ids_in_position was reached unnaturally, because the length of its output was reduced by 1 during iteration. To avoid problems like these, you need to collect the iterator to have a non dynamic version.

Lazy means that when possible the outputs of the iteration are not collected and instead are generated on the fly. A good example to illustrate this is nearby_ids, where doing something like

a = random_agent(model)
sort!(nearby_ids(random_agent(model), model))

leads to error, since you cannot sort! the returned iterator. This can be easily solved by adding a collect in between:

a = random_agent(model)
sort!(collect(nearby_agents(a, model)))
1-element Array{Main.ex-docs.Agent,1}:
 Main.ex-docs.Agent(2, (1, 1, 1, 1))

Higher-order interactions

There may be times when pair-wise, triplet-wise or higher interactions need to be accounted for across most or all of the model's agent population. The following methods provide an interface for such calculation.

iter_agent_groups(order::Int, model::ABM; scheduler = by_id)

Return an iterator over all agents of the model, grouped by order. When order = 2, the iterator returns agent pairs, e.g (agent1, agent2) and when order = 3: agent triples, e.g. (agent1, agent7, agent8). order must be larger than 1 but has no upper bound.

Index order is provided by the by_id scheduler by default, but can be altered with the scheduler keyword.

map_agent_groups(order::Int, f::Function, model::ABM; kwargs...)
map_agent_groups(order::Int, f::Function, model::ABM, filter::Function; kwargs...)

Applies function f to all grouped agents of an iter_agent_groups iterator. kwargs are passed to the iterator method. f must take the form f(NTuple{O,AgentType}), where the dimension O is equal to order.

Optionally, a filter function that accepts an iterable and returns a Bool can be applied to remove unwanted matches from the results. Note: This option cannot keep matrix order, so should be used in conjuction with index_mapped_groups to associate agent ids with the resultant data.

index_mapped_groups(order::Int, model::ABM; scheduler = by_id)
index_mapped_groups(order::Int, model::ABM, filter::Function; scheduler = by_id)

Return an iterable of agent ids in the model, meeting the filter criterea if used.


Parameter scanning

paramscan(parameters, initialize; kwargs...) → adf, mdf

Perform a parameter scan of a ABM simulation output by collecting data from all parameter combinations into dataframes (one for agent data, one for model data). The dataframes columns are both the collected data (as in run!) but also the input parameter values used.

parameters is a dictionary with key type Symbol which contains various parameters that will be scanned over (as well as other parameters that remain constant). This function uses DrWatson's dict_list convention. 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.

The second argument initialize is a function that creates an ABM and returns it. It should accept keyword arguments which are the keys of the parameters dictionary. Since the user decides how to use input arguments to make an ABM, parameters can be used to affect model properties, space type and creation as well as agent properties, see the example below.


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.

The following keywords are propagated into run!:

agent_step!, model_step!, n, when, step0, parallel, replicates, adata, mdata

agent_step!, model_step!, n and at least one of adata, mdata are mandatory.


A runnable example that uses paramscan is shown in Schelling's segregation model. There we define

function initialize(; numagents = 320, griddims = (20, 20), min_to_be_happy = 3)
    space = GridSpace(griddims, moore = true)
    properties = Dict(:min_to_be_happy => min_to_be_happy)
    model = ABM(SchellingAgent, space;
                properties = properties, scheduler = random_activation)
    for n in 1:numagents
        agent = SchellingAgent(n, (1, 1), false, n < numagents / 2 ? 1 : 2)
        add_agent_single!(agent, model)
    return model

and do a parameter scan by doing:

happyperc(moods) = count(x -> x == true, moods) / length(moods)
adata = [(:mood, happyperc)]

parameters = Dict(
    :min_to_be_happy => collect(2:5), # expanded
    :numagents => [200, 300],         # expanded
    :griddims => (20, 20),            # not Vector = not expanded

data, _ = paramscan(parameters, initialize; adata = adata, n = 3, agent_step! = agent_step!)

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. Specifically, these functions aid in making custom data collection loops, instead of using the run! function.

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)

run! uses the following functions:

collect_agent_data!(df, model, properties, step = 0; obtainer = identity)

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

aggname(k) → name
aggname(k, agg) → name
aggname(k, agg, condition) → name

Return the name of the column of the i-th collected data where k = adata[i] (or mdata[i]). aggname also accepts tuples with aggregate and conditional values.



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 (non-allocated) 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. You can also use the function schedule(model) to obtain the scheduled ID list, if you prefer to write your own step!-like loop.

Notice that schedulers can be given directly to model creation, and thus become the "default" scheduler a model uses, but they can just as easily be incorporated in a model_step! function as shown in Advanced stepping.

Predefined schedulers

Some useful schedulers are available below as part of the Agents.jl public API:


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.


Advanced scheduling

You can use Function-like-objects to make your scheduling possible of arbitrary events. For example, imagine that after the n-th step of your simulation you want to fundamentally change the order of agents. To achieve this you can define

mutable struct MyScheduler
    n::Int # step number

and then define a calling method for it like so

function (ms::MyScheduler)(model::ABM)
    ms.n += 1 # increment internal counter by 1 each time its called
              # be careful to use a *new* instance of this scheduler when plotting!
    if ms.n < 10
        return allids(model) # order doesn't matter in this case
        ids = collect(allids(model))
        # filter all ids whose agents have `w` less than some amount
        filter!(id -> model[id].w < ms.w, ids)
        return ids

and pass it to e.g. step! by initializing it

ms = MyScheduler(100, 0.5)
step!(model, agentstep, modelstep, 100; scheduler = ms)