# API

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.

`@agent`

macro

The `@agent`

macro makes defining agent types within Agents.jl simple.

`Agents.@agent`

— Macro```
@agent YourAgentType{X, Y} AgentSupertype begin
some_property::X
other_extra_property::Y
# etc...
end
```

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`

, `OSMAgent`

.

**Example**

Using

```
@agent Person{T} GridAgent{2} begin
age::Int
moneyz::T
end
```

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

```
mutable struct Person{T} <: AbstractAgent
id::Int
pos::NTuple{2, Int}
age::Int
moneyz::T
end
```

`Agents.GraphAgent`

— Type`GraphAgent`

Combine with `@agent`

to create an agent type for `GraphSpace`

. It attributes the fields `id::Int, pos::Int`

to the start of the agent type.

`Agents.GridAgent`

— Type`GridAgent{D}`

Combine with `@agent`

to create an agent type for `D`

-dimensional `GridSpace`

. It attributes the fields `id::Int, pos::NTuple{D,Int}`

to the start of the agent type.

`Agents.ContinuousAgent`

— Type`ContinuousAgent{D}`

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.

`Agents.OSMAgent`

— Type`OSMAgent`

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

`Base.getindex`

— Method```
model[id]
getindex(model::ABM, id::Integer)
```

Return an agent given its ID.

`Base.getproperty`

— Method```
model.prop
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**.

`Agents.seed!`

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

.

`Agents.random_agent`

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

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

— Function`allids(model)`

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

`Agents.GraphSpace`

— Type`GraphSpace(graph::AbstractGraph)`

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.

`Agents.GridSpace`

— Type`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 `i`

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

### Continuous spaces

`Agents.ContinuousSpace`

— Type`ContinuousSpace(extent::NTuple{D, <:Real}; kwargs...)`

Create a `D`

-dimensional `ContinuousSpace`

in range 0 to (but not including) `extent`

. Your agent positions (field `pos`

) must be of type `NTuple{D, <:Real}`

, and it is strongly recommend that agents also have a field `vel::NTuple{D, <:Real}`

to use in conjunction with `move_agent!`

. Use `ContinuousAgent`

for convenience.

`ContinuousSpace`

is a *true* representation of agent dynamics on a continuous medium where agent position, orientation, and speed, are true floats. In addition, strong support is provided for representing spatial properties in a model that contains a `ContinuousSpace`

. Spatial properties (which typically are contained in the model properties) can either be functions of the position vector, `f(pos) = value`

, or `AbstractArrays`

, representing discretizations of spatial data that may not be available in analytic form. In the latter case, the position is automatically mapped into the discretization represented by the array. Use `get_spatial_property`

to access spatial properties in conjuction with `ContinuousSpace`

.

See also Continuous space exclusives on the online docs for more functionality.

**Keywords**

`periodic = true`

: Whether the space is periodic or not. If set to`false`

an error will occur if an agent's position exceeds the boundary.`spacing = min(extent...)/10`

: Configures an internal compartment spacing that is used to accelerate nearest neighbor searches like`nearby_ids`

. All dimensions in`extent`

must be completely divisible by`spacing`

. There is no "best" choice for the value of`spacing`

and if you need optimal performance it's advised to set up a benchmark over a range of choices.`update_vel!`

: 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). If you use`update_vel!`

, the agent type must have a field`vel::NTuple{D, <:Real}`

.

`Agents.OpenStreetMapSpace`

— Type`OpenStreetMapSpace(path::AbstractString; kwargs...)`

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

. The functionality related to Open Street Map spaces is in the submodule `OSM`

.

This space represents the underlying map as a *continuous* entity choosing accuracy over performance by explicitly taking into account that every intersection is connected by a road with a finite length in meters. An example of its usage can be found in Zombie Outbreak. Nevertheless, all functions that target `DiscreteSpace`

s apply here as well, e.g. `positions`

. The discrete part are the underlying road intersections, that are represented by a graph.

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

to use as a `path`

for testing.

If your solution can tolerate routes to and from intersections only without caring for the continuity of the roads in between, 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
id::Int
pos::Tuple{Int,Int,Float64}
route::Vector{Int}
destination::Tuple{Int,Int,Float64}
end
```

Current `pos`

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

.

**Routing**

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

- Assign the value of
`OSM.plan_route`

to the`.route`

field of an Agent. This provides`:shortest`

and`:fastest`

paths (with the option of a`return_trip`

) between intersections or positions. `OSM.random_route!`

, choses a new`destination`

an plans a new path to it; overriding the current route (if any).

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

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

Create and add a new agent to the model using the constructor of the agent type of the model. Optionally provide a position to add the agent to as *first argument*, which must match the space position type.

This function takes care of setting the agent's id *and* position. The extra provided `args...`

and `kwargs...`

are propagated to other fields of the agent constructor (see example below).

`add_agent!([pos,] A::Type, 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!`

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

Add the agent to the `model`

at the agent's own position.

`Agents.nextid`

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

Return a valid `id`

for creating a new agent with it.

`Agents.random_position`

— Function`random_position(model) → pos`

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

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

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

.

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

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

`Agents.get_direction`

— Function`get_direction(from, to, model::ABM)`

Return the direction vector from the position `from`

to position `to`

taking into account periodicity of the space.

### Movement with paths

For `OpenStreetMapSpace`

, and `GridSpace`

s using `Pathfinding.Pathfinder`

, a special movement method is available.

`Agents.move_along_route!`

— Function`move_along_route!(agent, model::ABM{<:OpenStreetMapSpace}, distance::Real)`

Move an agent by `distance`

in meters along its planned route.

`Pathfinding.move_along_route!(agent, model::ABM{<:GridSpace{D}}, pathfinder::AStar{D})`

Move `agent`

for one step along the route toward its target set by `Pathfinding.set_target!`

For pathfinding in models with `GridSpace`

.

If the agent does not have a precalculated path or the path is empty, it remains stationary.

`Pathfinding.move_along_route!(agent, model::ABM{<:ContinuousSpace{D}}, pathfinder::AStar{D}, speed, dt = 1.0)`

Move `agent`

for one step along the route toward its target set by `Pathfinding.set_target!`

at the given `speed`

and timestep `dt`

.

For pathfinding in models with `ContinuousSpace`

If the agent does not have a precalculated path or the path is empty, it remains stationary.

`Agents.is_stationary`

— Function`is_stationary(agent, model)`

Return `true`

if agent has reached the end of its route, or no route has been set for it. Used in setups where using `move_along_route!`

is valid.

## Removing agents

`Agents.kill_agent!`

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

Remove an agent from the model.

`Pathfinding.kill_agent!(agent, model, pathfinder)`

The same as `kill_agent!(agent, model)`

, but also removes the agent's path data from `pathfinder`

.

`Agents.genocide!`

— Function`genocide!(model::ABM)`

Kill all the agents of the model.

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

Kill the agents whose IDs are larger than n.

`genocide!(model::ABM, IDs)`

Kill the agents with the given IDs.

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

Example usage in Wright-Fisher model of evolution.

## Discrete space exclusives

`Agents.positions`

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

`Agents.ids_in_position`

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

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

.

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

`Agents.has_empty_positions`

— Function`has_empty_positions(model::ABM{<:DiscreteSpace})`

Return `true`

if there are any positions in the model without agents.

`Agents.empty_positions`

— Function`empty_positions(model)`

Return a list of positions that currently have no agents on them.

`Agents.random_empty`

— Function`random_empty(model::ABM{<:DiscreteSpace}, cutoff = 0.998)`

Return a random position without any agents, or `nothing`

if no such positions exist. `cutoff`

switches the search algorithm from probabalistic to a filter.

`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, updating the agent's position to the new one.

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...; kwargs...)`

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

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

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.

The keyword `cutoff = 0.998`

is sent to `random_empty`

.

`Base.isempty`

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

Return `true`

if there are no agents in `position`

.

## Continuous space exclusives

`Agents.get_spatial_property`

— Function`get_spatial_property(pos::NTuple{D, Float64}, property::AbstractArray, model::ABM)`

Convert the continuous agent position into an appropriate `index`

of `property`

, which represents some discretization of a spatial field over a `ContinuousSpace`

. Then, return `property[index]`

. To get the `index`

directly, for e.g. mutating the `property`

in-place, use `get_spatial_index`

.

`get_spatial_property(pos::NTuple{D, Float64}, property, model::ABM)`

Literally equivalent with `property(pos, model)`

, useful when `property`

is a function, or a function-like object.

`Agents.get_spatial_index`

— Function`get_spatial_index(pos, property::AbstractArray, model::ABM)`

Convert the continuous agent position into an appropriate `index`

of `property`

, which represents some discretization of a spatial field over a `ContinuousSpace`

.

The dimensionality of `property`

and the continuous space do not have to match. If `property`

has lower dimensionalty than the space (e.g. representing some surface property in 3D space) then the necessary starting dimensions of `pos`

will be used to index.

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

`Agents.nearest_neighbor`

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

.

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

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

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

.

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

## OpenStreetMap space exclusives

`Agents.OSM`

— Module`OSM`

Submodule for functionality related to `OpenStreetMapSpace`

. See the docstring of the space for more info.

`Agents.OSM.latlon`

— Function```
OSM.latlon(pos, model)
OSM.latlon(agent, model)
```

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

`Agents.OSM.intersection`

— Function`intersection(latlon::Tuple{Float64,Float64}, model::ABM{<:OpenStreetMapSpace})`

Return the nearest intersection position to (latitude, longitude). Quicker, but less precise than `OSM.road`

.

`Agents.OSM.road`

— Function`OSM.road(latlon::Tuple{Float64,Float64}, model::ABM{<:OpenStreetMapSpace})`

Return a location on a road nearest to (latitude, longitude). Slower, but more precise than `OSM.intersection`

.

`Agents.OSM.random_road_position`

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

`Agents.OSM.plan_route`

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

`Agents.OSM.random_route!`

— Function`OSM.random_route!(agent, model::ABM{<:OpenStreetMapSpace})`

Plan a new random route for the agent, by selecting a random destination and planning a route from the agent's current position. Overwrite any current route.

`Agents.OSM.road_length`

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

`Agents.OSM.map_coordinates`

— Function`OSM.map_coordinates(agent, model::ABM{OpenStreetMapSpace})`

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

## Local area

`Agents.nearby_ids`

— Function`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), always including ids of the same node as`position`

. For example, for`r=2`

include first and second degree neighbors. If`r=0`

, only ids in the same node as`position`

are returned.`GridSpace`

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

: Standard distance according to the space metric.`OpenStreetMapSpace`

:`r`

is equivalent with distance (in meters) needed to be travelled according to existing roads in order to reach given`position`

.

**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_agents`

— Function`nearby_agents(agent, model::ABM, r = 1; 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`

.

`Agents.nearby_positions`

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

.

`Agents.edistance`

— Function`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
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 Vector{Int64}:
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 Vector{Main.Agent}:
Main.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.

These methods follow the conventions outlined above in A note on iteration.

`Agents.iter_agent_groups`

— Function`iter_agent_groups(order::Int, model::ABM; scheduler = Schedulers.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 `Schedulers.by_id`

scheduler by default, but can be altered with the `scheduler`

keyword.

`Agents.map_agent_groups`

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

`Agents.index_mapped_groups`

— Function```
index_mapped_groups(order::Int, model::ABM; scheduler = Schedulers.by_id)
index_mapped_groups(order::Int, model::ABM, filter::Function; scheduler = Schedulers.by_id)
```

Return an iterable of agent ids in the model, meeting the `filter`

criterea if used.

## Parameter scanning

`Agents.paramscan`

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

s are not expanded in the scan.

The second argument `initialize`

is a function that creates an ABM and returns it. It must 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`

: by default, only the varying parameters (Vector in`parameters`

) will be included in the output`DataFrame`

. If`true`

, constant parameters (non-Vector in`parameteres`

) will also be included.`parallel::Bool = false`

whether`Distributed.pmap`

is invoked to run simulations in parallel. This must be used in conjunction with`@everywhere`

(see Performance Tips).

All other keywords are propagated into `run!`

. Furthermore, `agent_step!, model_step!, n`

are also keywords here, that are given to `run!`

as arguments. Naturally, `agent_step!, model_step!, n`

and at least one of `adata, mdata`

are mandatory. The `adata, mdata`

lists shouldn't contain the parameters that are already in the `parameters`

dictionary to avoid duplication.

**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 = Schedulers.randomly)
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(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
)
adf, _ = paramscan(parameters, initialize; adata, agent_step!, n = 3)
```

## 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)
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)

`run!`

uses the following functions:

`Agents.init_agent_dataframe`

— Function`init_agent_dataframe(model, adata) → 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; 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`

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

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

. `mdata`

can be a `Vector`

or generator `Function`

.

`Agents.collect_model_data!`

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

Same as `collect_agent_data!`

but for model data instead. `properties`

can be a `Vector`

or generator `Function`

.

`Agents.dataname`

— Function`dataname(k) → name`

Return the name of the column of the `i`

-th collected data where `k = adata[i]`

(or `mdata[i]`

). `dataname`

also accepts tuples with aggregate and conditional values.

## Schedulers

`Agents.Schedulers`

— Module`Schedulers`

Submodule containing all predefined schedulers of Agents.jl and the scheduling API. Schedulers have a very simple interface. They 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.

See also Advanced scheduling for making more advanced schedulers.

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 API:

`Agents.Schedulers.fastest`

— Function`Schedulers.fastest`

A scheduler that activates 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.Schedulers.by_id`

— Function`Schedulers.by_id`

A scheduler that activates all agents agents at each step according to their id.

`Agents.Schedulers.randomly`

— Function`Schedulers.randomly`

A scheduler that activates all agents once per step in a random order. Different random ordering is used at each different step.

`Agents.Schedulers.partially`

— Function`Schedulers.partially(p)`

A scheduler that at each step activates only `p`

percentage of randomly chosen agents.

`Agents.Schedulers.by_property`

— Function`Schedulers.by_property(property)`

A scheduler that at each step activates the agents in an order dictated by their `property`

, with agents with greater `property`

acting first. `property`

can be a `Symbol`

, which just dictates which field of the agents to compare, or a function which inputs an agent and outputs a real number.

`Agents.Schedulers.by_type`

— Function`Schedulers.by_type(shuffle_types::Bool, shuffle_agents::Bool)`

A scheduler 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 `Schedulers.fastest`

).

`Schedulers.by_type((C, B, A), shuffle_agents::Bool)`

A scheduler that activates 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)
```

## Ensemble runs and Parallelization

`Agents.ensemblerun!`

— Function`ensemblerun!(models::Vector, agent_step!, model_step!, n; kwargs...)`

Perform an ensemble simulation of `run!`

for all `model ∈ models`

. Each `model`

should be a (different) instance of an `AgentBasedModel`

but probably initialized with a different random seed or different initial agent distribution. All models obey the same rules `agent_step!, model_step!`

and are evolved for `n`

.

Similarly to `run!`

this function will collect data. It will furthermore add one additional column to the dataframe called `:ensemble`

, which has an integer value counting the ensemble member. The function returns `agent_df, model_df, models`

.

The keyword `parallel = false`

, when `true`

, will run the simulations in parallel using Julia's `Distributed.pmap`

(you need to have loaded `Agents`

with `@everywhere`

, see docs online).

All other keywords are propagated to `run!`

as-is.

Example usage in Schelling's segregation model.

If you want to scan parameters and at the same time run multiple simulations at each parameter combination, simply use `seed`

as a parameter, and use that parameter to tune the model's initial random seed and agent distribution.

`ensemblerun!(generator, agent_step!, model_step!, n; kwargs...)`

Generate many `ABM`

s and propagate them into `ensemblerun!(models, ...)`

using the provided `generator`

which is a one-argument function whose input is a seed.

This method has additional keywords `ensemble = 5, seeds = rand(UInt32, ensemble)`

.

### How to use `Distributed`

To use the `parallel=true`

option of `ensemblerun!`

you need to load `Agents`

and define your fundamental types at all processors. How to do this is shown in Ensembles and distributed computing section of Schelling's Segregation Model example. See also the Performance Tips page for parallelization.

## Path-finding

`Agents.Pathfinding`

— Module`Pathfinding`

Submodule containing functionality for path-finding based on the A* algorithm. Currently available for `GridSpace`

and `ContinuousSpace`

. Discretization of `ContinuousSpace`

is taken care of internally.

You can enable path-finding and set its options by creating an instance of a `Pathfinding.AStar`

struct. This must be passed to the relevant pathfinding functions during the simulation. Call `Pathfinding.set_target!`

to set the target destination for an agent. This triggers the algorithm to calculate a path from the agent's current position to the one specified. You can alternatively use `Pathfinding.set_best_target!`

to choose the best target from a list. Once a target has been set, you can move an agent one step along its precalculated path using the `move_along_route!`

function.

Refer to the Maze Solver, Mountain Runners and Rabbit, Fox, Hawk examples using path-finding and see the available functions below as well.

`Agents.Pathfinding.AStar`

— Type`Pathfinding.AStar(space; kwargs...)`

Enables pathfinding for agents in the provided `space`

(which can be a `GridSpace`

or `ContinuousSpace`

) using the A* algorithm. This struct must be passed into any pathfinding functions.

For `ContinuousSpace`

, a walkmap or instance of `PenaltyMap`

must be provided to specify the level of discretisation of the space.

**Keywords**

`diagonal_movement = true`

specifies if movement can be to diagonal neighbors of a tile, or only orthogonal neighbors. Only available for`GridSpace`

`admissibility = 0.0`

allows the algorithm to aprroximate paths to speed up pathfinding. A value of`admissibility`

allows paths with at most`(1+admissibility)`

times the optimal length.`walkmap = trues(size(space))`

specifies the (un)walkable positions of the space. If specified, it should be a`BitArray`

of the same size as the corresponding`GridSpace`

. By default, agents can walk anywhere in the space. An example usage can be found in Maze Solver`cost_metric = DirectDistance{D}()`

is an instance of a cost metric and specifies the metric used to approximate the distance between any two points. See`CostMetric`

. An example usage can be found in Mountain Runners.

`Agents.Pathfinding.set_target!`

— Function`Pathfinding.set_target!(agent, target, pathfinder::AStar{D})`

Calculate and store the shortest path to move the agent from its current position to `target`

(a position e.g. `(1, 5)`

or `(1.3, 5.2)`

) using the provided `pathfinder`

.

Use this method in conjuction with `move_along_route!`

.

`Agents.Pathfinding.set_best_target!`

— Function`Pathfinding.set_best_target!(agent, targets, pathfinder::AStar{D}; kwargs...)`

Calculate, store, and return the best path to move the agent from its current position to a chosen target position taken from `targets`

using `pathfinder`

.

The `condition = :shortest`

keyword retuns the shortest path which is shortest out of the possible target positions. Alternatively, the `:longest`

path may also be requested.

Return the position of the chosen target. Return `nothing`

if none of the supplied targets are reachable.

`Agents.Pathfinding.penaltymap`

— Function`Pathfinding.penaltymap(pathfinder)`

Return the penalty map of a `Pathfinding.AStar`

if the `Pathfinding.PenaltyMap`

metric is in use, `nothing`

otherwise.

It is possible to mutate the map directly, for example `Pathfinding.penaltymap(pathfinder)[15, 40] = 115`

or `Pathfinding.penaltymap(pathfinder) .= rand(50, 50)`

. If this is mutated, a new path needs to be planned using `Pathfinding.set_target!`

.

`Agents.Pathfinding.nearby_walkable`

— Function`Pathfinding.nearby_walkable(position, model::ABM{<:GridSpace{D}}, pathfinder::AStar{D}, r = 1)`

Return an iterator over all `nearby_positions`

within "radius" `r`

of the given `position`

(excluding `position`

), which are walkable as specified by the given `pathfinder`

.

`Agents.Pathfinding.random_walkable`

— Function`Pathfinding.random_walkable(model, pathfinder::AStar{D})`

Return a random position in the given `model`

that is walkable as specified by the given `pathfinder`

.

`Pathfinding.random_walkable(pos, model::ABM{<:ContinuousSpace{D}}, pathfinder::AStar{D}, r = 1.0)`

Return a random position within radius `r`

of `pos`

which is walkable, as specified by `pathfinder`

. Return `pos`

if no such position exists.

### Pathfinding Metrics

`Agents.Pathfinding.DirectDistance`

— Type`Pathfinding.DirectDistance{D}([direction_costs::Vector{Int}]) <: CostMetric{D}`

Distance is approximated as the shortest path between the two points, provided the `walkable`

property of `Pathfinding.AStar`

allows. Optionally provide a `Vector{Int}`

that represents the cost of going from a tile to the neighboring tile on the `i`

dimensional diagonal (default is `10√i`

).

If `diagonal_movement=false`

in `Pathfinding.AStar`

, neighbors in diagonal positions will be excluded. Cost defaults to the first value of the provided vector.

`Agents.Pathfinding.MaxDistance`

— Type`Pathfinding.MaxDistance{D}() <: CostMetric{D}`

Distance between two tiles is approximated as the maximum of absolute difference in coordinates between them.

`Agents.Pathfinding.PenaltyMap`

— Type`Pathfinding.PenaltyMap(pmap::Array{Int,D} [, base_metric::CostMetric]) <: CostMetric{D}`

Distance between two positions is the sum of the shortest distance between them and the absolute difference in penalty.

A penalty map (`pmap`

) is required. For pathfinding in `GridSpace`

, this should be the same dimensions as the space. For pathfinding in `ContinuousSpace`

, the size of this map determines the granularity of the underlying grid, and should agree with the size of the `walkable`

map.

Distance is calculated using `Pathfinding.DirectDistance`

by default, and can be changed by specifying `base_metric`

.

An example usage can be found in Mountain Runners.

Building a custom metric is straightforward, if the provided ones do not suit your purpose. See the Developer Docs for details.

## Save, Load, Checkpoints

There may be scenarios where interacting with data in the form of files is necessary. The following functions provide an interface to save/load data to/from files.

`Agents.AgentsIO.save_checkpoint`

— Function`AgentsIO.save_checkpoint(filename, model::ABM)`

Write the entire `model`

to file specified by `filename`

. The following points should be considered before using this functionality:

- OpenStreetMap data is not saved. The path to the map should be specified when loading the model using the
`map`

keyword of`AgentsIO.load_checkpoint`

. - Functions are not saved, including stepping functions, schedulers, and
`update_vel!`

. The last two can be provided to`AgentsIO.load_checkpoint`

using the appropriate keyword arguments.

`Agents.AgentsIO.load_checkpoint`

— Function`AgentsIO.load_checkpoint(filename; kwargs...)`

Load the model saved to the file specified by `filename`

.

**Keywords**

`scheduler = Schedulers.fastest`

specifies what scheduler should be used for the model.`warn = true`

can be used to disable warnings from type checks on the agent type.

`ContinuousSpace`

specific:

`update_vel!`

specifies a function that should be used to update each agent's velocity before it is moved. Refer to`ContinuousSpace`

for details.

`OpenStreetMapSpace`

specific:

`map`

is a path to the OpenStreetMap to be used for the space. This is a required parameter if the space is`OpenStreetMapSpace`

.`use_cache = false`

,`trim_to_connected_graph = true`

refer to`OpenStreetMapSpace`

`Agents.AgentsIO.populate_from_csv!`

— Function`AgentsIO.populate_from_csv!(model, filename [, agent_type, col_map]; row_number_is_id, kwargs...)`

Populate the given `model`

using CSV data contained in `filename`

. Use `agent_type`

to specify the type of agent to create (In the case of multi-agent models) or a function that returns an agent to add to the model. The CSV row is splatted into the `agent_type`

constructor/function.

`col_map`

is a `Dict{Symbol,Int}`

specifying a mapping of keyword-arguments to row number. If `col_map`

is specified, the specified data is splatted as keyword arguments.

The keyword `row_number_is_id = false`

specifies whether the row number will be passed as the first argument (or as `id`

keyword) to `agent_type`

.

Any other keyword arguments are forwarded to `CSV.Rows`

. If the `types`

keyword is not specified and `agent_type`

is a struct, then the mapping from struct field to type will be used. `Tuple{...}`

fields will be suffixed with `_1`

, `_2`

, ... similarly to `AgentsIO.dump_to_csv`

For example,

```
struct Foo <: AbstractAgent
id::Int
pos::NTuple{2,Int}
foo::Tuple{Int,String}
end
model = ABM(Foo, ...)
AgentsIO.populate_from_csv!(model, "test.csv")
```

Here, `types`

will be inferred to be

```
Dict(
:id => Int,
:pos_1 => Int,
:pos_2 => Int,
:foo_1 => Int,
:foo_2 => String,
)
```

It is not necessary for all these fields to be present as columns in the CSV. Any column names that match will be converted to the appropriate type. There should exist a constructor for `Foo`

taking the appropriate combination of fields as parameters.

If `"test.csv"`

contains the following columns: `pos_1, pos_2, foo_1, foo_2`

, then `model`

can be populated as `AgentsIO.populate_from_csv!(model, "test.csv"; row_number_is_id = true)`

.

`Agents.AgentsIO.dump_to_csv`

— Function`AgentsIO.dump_to_csv(filename, agents [, fields]; kwargs...)`

Dump `agents`

to the CSV file specified by `filename`

. `agents`

is any iterable sequence of types, such as from `allagents`

. `fields`

is an iterable sequence of `Symbol`

s specifying which fields of each agent are dumped. If not explicitly specified, it is automatically inferred using `eltype(agents)`

. All `kwargs...`

are forwarded to `CSV.write`

.

All `Tuple{...}`

fields are flattened to multiple columns suffixed by `_1`

, `_2`

... similarly to `AgentsIO.populate_from_csv!`

For example,

```
struct Foo <: AbstractAgent
id::Int
pos::NTuple{2,Int}
foo::Tuple{Int,String}
end
model = ABM(Foo, ...)
...
AgentsIO.dump_to_csv("test.csv", allagents(model))
```

The resultant `"test.csv"`

file will contain the following columns: `id`

, `pos_1`

, `pos_2`

, `foo_1`

, `foo_2`

.

In case you require custom serialization for model properties, refer to the Developer Docs for details.