Skip to content

Hardcoded types: bug adding a new model type #1218

Description

@omerchor

model_accessing_API.jl has explicit references to StandardABM, EventQueueABM and ReinforcementLearningABM in the Dict/Vec/StructVecABM unions defined at the top of the file. This means that users wanting to implement new types of ABMs, which may still have a Dict/Vector/StructVector as a container, must override nextid and add_agent_to_container!(agent::AbstractAgent, model::ABM), though the implementation is essentially a copy-paste from existing code. Specifically, if add_agent_to_container! is not manually implemented (which isn't required by the documentation, nor does it raise an notimplemented error), adding more than one agent fails if a dictionary is used as the container, because maxid is not updated

   function add_agent_to_container!(agent::AbstractAgent, model::ABM)
    ...
    # Update maxid for DictABM
    if model isa DictABM
        maxid = getfield(model, :maxid)
        if maxid[] < getid(agent)
            maxid[] = getid(agent)
        end
    end

Similarly, in collect.jl, lines 376, 404 and 431 explicitly refer to EventBasedABM, instead of calling discretimeabm(model), which would allow other implementations of continuous time models beyond EventBasedABM.

Minimal Working Example

using Agents
using Random

@agent struct Ticker(NoSpaceAgent) end

struct MinimalABM{A, C <: Union{AbstractDict{Int, A}, AbstractVector{A}}, R <: AbstractRNG} <: AgentBasedModel{Nothing}
    agents::C
    rng::R
    maxid::Base.RefValue{Int}
    time::Base.RefValue{Int}
end

function MinimalABM(::Type{A}; container::Type = Dict) where {A <: AbstractAgent}
    agents = container == Dict ? Dict{Int, A}() : Vector{A}()
    return MinimalABM{A, typeof(agents), typeof(Random.default_rng())}(
        agents, Random.default_rng(), Ref(0), Ref(0)
    )
end

Agents.containertype(::MinimalABM{A, C}) where {A, C} = C
Agents.agenttype(::MinimalABM{A}) where {A} = A
Agents.discretimeabm(::MinimalABM) = true
Agents.extra_actions_after_add!(agent, model::MinimalABM) = nothing
Agents.step!(model::MinimalABM, t::Real) = model

Agents.nextid(model::MinimalABM{A, <:AbstractDict}) where {A} = getfield(model, :maxid)[] + 1
Agents.nextid(model::MinimalABM{A, <:AbstractVector}) where {A} = nagents(model) + 1

function try_add_two_agents(container::Type)
    println("=== container = $container ===")
    model = MinimalABM(Ticker; container)

    a1 = add_agent!(Ticker, model)
    println("first agent id:  ", a1.id)
    println("maxid after first add: ", getfield(model, :maxid)[])

    a2 = add_agent!(Ticker, model)
    println("second agent id: ", a2.id)
    println("-> OK, no id collision")
end

try_add_two_agents(Vector)

try_add_two_agents(Dict)

This works for the Vector but fails for the Dict:

=== container = Vector ===
first agent id:  1
maxid after first add: 0
second agent id: 2
-> OK, no id collision
=== container = Dict ===
first agent id:  1
maxid after first add: 0
ERROR: Can't add agent to container. There is already an agent with id=1

Agents.jl version v7.0.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingquality of lifeQoL enhancements that make user experience smoother

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions