Modelling collisions of agents with the environment in discrete time

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! :slight_smile:

1 Like