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.getindex
— Methodmodel[id]
getindex(model::ABM, id::Integer)
Return an agent given its ID.
Base.getproperty
— Methodmodel.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.
Agents.random_agent
— Functionrandom_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.
Agents.nagents
— Functionnagents(model::ABM)
Return the number of agents in the model
.
Agents.allagents
— Functionallagents(model)
Return an iterator over all agents of the model.
Agents.allids
— Functionallids(model)
Return an iterator over all agent IDs of the model.
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!
— Functionadd_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
.
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
Agents.add_agent_pos!
— Functionadd_agent_pos!(agent::AbstractAgent, model::ABM) → agent
Add the agent to the model
at the agent's own position.
Agents.nextid
— Functionnextid(model::ABM) → id
Return a valid id
for creating a new agent with it.
Agents.random_position
— Functionrandom_position(model) → pos
Return a random position in the model's space (always with appropriate Type).
Moving agents
Agents.move_agent!
— Functionmove_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.
Agents.walk!
— Functionwalk!(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 ContinuousSpace
s.
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 onGridSpace
.
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
Agents.kill_agent!
— Functionkill_agent!(agent::AbstractAgent, model::ABM)
kill_agent!(id::Int, model::ABM)
Remove an agent from the model.
Agents.genocide!
— Functiongenocide!(model::ABM)
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
.
Agents.sample!
— Functionsample!(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.
Local area
Agents.nearby_ids
— Functionnearby_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 (thusr
is always an integer). For example, forr=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
.
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.
Agents.nearby_positions
— Functionnearby_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
.
nearby_positions(agent::AbstractAgent, model::ABM, r=1)
Same as nearby_positions(agent.pos, model, r)
.
Agents.edistance
— Functionedistance(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
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.positions
— Functionpositions(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.
Agents.ids_in_position
— Functionids_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.agents_in_position
— Functionagents_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
.
Agents.fill_space!
— Functionfill_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.
Agents.has_empty_positions
— Functionhas_empty_positions(model::ABM{<:DiscreteSpace})
Return true
if there are any positions in the model without agents.
Agents.empty_positions
— Functionempty_positions(model)
Return a list of positions that currently have no agents on them.
Agents.random_empty
— Functionrandom_empty(model::ABM{<:DiscreteSpace})
Return a random position without any agents, or nothing
if no such positions exist.
Agents.add_agent_single!
— Functionadd_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).
Agents.move_agent_single!
— Functionmove_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.
Base.isempty
— Methodisempty(position, model::ABM{<:DiscreteSpace})
Return true
if there are no agents in position
.
Continuous space exclusives
Agents.interacting_pairs
— Functioninteracting_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 radiusr
of each other, not only the nearest ones.:nearest
: agents are only paired with their true nearest neighbor (existing within radiusr
). 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 inscheduler
) will be paired.:types
: For mixed agent models only. Return every pair of agents within radiusr
(similar to:all
), only capturing pairs of differing types. For example, a model ofUnion{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 involveGrass
, can be achived by ascheduler
that doesn't scheduleGrass
types, i.e.:scheduler(model) = (a.id for a in allagents(model) if !(a isa Grass))
.
Example usage in Bacterial Growth.
Agents.nearest_neighbor
— Functionnearest_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
.
Agents.elastic_collision!
— Functionelastic_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
Agents.osm_latlon
— Functionosm_latlon(pos, model)
osm_latlon(agent, model)
Return (latitude, longitude) of current road or intersection position.
Agents.osm_intersection
— Functionosm_intersection(latlon::Tuple{Float64,Float64}, model::ABM{<:OpenStreetMapSpace})
Returns the nearest intersection position to (latitude, longitude). Quicker, but less precise than osm_road
.
Agents.osm_road
— Functionosm_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
.
Agents.osm_random_road_position
— Functionosm_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.
Agents.osm_plan_route
— Functionosm_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.
Agents.osm_random_route!
— Functionosm_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.
Agents.osm_road_length
— Functionosm_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.
Agents.osm_is_stationary
— Functionosm_is_stationary(agent)
Return true
if agent has no route left to follow and is therefore standing still.
Agents.osm_map_coordinates
— Functionosm_map_coordinates(agent, model::ABM{OpenStreetMapSpace})
Return a set of coordinates for an agent on the underlying map. Useful for plotting.
Graph space exclusives
LightGraphs.SimpleGraphs.add_edge!
— Functionadd_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.
Agents.add_node!
— Functionadd_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!
.
Agents.rem_node!
— Functionrem_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.
Parameter scanning
Agents.paramscan
— Functionparamscan(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 Vector
s 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 outputDataFrame
.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!)
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.init_agent_dataframe
— Functioninit_agent_dataframe(model, adata) → agent_df
Initialize a dataframe to add data later with collect_agent_data!
.
Agents.collect_agent_data!
— Functioncollect_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.
Agents.init_model_dataframe
— Functioninit_model_dataframe(model, mdata) → model_df
Initialize a dataframe to add data later with collect_model_data!
.
Agents.collect_model_data!
— Functioncollect_model_data!(df, model, properties, step = 0, obtainer = identity)
Same as collect_agent_data!
but for model data instead.
Agents.aggname
— Functionaggname(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.
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.fastest
— Functionfastest
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.
Agents.by_id
— Functionby_id
Activate agents at each step according to their id.
Agents.random_activation
— Functionrandom_activation
Activate agents once per step in a random order. Different random ordering is used at each different step.
Agents.partial_activation
— Functionpartial_activation(p)
At each step, activate only p
percentage of randomly chosen agents.
Agents.property_activation
— Functionproperty_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.
Agents.by_type
— Functionby_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 Union
s 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
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.plotabm
— Functionplotabm(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.
Agents.plotabm!
— Functionplotabm!(model)
plotabm!(plt, model)
Functionally the same as plotabm
, however this method appends to the active plot, or one identified as plt
.
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_exploration
— Functionabm_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 toabm_plot
.adata, mdata
: Same as the keyword arguments ofAgents.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 withadata, mdata
, the corresponding plots have a y-label named after the collected data. Instead, you can givealabels, mlabels
(vectors of strings with exactly same length asadata, mdata
), and these labels will be used instead.when = true
: When to perform data collection, as inAgents.run!
.spu = 1:100
: Values that the "spu" slider will obtain.