API

The API of Agents.jl is defined on top of the fundamental structures AgentBasedModel, Space, AbstractAgent which are described in the Tutorial page.

Agent/model retrieval

Base.getindexMethod
model[id]
getindex(model::ABM, id::Integer)

Return an agent given its ID.

source
Base.getpropertyMethod
model.prop
getproperty(model::ABM, prop::Symbol)

Return a property 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.

source
Agents.random_agentFunction
random_agent(model) → agent

Return a random agent from the model.

source
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.

source
Agents.allidsFunction
allids(model)

Return an iterator over all agent IDs of the model.

source

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.

Adding agents

Agents.add_agent!Function
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.

source
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.

Example

using Agents
mutable struct Agent <: AbstractAgent
    id::Int
    pos::Int
    w::Float64
    k::Bool
end
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
source
Agents.add_agent_pos!Function
add_agent_pos!(agent::AbstractAgent, model::ABM) → agent

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

source
Agents.nextidFunction
nextid(model::ABM) → id

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

source
Agents.random_positionFunction
random_position(model) → pos

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

source

Moving agents

Agents.move_agent!Function
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.

source
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).

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

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

source
Agents.walk!Function
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.

Keywords

  • 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.

source
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].

source

Removing agents

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

Remove an agent from the model.

source
Agents.genocide!Function
genocide!(model::ABM)

Kill all the agents of the model.

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

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

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

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

source
Agents.sample!Function
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.

Keywords

  • 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.

source

Local area

Agents.nearby_idsFunction
nearby_ids(position, model::ABM, r=1; 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.

Keywords

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.

source
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.

source
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.

source
Agents.nearby_positionsFunction
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.

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

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

source
Agents.edistanceFunction
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.

source

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
    id::Int
    pos::NTuple{4, Int}
end

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)
end
collect(allids(model))
2-element Array{Int64,1}:
 2
 3

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(3, (2, 1, 1, 1))

Discrete space exclusives

Agents.positionsFunction
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.
source
Agents.ids_in_positionFunction
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.

source
Agents.agents_in_positionFunction
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.

source
Agents.fill_space!Function
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.

source
Agents.has_empty_positionsFunction
has_empty_positions(model::ABM{<:DiscreteSpace})

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

source
Agents.random_emptyFunction
random_empty(model::ABM{<:DiscreteSpace})

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

source
Agents.add_agent_single!Function
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.

source
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).

source
Agents.move_agent_single!Function
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.

source
Base.isemptyMethod
isempty(position, model::ABM{<:DiscreteSpace})

Return true if there are no agents in position.

source

Continuous space exclusives

Agents.interacting_pairsFunction
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.

source
Agents.nearest_neighborFunction
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.

source
Agents.elastic_collision!Function
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.

source

OpenStreetMap space exclusives

Agents.osm_latlonFunction
osm_latlon(pos, model)
osm_latlon(agent, model)

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

source
Agents.osm_intersectionFunction
osm_intersection(latlon::Tuple{Float64,Float64}, model::ABM{<:OpenStreetMapSpace})

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

source
Agents.osm_roadFunction
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.

source
Agents.osm_random_road_positionFunction
osm_random_road_position(model::ABM{OpenStreetMapSpace})

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

source
Agents.osm_plan_routeFunction
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.

source
Agents.osm_random_route!Function
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.

source
Agents.osm_road_lengthFunction
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.

source
Agents.osm_map_coordinatesFunction
osm_map_coordinates(agent, model::ABM{OpenStreetMapSpace})

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

source

Graph space exclusives

LightGraphs.SimpleGraphs.add_edge!Function
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.

source
Agents.add_node!Function
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!.

source
Agents.rem_node!Function
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.

source

Parameter scanning

Agents.paramscanFunction
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.

Keywords

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.

Example

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)
    end
    return model
end

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!)
source

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.

Agents.collect_agent_data!Function
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.

source
Agents.aggnameFunction
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.

source

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)
  end
  if should_we_collect(s, model, when_model)
      collect_model_data!(df_model, model, mdata, s)
  end
  step!(model, agent_step!, model_step!, 1)
  s += 1
end
return df_agent, df_model

(here until and should_we_collect are internal functions)

Schedulers

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:

Agents.fastestFunction
fastest

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.

source
Agents.random_activationFunction
random_activation

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

source
Agents.property_activationFunction
property_activation(property)

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.

source
Agents.by_typeFunction
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).

source
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.

source

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
    w::Float64
end

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
    else
        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
    end
end

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

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

Plotting

Plotting functionality comes Plots.jl. You need to install a plotting backend (we use GR and pyplot) to use the following functions.

Agents.plotabmFunction
plotabm(model::ABM{<: ContinuousSpace}; ac, as, am, kwargs...)
plotabm(model::ABM{<: 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{<: 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.

source
Agents.plotabm!Function
plotabm!(model)
plotabm!(plt, model)

Functionally the same as plotabm, however this method appends to the active plot, or one identified as plt.

source

Interactive application

You need to be using InteractiveDynamics to access this application, as well as GLMakie to provide a plotting backend. Then you can use the function abm_data_exploration as explained in the Schelling's segregation model example.

InteractiveDynamics.abm_data_explorationFunction
abm_data_exploration(model::ABM, agent_step!, model_step!, params=Dict(); kwargs...)

Open an interactive application for exploring an agent based model and the impact of changing parameters on the time evolution. Requires Agents.

The application evolves an ABM interactively and plots its evolution, while allowing changing any of the model parameters interactively and also showing the evolution of collected data over time (if any are asked for, see below). The agent based model is plotted and animated exactly as in abm_play, and the arguments model, agent_step!, model_step! are propagated there as-is.

Calling abm_data_exploration returns: figure, agent_df, model_df. So you can save the figure, but you can also access the collected data (if any).

Interaction

Besides the basic time evolution interaction of abm_play, additional functionality here allows changing model parameters in real time, based on the provided fourth argument params. This is a dictionary which decides which parameters of the model will be configurable from the interactive application. Each entry of params is a pair of Symbol to an AbstractVector, and provides a range of possible values for the parameter named after the given symbol (see example online). Changing a value in the parameter slides is only updated into the actual model when pressing the "update" button.

The "reset" button resets the model to its original agent and space state but it updates it to the currently selected parameter values. A red vertical line is displayed in the data plots when resetting, for visual guidance.

Keywords

  • ac, am, as, scheduler, offset, equalaspect, scatterkwargs: propagated to abm_plot.
  • adata, mdata: Same as the keyword arguments of Agents.run!, and decide which data of the model/agents will be collected and plotted below the interactive plot. Notice that data collection can only occur on plotted steps (and thus steps not plotted due to "spu" are also not data-collected).
  • alabels, mlabels: If data are collected from agents or the model with adata, mdata, the corresponding plots have a y-label named after the collected data. Instead, you can give alabels, mlabels (vectors of strings with exactly same length as adata, mdata), and these labels will be used instead.
  • when = true: When to perform data collection, as in Agents.run!.
  • spu = 1:100: Values that the "spu" slider will obtain.