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...
endCreate 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
endwill 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
endAgents.GraphAgent — TypeGraphAgentCombine 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 — TypeGridAgent{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 — TypeContinuousAgent{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 — TypeOSMAgentCombine with @agent to create an agent type for OpenStreetMapSpace. It attributes the fields id::Int, pos::Tuple{Int,Int,Float64} to the start of the agent type.
Agent/model retrieval and access
Base.getindex — Methodmodel[id]
getindex(model::ABM, id::Integer)Return an agent given its ID.
Base.getproperty — Methodmodel.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! — Functionseed!(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 — Functionrandom_agent(model) → agentReturn a random agent from the model.
random_agent(model, condition) → agentReturn 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.
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 — TypeGraphSpace(graph::AbstractGraph)Create a GraphSpace instance that is underlined by an arbitrary graph from Graphs.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 Graphs.jl directly in the model parameters, as shown in the Social networks with Graphs.jl integration example.
Agents.GridSpace — TypeGridSpace(d::NTuple{D, Int}; periodic = true, metric = :chebyshev)Create a GridSpace that has size given by the tuple d, having D ≥ 1 dimensions. Optionally decide whether the space will be periodic and what will be the distance metric used, which decides the behavior of e.g. nearby_ids. The position type for this space is NTuple{D, Int}, use GridAgent for convenience. In our examples we typically use Dims{D} instead of NTuple{D, Int} (they are equivalent). Valid positions have indices in the range 1:d[i] for the ith dimension.
:chebyshev metric means that the r-neighborhood of a position are all positions within the hypercube having side length of 2*floor(r) and being centered in the origin position.
:euclidean metric means that the r-neighborhood of a position are all positions whose cartesian indices have Euclidean distance ≤ r from the cartesian index of the given position.
An example using GridSpace is Schelling's segregation model.
Continuous spaces
Agents.ContinuousSpace — TypeContinuousSpace(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. An example using continuous space is the Flocking model.
Keywords
periodic = true: Whether the space is periodic or not. If set tofalsean 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 likenearby_ids. All dimensions inextentmust be completely divisible byspacing. There is no "best" choice for the value ofspacingand 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, seemove_agent!. You can of course change the agents' velocities during the agent interaction, theupdate_vel!functionality targets spatial force fields acting on the agents individually (e.g. some magnetic field). If you useupdate_vel!, the agent type must have a fieldvel::NTuple{D, <:Real}.
Agents.OpenStreetMapSpace — TypeOpenStreetMapSpace(path::AbstractString; kwargs...)Create a space residing on the Open Street Map (OSM) file provided via path. A sample file is provided using OSM.test_map. Additional maps can be downloaded using the functions provided by LightOSM.jl. The functionality related to Open Street Map spaces is in the submodule OSM. Agents.jl also re-exports OSM.download_osm_network. An example usage to download the map of London to "london.json":
OSM.download_osm_network(
:place_name;
place_name = "London",
save_to_file_location = "london.json"
)This space represents the underlying map as a continuous entity choosing accuracy over performance. The map is represented as a graph, consisting of nodes connected by edges. Nodes are not necessarily intersections, and there may be multiple nodes on a road joining two intersections. Agents move along the available roads of the map using routing, see below.
The length of an edge between two nodes is specified in the units of the map's weight_type as listed in the documentation for LightOSM.OSMGraph. The possible weight_types are:
:distance: The distance in kilometers of an edge:time: The time in hours to travel along an edge at the maximum speed allowed on that road:lane_efficiency: Time scaled by number of lanes
The default weight_type used is :distance.
An example of its usage can be found in Zombie Outbreak.
All kwargs are propagated to LightOSM.graph_from_file.
The OSMAgent
The base properties for an agent residing on an OSMSpace are as follows:
mutable struct Agent <: AbstractAgent
id::Int
pos::Tuple{Int,Int,Float64}
endCurrent position tuple is represented as (first intersection index, second intersection index, distance travelled). The distance travelled is in the units of weight_type. This ensures that the map is a continuous kind of space, as an agent can truly be at any possible point on an existing road.
Use OSMAgent for convenience.
Routing
There are two ways to generate a route, depending on the situation.
- Use
plan_route!to plan a route from an agent's current position to a target destination. This also has the option of planning a return trip. plan_random_route!, choses a new random destination and plans a path to it.
Both of these functions override any pre-existing route that may exist for an agent. To actually move along a planned route use move_along_route!.
Adding agents
Agents.add_agent! — Functionadd_agent!(agent::AbstractAgent [, pos], model::ABM) → agentAdd 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...) → newagentCreate 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...) → newagentUse 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 falseAgents.add_agent_pos! — Functionadd_agent_pos!(agent::AbstractAgent, model::ABM) → agentAdd the agent to the model at the agent's own position.
Agents.nextid — Functionnextid(model::ABM) → idReturn a valid id for creating a new agent with it.
Agents.random_position — Functionrandom_position(model) → posReturn a random position in the model's space (always with appropriate Type).
Moving agents
Agents.move_agent! — Functionmove_agent!(agent [, pos], model::ABM) → agentMove 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! — 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 ContinuousSpaces.
The dimensionality of direction must be the same as the space. GridSpace asks for Int, and ContinuousSpace for Float64 vectors, describing the walk distance in each direction. direction = (2, -3) is an example of a valid direction on a GridSpace, which moves the agent to the right 2 positions and down 3 positions. Velocity is ignored for this operation in ContinuousSpace.
Keywords
ifemptywill 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].
Agents.get_direction — Functionget_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/ContinuousSpace using Pathfinding.Pathfinder, a special movement method is available.
Agents.plan_route! — Functionplan_route!(agent, dest, pathfinder::AStar{D})Calculate and store the shortest path to move the agent from its current position to dest (a position e.g. (1, 5) or (1.3, 5.2)) using the provided pathfinder.
Use this method in conjuction with move_along_route!.
plan_route!(agent, dest, model::ABM{<:OpenStreetMapSpace};
return_trip = false, kwargs...)Plan a route from the current position of agent to the location specified in dest, which can be an intersection or a point on a road.
If return_trip = true, a route will be planned from start ⟶ finish ⟶ start. All other keywords are passed to LightOSM.shortest_path.
Returns true if a path to dest exists, and false if it doesn't. Specifying return_trip = true also requires the existence of a return path for a route to be planned.
Agents.plan_best_route! — Functionplan_best_route!(agent, dests, pathfinder::AStar{D}; kwargs...)Calculate, store, and return the best path to move the agent from its current position to a chosen destination taken from dests using pathfinder.
The condition = :shortest keyword retuns the shortest path which is shortest out of the possible destinations. Alternatively, the :longest path may also be requested.
Return the position of the chosen destination. Return nothing if none of the supplied destinations are reachable.
Agents.move_along_route! — Functionmove_along_route!(agent, model::ABM{<:GridSpace{D}}, pathfinder::AStar{D})Move agent for one step along the route toward its target set by plan_route!
For pathfinding in models with GridSpace.
If the agent does not have a precalculated path or the path is empty, it remains stationary.
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 plan_route! 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.
move_along_route!(agent, model::ABM{<:OpenStreetMapSpace}, distance::Real) → remainingMove an agent by distance along its planned route. Units of distance are as specified by the underlying graph's weighttype. If the provided distance is greater than the distance to the end of the route, return the remaining distance. Otherwise, return 0. 0 is also returned if `isstationary(agent, model)`.
Agents.is_stationary — Functionis_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! — FunctionPathfinding.kill_agent!(agent, model, pathfinder)The same as kill_agent!(agent, model), but also removes the agent's path data from pathfinder.
kill_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 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! — 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.
Example usage in Wright-Fisher model of evolution.
Discrete space exclusives
Agents.positions — Functionpositions(model::ABM{<:DiscreteSpace}) → nsReturn an iterator over all positions of a model with a discrete space.
positions(model::ABM{<:DiscreteSpace}, by::Symbol) → nsReturn 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, integer 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).
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}, 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! — Functionadd_agent_single!(agent, model::ABM{<:DiscreteSpace}) → agentAdd 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! — Functionmove_agent_single!(agent, model::ABM{<:DiscreteSpace}) → agentMove 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 — Methodisempty(position, model::ABM{<:DiscreteSpace})Return true if there are no agents in position.
Continuous space exclusives
Agents.get_spatial_property — Functionget_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 — Functionget_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 — 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 radiusrof 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 aschedulerthat doesn't scheduleGrasstypes, i.e.:scheduler(model) = (a.id for a in allagents(model) if !(a isa Grass)).
Example usage in https://juliadynamics.github.io/AgentsExampleZoo.jl/dev/examples/growing_bacteria/.
Agents.nearest_neighbor — Functionnearest_neighbor(agent, model::ABM{<:ContinuousSpace}, r) → nearestReturn 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.
Graph space exclusives
Graphs.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: Graphs.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 — ModuleOSMSubmodule for functionality related to OpenStreetMapSpace. See the docstring of the space for more info.
Agents.OSM.lonlat — FunctionOSM.lonlat(pos, model)
OSM.lonlat(agent, model)Return (longitude, latitude) of current road or intersection position.
Agents.OSM.nearest_node — FunctionOSM.nearest_node(lonlat::Tuple{Float64,Float64}, model::ABM{<:OpenStreetMapSpace})Return the nearest intersection position to (longitude, latitude). Quicker, but less precise than OSM.nearest_road.
Agents.OSM.nearest_road — FunctionOSM.nearest_road(lonlat::Tuple{Float64,Float64}, model::ABM{<:OpenStreetMapSpace})Return a location on a road nearest to (longitude, latitude). Significantly slower, but more precise than OSM.nearest_node.
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_random_route! — FunctionOSM.plan_random_route!(agent, model::ABM{<:OpenStreetMapSpace}; kwargs...) → successPlan a new random route for the agent, by selecting a random destination and planning a route from the agent's current position. Overwrite any existing route.
The keyword limit = 10 specifies the limit on the number of attempts at planning a random route, as no connection may be possible given the random start and end. Return true if a route was successfully planned, false otherwise. All other keywords are passed to plan_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 between two intersections. This takes into account the direction of the road, so OSM.road_length(pos_1, pos_2, model) may not be the same as OSM.road_length(pos_2, pos_1, mode). Units of the returned quantity are as specified by the underlying graph's weight_type. If start and finish are the same or pos[1] and pos[2] are the same, then return 0.
Agents.OSM.same_position — FunctionOSM.same_position(a::Tuple{Int,Int,Float64}, b::Tuple{Int,Int,Float64}, model::ABM{<:OpenStreetMapSpace})Return true if the given positions a and b are (approximately) identical
Agents.OSM.same_road — FunctionOSM.same_road(a::Tuple{Int,Int,Float64}, b::Tuple{Int,Int,Float64})Return true if both points lie on the same road of the graph
Agents.OSM.test_map — FunctionOSM.test_map()Download a small test map of Göttingen as an artifact. Return a path to the downloaded file.
Using this map requires network_type = :none to be passed as a keyword to OSMSpace. The unit of distance used for this map is :time.
LightOSM.download_osm_network — Functiondownload_osm_network(download_method::Symbol;
network_type::Symbol=:drive,
metadata::Bool=false,
download_format::Symbol=:json,
save_to_file_location::Union{String,Nothing}=nothing,
download_kwargs...
)::Union{XMLDocument,Dict{String,Any}}Downloads an OpenStreetMap network by querying with a place name, bounding box, or centroid point.
Arguments
download_method::Symbol: Download method, choose from:place_name,:bboxor:point.network_type::Symbol=:drive: Network type filter, pick from:drive,:drive_service,:walk,:bike,:all,:all_private,:none,:railmetadata::Bool=false: Set true to return metadata.download_format::Symbol=:json: Download format, either:osm,:xmlorjson.save_to_file_location::Union{String,Nothing}=nothing: Specify a file location to save downloaded data to disk.
Required Kwargs for each Download Method
download_method=:place_name
place_name::String: Any place name string used as a search argument to the Nominatim API.
download_method=:bbox
minlat::AbstractFloat: Bottom left bounding box latitude coordinate.minlon::AbstractFloat: Bottom left bounding box longitude coordinate.maxlat::AbstractFloat: Top right bounding box latitude coordinate.maxlon::AbstractFloat: Top right bounding box longitude coordinate.
download_method=:point
point::GeoLocation: Centroid point to draw the bounding box around.radius::Number: Distance (km) from centroid point to each bounding box corner.
download_method=:polygon
polygon::AbstractVector: Vector of longitude-latitude pairs.
Network Types
:drive: Motorways excluding private and service ways.:drive_service: Motorways including private and service ways.:walk: Walkways only.:bike: Cycleways only.:all: All motorways, walkways and cycleways excluding private ways.:all_private: All motorways, walkways and cycleways including private ways.:none: No network filters.:rail: Railways excluding proposed and platform.
Return
Union{XMLDocument,Dict{String,Any}}: OpenStreetMap network data parsed as either XML or Dictionary object depending on the download method.
Nearby Agents
Agents.nearby_ids — Functionnearby_ids(position, model::ABM, r; kwargs...) → idsReturn 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 (thusris always an integer), always including ids of the same node asposition. For example, forr=2include first and second degree neighbors. Ifr=0, only ids in the same node aspositionare returned.GridSpace: Either Chebyshev (also called Moore) or Euclidean distance, in the space of cartesian indices.GridSpacecan 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. This can be useful when different coordinates in the space need to be searched with different ranges, e.g., if the space corresponds to a full building, with the third dimension the floor number. See also the Battle Royale for advanced usage where one dimension is used as a categorical one.ContinuousSpace: Standard distance according to the space metric.OpenStreetMapSpace:ris equivalent with distance (in theweight_typeof the space) needed to be travelled according to existing roads in order to reach givenposition.
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.
:defaultreturns neighbors of a vertex (position). If graph is directed, this is equivalent to:out. For undirected graphs, all options are equivalent to:out.:allreturns both:inand:outneighbors.:inreturns incoming vertex neighbors.:outreturns 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.
In periodic discrete or continuous spaces, when used with a radius larger than half of the entire space, this function may find the same agent(s) more than once. See Issue #566 online for more information.
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 advanced method, see Battle Royale.
Agents.nearby_agents — Functionnearby_agents(agent, model::ABM, r = 1; kwargs...) -> agentReturn 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 — Functionnearby_positions(position, model::ABM, r=1; kwargs...) → positionsReturn 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...) → positionsFor 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.random_nearby_id — Functionrandom_nearby_id(agent, model::ABM, r = 1; kwargs...) → idReturn the id of a random agent near the position of the given agent using an optimized algorithm from Reservoir sampling. Return nothing if no agents are nearby.
The value of the argument r and possible keywords operate identically to nearby_ids.
Agents.random_nearby_agent — Functionrandom_nearby_agent(agent, model::ABM, r = 1; kwargs...) → agentReturn the a random agent near the position of the given agent. Return nothing if no agent is nearby.
The value of the argument r and possible keywords operate identically to nearby_ids.
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 Flocking 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
3You 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.ex-docs.Agent}:
Main.ex-docs.Agent(3, (2, 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 — Functioniter_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 — Functionmap_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 — Functionindex_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 criteria if used.
Parameter scanning
Agents.paramscan — Functionparamscan(parameters, initialize; kwargs...) → adf, mdfPerform a parameter scan of a ABM simulation output by collecting data from all parameter combinations into dataframes (one for agent data, one for model data). The dataframes columns are both the collected data (as in run!) but also the input parameter values used.
parameters is a dictionary with key type Symbol which contains various parameters that will be scanned over (as well as other parameters that remain constant). This function uses DrWatson's dict_list convention. This means that every entry of parameters that is a Vector contains many parameters and thus is scanned. All other entries of parameters that are not Vectors are not expanded in the scan.
The second argument initialize is a function that creates an ABM and returns it. It 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 inparameters) will be included in the outputDataFrame. Iftrue, constant parameters (non-Vector inparameteres) will also be included.parallel::Bool = falsewhetherDistributed.pmapis 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
endand 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 — Functioninit_agent_dataframe(model, adata) → agent_dfInitialize 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_dfInitialize a dataframe to add data later with collect_model_data!. mdata can be a Vector or generator Function.
Agents.collect_model_data! — Functioncollect_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 — Functiondataname(k) → nameReturn 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 — ModuleSchedulersSubmodule 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 — FunctionSchedulers.fastestA 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.ByID — TypeSchedulers.ByID()A non-allocating scheduler that activates all agents agents at each step according to their id.
Agents.Schedulers.Randomly — TypeSchedulers.Randomly()A non-allocating scheduler that activates all agents once per step in a random order. Different random ordering is used at each different step.
Agents.Schedulers.Partially — TypeSchedulers.Partially(p)A non-allocating scheduler that at each step activates only p percentage of randomly chosen agents.
Agents.Schedulers.ByProperty — TypeSchedulers.ByProperty(property)A non-allocating 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.ByType — TypeSchedulers.ByType(shuffle_types::Bool, shuffle_agents::Bool, agent_union)A non-allocating scheduler useful only for mixed agent models using Union types.
- Setting
shuffle_types = truegroups by agent type, but randomizes the type order.
Otherwise returns agents grouped in order of appearance in the Union.
shuffle_agents = truerandomizes the order of agents within each group,falsereturns
the default order of the container (equivalent to Schedulers.fastest).
agent_unionis aUnionof all valid agent types (as passed toABM)
Schedulers.ByType((C, B, A), shuffle_agents::Bool)A non-allocating scheduler that activates agents by type in specified order (since Unions are not order preserving). shuffle_agents = true randomizes the order of agents within each group.
Advanced scheduling
You can use Function-like objects to make your scheduling possible of arbitrary events. For example, imagine that after the n-th step of your simulation you want to fundamentally change the order of agents. To achieve this you can define
mutable struct MyScheduler
n::Int # step number
w::Float64
endand 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
endand 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! — Functionensemblerun!(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 ABMs 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 — ModulePathfindingSubmodule 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 plan_route! to set the 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 plan_best_route! 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 — TypePathfinding.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 = truespecifies if movement can be to diagonal neighbors of a tile, or only orthogonal neighbors. Only available forGridSpaceadmissibility = 0.0allows the algorithm to aprroximate paths to speed up pathfinding. A value ofadmissibilityallows 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 aBitArrayof the same size as the correspondingGridSpace. By default, agents can walk anywhere in the space.cost_metric = DirectDistance{D}()is an instance of a cost metric and specifies the metric used to approximate the distance between any two points.
Utilization of all features of AStar occurs in the Rabbit, Fox, Hawk example.
Agents.Pathfinding.penaltymap — FunctionPathfinding.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 plan_route!.
Agents.Pathfinding.nearby_walkable — FunctionPathfinding.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 — FunctionPathfinding.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 — TypePathfinding.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 — TypePathfinding.MaxDistance{D}() <: CostMetric{D}Distance between two tiles is approximated as the maximum of absolute difference in coordinates between them.
Agents.Pathfinding.PenaltyMap — TypePathfinding.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 — FunctionAgentsIO.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
mapkeyword ofAgentsIO.load_checkpoint. - Functions are not saved, including stepping functions, schedulers, and
update_vel!. The last two can be provided toAgentsIO.load_checkpointusing the appropriate keyword arguments.
Agents.AgentsIO.load_checkpoint — FunctionAgentsIO.load_checkpoint(filename; kwargs...)Load the model saved to the file specified by filename.
Keywords
scheduler = Schedulers.fastestspecifies what scheduler should be used for the model.warn = truecan 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 toContinuousSpacefor details.
OpenStreetMapSpace specific:
mapis a path to the OpenStreetMap to be used for the space. This is a required parameter if the space isOpenStreetMapSpace.use_cache = false,trim_to_connected_graph = truerefer toOpenStreetMapSpace
Agents.AgentsIO.populate_from_csv! — FunctionAgentsIO.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 — FunctionAgentsIO.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 Symbols 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.