Hi everyone,
I am new to agent-based modelling and have recently started using Agents.jl for a research project. Thanks a lot for this great package!
The model should simulate how (for example) coconuts disperse through the pacific ocean with the help of ocean currents.
One of the goals is to track whether / on which islands the coconuts arrive. I thought I could achieve this by adding an island_matrix
to the model properties, and removing an agent when its location is marked as an island.
The problem I have run into here is that – depending on the current’s strength – agents “hop over” islands, leading to the contact with the island never being recorded and the agent remaining in the simulation.
For an illustration, please see this MWE:
using Agents, Random, CairoMakie
@agent struct Cocos(ContinuousAgent{2,Float64})
lifetime::Int
pos_type::Int
end
function initialize(;
n_nuts=14,
lifetime=40,
extent=(150, 150),
seed=1,
)
space2d = ContinuousSpace(extent; spacing=1.0, update_vel!)
rng = Xoshiro(seed)
xdim, ydim = extent
island_matrix = zeros(extent)
island_matrix[:, 100] .= 1
properties = (
current=[(0, 1) for _ in 1:xdim, _ in 1:ydim],
island_matrix=island_matrix,
)
model = StandardABM(Cocos, space2d; properties, rng, agent_step!, model_step!, scheduler=Schedulers.Randomly())
for i in 1:n_nuts
vel = (0, 0)
pos = (10i, ydim / 2)
pos_type = getindex_grid(model.island_matrix, pos)
add_agent!(pos, model, vel, lifetime, pos_type)
end
return model
end
function agent_step!(agent, model)
if expired(agent) || onisland(agent)
remove_agent!(agent, model)
else
# illustrate the effect of a strong current
# in the real model, `dt` will be 1.0 and
# fast movement would result from values in `model.current`
dt = 10.0
move_agent!(agent, model, dt)
end
return nothing
end
function model_step!(model)
# more stuff here in the real model ...
update_agents!(model)
end
function update_vel!(agent, model)
agent.vel = getindex_grid(model.current, agent.pos)
end
function update_agents!(model)
for agent in allagents(model)
agent.lifetime -= 1
maybe_update_pos_type!(agent, model)
end
return nothing
end
function maybe_update_pos_type!(agent, model)
if onisland(agent, model)
agent.pos_type = getindex_grid(model.island_matrix, agent.pos)
end
return nothing
end
onisland(agent::Cocos, model) = getindex_grid(model.island_matrix, agent.pos) > 0
onisland(agent::Cocos) = agent.pos_type > 0
expired(agent::Cocos) = iszero(agent.lifetime)
truncate(x; at=1) = x < at ? 1 : x
coords(v) = truncate.(round.(Int, v, RoundUp))
getindex_grid(A, pos) = A[coords(pos)...]
model = initialize()
const cocos_polygon = Makie.Polygon(Point2f[(-1, -1), (2, 0), (-1, 1)])
function cocos_marker(c::Cocos)
φ = atan(c.vel[2], c.vel[1])
rotate_polygon(cocos_polygon, φ)
end
islandcolor(model)::Matrix{Int} = model.island_matrix .> 0
heatkwargs = (colormap = [:white, :black], colorrange = (0, 1))
plotkwargs = (;
agent_marker = cocos_marker,
agent_color = :orange,
agentplotkwargs = (strokewidth = 1.0, strokecolor = :black),
heatarray = islandcolor,
heatkwargs = heatkwargs,
)
abmvideo(
"floating.mp4", model;
framerate=10, frames=40,
title="Coconuts",
plotkwargs...
)
I’m sure that there are other oddities in my code; for example, it felt a bit strange to index the ocean current
force field having to first convert agent.pos
(a Float64
) to Int
, and then ensuring that both coordinates are > 0. Is this how it’s supposed to be done?
Thank you for your help in advance!