# API

The core API is defined by `AgentBasedModel`

, Space, `AbstractAgent`

and `step!`

, which are described in the Tutorial page. The functionality described here builds on top of the core API.

## Agent information and retrieval

`Agents.space_neighbors`

— Function`space_neighbors(position, model::ABM, r) → ids`

Return the ids of the agents neighboring the given `position`

(which must match type with the spatial structure of the `model`

). `r`

is the radius to search for agents.

For `DiscreteSpace`

s `r`

must be integer and defines higher degree neighbors. For example, for `r=2`

include first and second degree neighbors, that is, neighbors and neighbors of neighbors.

For `ContinuousSpace`

, `r`

is real number and finds all neighbors within distance `r`

(based on the space's metric).

The `node_neighbors`

keyword `neighbor_type`

can also be used here to restrict the spatial search on directed graphs.

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

Call `space_neighbors(agent.pos, model, r)`

but *exclude* the given `agent`

from the neighbors.

`Agents.random_agent`

— Function`random_agent(model)`

Return a random agent from the model.

`Agents.nagents`

— Function`nagents(model::ABM)`

Return the number of agents in the `model`

.

`Agents.allagents`

— Function`allagents(model)`

Return an iterator over all agents of the model.

`Agents.nextid`

— Function`nextid(model::ABM) → id`

Return a valid `id`

for creating a new agent with it.

## Model-Agent interaction

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

`Agents.add_agent!`

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

Add the `agent`

to the `position`

in the space and to the list of agents. If `position`

is not given, the `agent`

is added to a random position. The `agent`

's position is always updated to match `position`

, and therefore for `add_agent!`

the position of the `agent`

is meaningless. Use `add_agent_pos!`

to use the `agent`

's position.

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

Create and add a new agent to the model by constructing an agent of the type of the `model`

. Propagate all *extra* positional arguments and keyword arguemts to the agent constructor.

Notice that this function takes care of setting the agent's id *and* position and thus `args...`

and `kwargs...`

are propagated to other fields the agent has.

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

**Example**

```
using Agents
mutable struct Agent <: AbstractAgent
id::Int
pos::Int
w::Float64
k::Bool
end
Agent(id; w, k) = Agent(id, w, k) # keyword constructor
model = ABM(Agent, GraphSpace(complete_digraph(5)))
add_agent!(model, 1, 0.5, true) # incorrect: id is set internally
add_agent!(model, 0.5, true) # correct: weight becomes 0.5
add_agent!(5, model, 0.5, true) # add at node 5
add_agent!(model; w = 0.5, k =true) # use keywords: weight becomes 0.5
```

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

`Agents.add_agent_single!`

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

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

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

Same as `add_agent!(model, properties...)`

but ensures that it adds an agent into a node with no other agents (does nothing if no such node exists).

`Agents.move_agent!`

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

Propagate the agent forwards one step according to its velocity, *after* updating the agent's velocity (see `ContinuousSpace`

). Also take care of periodic boundary conditions.

For this continuous space version of `move_agent!`

, the "evolution algorithm" is a trivial Euler scheme with `dt`

the step size, i.e. the agent position is updated as `agent.pos += agent.vel * dt`

.

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

Propagate the agent forwards one step according to `vel`

and the model's space, with `dt`

as the time step. (`update_vel!`

is not used)

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

Add `agentID`

to the new position `pos`

(or a random one if `pos`

is not given) in the model and remove it from the old position (also update the agent to have the new position). `pos`

must be the appropriate position type depending on the space type.

`Agents.move_agent_single!`

— Function`move_agent_single!(agent::AbstractAgent, model::ABM) → agent`

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

`Agents.kill_agent!`

— Function`kill_agent!(agent::AbstractAgent, model::ABM)`

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

`Agents.genocide!`

— Function`genocide!(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!`

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

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

.

## Discrete space exclusives

`Agents.node_neighbors`

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

Return all nodes that are neighbors to the given `node`

, which can be an `Int`

for `GraphSpace`

, or a `NTuple{Int}`

for `GridSpace`

.

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

Same as above, but uses `agent.pos`

as `node`

.

Keyword argument `neighbor_type=:default`

can be used to select differing neighbors depending on the underlying graph type.

`:default`

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

to `:out`

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

.

`:all`

returns both`:in`

and`:out`

neighbors.`:in`

returns incoming vertex neighbors.`:out`

returns outgoing vertex neighbors.

`LightGraphs.nv`

— Method`nv(model::ABM)`

Return the number of nodes (vertices) in the `model`

space.

`LightGraphs.ne`

— Method`ne(model::ABM)`

Return the number of edges in the `model`

space.

`Agents.has_empty_nodes`

— Function`has_empty_nodes(model)`

Return true if there are empty nodes in the `model`

.

`Agents.find_empty_nodes`

— Function`find_empty_nodes(model::ABM)`

Returns the indices of empty nodes on the model space.

`Agents.pick_empty`

— Function`pick_empty(model)`

Return a random empty node or `0`

if there are no empty nodes.

`Agents.get_node_contents`

— Function`get_node_contents(node, model)`

Return the ids of agents in the `node`

of the model's space (which is an integer for `GraphSpace`

and a tuple for `GridSpace`

).

`get_node_contents(agent::AbstractAgent, model)`

Return all agents' ids in the same node as the `agent`

(including the agent's own id).

`Agents.get_node_agents`

— Function`get_node_agents(x, model)`

Same as `get_node_contents(x, model)`

but directly returns the list of agents instead of just the list of IDs.

`Base.isempty`

— Method`isempty(node::Int, model::ABM)`

Return `true`

if there are no agents in `node`

.

`Agents.NodeIterator`

— Type`NodeIterator(model) → iterator`

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

`Agents.nodes`

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

Return a vector of the node ids of the `model`

that you can iterate over. The `ns`

are sorted depending on `by`

:

`:id`

- just sorted by their number`:random`

- randomly sorted`:population`

- nodes are sorted depending on how many agents they accommodate. The more populated nodes are first.

## Continuous space exclusives

`Agents.interacting_pairs`

— Function`interacting_pairs(model, r, method; scheduler = model.scheduler)`

Return an iterator that yields unique pairs of agents `(a1, a2)`

that are close neighbors to each other, within some interaction radius `r`

.

This function is usefully combined with `model_step!`

, when one wants to perform some pairwise interaction across all pairs of close agents once (and does not want to trigger the event twice, both with `a1`

and with `a2`

, which is unavoidable when using `agent_step!`

).

The argument `method`

provides three pairing scenarios

`:all`

: return every pair of agents that are within radius`r`

of each other, not only the nearest ones.`:nearest`

: agents are only paired with their true nearest neighbor (existing within radius`r`

). Each agent can only belong to one pair, therefore if two agents share the same nearest neighbor only one of them (sorted by id) will be paired.`:scheduler`

: agents are scanned according to the given keyword`scheduler`

(by default the model's scheduler), and each scanned agent is paired to its nearest neighbor. Similar to`:nearest`

, each agent can belong to only one pair. This functionality is useful e.g. when you want some agents to be paired "guaranteed", even if some other agents might be nearest to each other.`:types`

: For mixed agent models only. Return every pair of agents within radius`r`

(similar to`:all`

), only capturing pairs of differing types. For example, a model of`Union{Sheep,Wolf}`

will only return pairs of`(Sheep, Wolf)`

. In the case of multiple agent types,*e.g.*`Union{Sheep, Wolf, Grass}`

, skipping pairings that involve`Grass`

, can be achived by a`scheduler`

that doesn't schedule`Grass`

types,*i.e.*:`scheduler = [a.id for a in allagents(model) of !(a isa Grass)]`

.

`Agents.nearest_neighbor`

— Function`nearest_neighbor(agent, model, r) → nearest`

Return the agent that has the closest distance to given `agent`

, according to the space's metric. Valid only in continuous space. Return `nothing`

if no agent is within distance `r`

.

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

`Agents.index!`

— Function`index!(model)`

Index the database underlying the `ContinuousSpace`

of the model.

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

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

`Agents.update_space!`

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

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

functions).

## Data collection

The central simulation function is `run!`

, which is mentioned in our Tutorial. But there are other functions that are related to simulations listed here.

`Agents.init_agent_dataframe`

— Function`init_agent_dataframe(model, properties) → agent_df`

Initialize a dataframe to add data later with `collect_agent_data!`

.

`Agents.collect_agent_data!`

— Function`collect_agent_data!(df, model, properties, step = 0)`

Collect and add agent data into `df`

(see `run!`

for the dispatch rules of `properties`

). `step`

is given because the step number information is not known.

`Agents.init_model_dataframe`

— Function`init_model_dataframe(model, properties) → model_df`

Initialize a dataframe to add data later with `collect_model_data!`

.

`Agents.collect_model_data!`

— Function`collect_model_data!(df, model, properties, step = 0)`

Same as `collect_agent_data!`

but for model data instead.

`Agents.aggname`

— Function`aggname(k, agg) → name`

Return the name of the column of the aggregated data with key `k`

and aggregating function `agg`

.

`Agents.paramscan`

— Function`paramscan(parameters, initialize; kwargs...)`

Run the model with all the parameter value combinations given in `parameters`

while initializing the model with `initialize`

. This function uses `DrWatson`

's `dict_list`

internally. This means that every entry of `parameters`

that is a `Vector`

, contains many parameters and thus is scanned. All other entries of `parameters`

that are not `Vector`

s are not expanded in the scan. Keys of `parameters`

should be of type `Symbol`

.

`initialize`

is a function that creates an ABM. It should accept keyword arguments, of which all values in `parameters`

should be a subset. This means `parameters`

can take both model and agent constructor properties.

**Keywords**

All the following keywords are propagated into `run!`

. Defaults are also listed for convenience: `agent_step! = dummystep, n = 1, when = 1:n, model_step! = dummystep`

, `step0::Bool = true`

, `parallel::Bool = false`

, `replicates::Int = 0`

. Keyword arguments such as `adata`

and `mdata`

are also propagated.

The following keywords modify the `paramscan`

function:

`include_constants::Bool=false`

determines whether constant parameters should be included in the output `DataFrame`

.

`progress::Bool = true`

whether to show the progress of simulations.

For example, the core loop of `run!`

is just

```
df_agent = init_agent_dataframe(model, adata)
df_model = init_model_dataframe(model, mdata)
s = 0
while until(s, n, model)
if should_we_collect(s, model, when)
collect_agent_data!(df_agent, model, adata, s)
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 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`

.

`Agents.fastest`

— Function`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.

`Agents.by_id`

— Function`by_id`

Activate agents at each step according to their id.

`Agents.random_activation`

— Function`random_activation`

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

`Agents.partial_activation`

— Function`partial_activation(p)`

At each step, activate only `p`

percentage of randomly chosen agents.

`Agents.property_activation`

— Function`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.

`Agents.by_type`

— Function`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 `Union`

s are not order preserving). `shuffle_agents = true`

randomizes the order of agents within each group.

## Plotting

Plotting functionality comes from `AgentsPlots`

, which uses Plots.jl. You need to install both `AgentsPlots`

, as well as a plotting backend (we use GR) to use the following functions.

The version of `AgentsPlots`

is:

```
using Pkg
Pkg.status("AgentsPlots")
```

```
Status `~/build/JuliaDynamics/Agents.jl/docs/Project.toml`
[7820620d] AgentsPlots v0.3.0 #master (https://github.com/JuliaDynamics/AgentsPlots.jl.git)
```

`AgentsPlots.plotabm`

— Function```
plotabm(model::ABM{A, <: ContinuousSpace}; ac, as, am, kwargs...)
plotabm(model::ABM{A, <: DiscreteSpace}; ac, as, am, kwargs...)
```

Plot the `model`

as a `scatter`

-plot, by configuring the agent shape, color and size via the keywords `ac, as, am`

. These keywords can be constants, or they can be functions, each accepting an agent and outputting a valid value for color/shape/size.

The keyword `scheduler = model.scheduler`

decides the plotting order of agents (which matters only if there is overlap).

The keyword `offset`

is a function with argument `offest(a::Agent)`

. It targets scenarios where multiple agents existin within a grid cell as it adds an offset (same type as `agent.pos`

) to the plotted agent position.

All other keywords are propagated into `Plots.scatter`

and the plot is returned.

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

This function is the same as `plotabm`

for `ContinuousSpace`

, but here the three key functions `ac, as, am`

do not get an agent as an input but a vector of agents at each node of the graph. Their output is the same.

Here `as`

defaults to `length`

. Internally, the `graphplot`

recipe is used, and all other `kwargs...`

are propagated there.