Skip to content

Commit

Permalink
deprecate UnremovableABM (#919)
Browse files Browse the repository at this point in the history
Co-authored-by: George Datseris <[email protected]>
  • Loading branch information
Tortar and Datseris authored Nov 9, 2023
1 parent 668f1c0 commit ba624d0
Show file tree
Hide file tree
Showing 16 changed files with 206 additions and 216 deletions.
3 changes: 2 additions & 1 deletion docs/logo/example_zoo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -512,11 +512,12 @@ function initialize_antworld(;number_ants::Int = 125, dimensions::Tuple = (70, 7
pheremone_ceiling
)

model = UnremovableABM(
model = StandardABM(
Ant,
GridSpace(dimensions, periodic = false);
properties,
rng,
container = Vector,
scheduler = Schedulers.Randomly()
)

Expand Down
16 changes: 7 additions & 9 deletions examples/schelling.jl
Original file line number Diff line number Diff line change
Expand Up @@ -158,17 +158,15 @@ nagents(schelling)
# We can obtain the created and added agent, that got assigned the ID 2, like so
agent = schelling[2]

# ## Using an `UnremovableABM`
# ## Using a `StandardABM` with `container = Vector`

# We know that the number of agents in the model never changes.
# This means that we shouldn't use the default version of ABM that is initialized
# by `ABM` because it allows deletion of agents (at a performance deficit) and we
# don't need that feature here.
# Instead, we should use [`UnremovableABM`](@ref).
# The only change necessary for this to work is to simply change the call to
# `ABM` to a call to `UnremovableABM`.
# don't need that feature here.The only change necessary for this to work is to
# simply add `container = Vector` when building the model.

schelling = UnremovableABM(SchellingAgent, space; agent_step!, properties)
schelling = StandardABM(SchellingAgent, space; agent_step!, properties, container = Vector)

# ## Creating the ABM through a function

Expand All @@ -185,10 +183,10 @@ function initialize(; total_agents = 320, griddims = (20, 20), min_to_be_happy =
space = GridSpaceSingle(griddims, periodic = false)
properties = Dict(:min_to_be_happy => min_to_be_happy)
rng = Random.Xoshiro(seed)
model = UnremovableABM(
model = StandardABM(
SchellingAgent, space;
agent_step!,
properties, rng, scheduler = Schedulers.Randomly()
agent_step!, properties, rng,
container = Vector, scheduler = Schedulers.Randomly()
)
## populate the model with agents, adding equal amount of the two types of agents
## at random positions in the model
Expand Down
2 changes: 1 addition & 1 deletion src/Agents.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import LinearAlgebra
include("core/agents.jl")
include("core/model_abstract.jl")
include("core/model_free_extensions.jl")
include("core/model_single_container.jl")
include("core/model_standard.jl")
include("core/space_interaction_API.jl")
include("core/higher_order_iteration.jl")

Expand Down
10 changes: 8 additions & 2 deletions src/core/model_abstract.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ interface (see below). `ABM` is an alias to `AgentBasedModel`.
## Available concrete implementations
- [`StandardABM`](@ref)
- [`UnremovableABM`](@ref)
## Interface of `AgentBasedModel`
Expand Down Expand Up @@ -86,6 +85,13 @@ Return the properties container stored in the `model`.
"""
abmproperties(model::ABM) = getfield(model, :properties)

"""
abmscheduler(model::ABM)
Return the default scheduler stored in `model`.
"""
abmscheduler(model::ABM) = getfield(model, :scheduler)

"""
abmspace(model::ABM)
Return the space instance stored in the `model`.
Expand Down Expand Up @@ -139,7 +145,7 @@ agenttype(model::ABM) = notimplemented(model)
Return the "container" of agents in the model.
"""
agent_container(model::ABM) = notimplemented(model)
agent_container(model::ABM) = getfield(model, :agents)

"""
nextid(model::ABM) → id
Expand Down
2 changes: 1 addition & 1 deletion src/core/model_free_extensions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export random_agent, random_id, nagents, allagents, allids
Return an agent given its ID.
"""
Base.getindex(m::ABM, id::Int) = agent_container(m)[id]
Base.getindex(m::ABM, id::Integer) = agent_container(m)[id]


"""
Expand Down
147 changes: 59 additions & 88 deletions src/core/model_single_container.jl → src/core/model_standard.jl
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
export SingleContainerABM, StandardABM, UnremovableABM
export StandardABM, UnremovableABM
export abmscheduler
using StaticArrays: SizedVector

ContainerType{A} = Union{AbstractDict{Int,A}, AbstractVector{A}}

# And the two implementations here are just variants with different `C` type.
struct SingleContainerABM{
struct StandardABM{
S<:SpaceType,
A<:AbstractAgent,
C<:ContainerType{A},
Expand All @@ -21,14 +21,12 @@ struct SingleContainerABM{
agents_first::Bool
end

const SCABM = SingleContainerABM
const StandardABM = SingleContainerABM{S,A,Dict{Int,A}} where {S,A}
const UnremovableABM = SingleContainerABM{S,A,Vector{A}} where {S,A}
const DictStandardABM = StandardABM{S,A,Dict{Int,A}} where {S,A}
const VecStandardABM = StandardABM{S,A,Vector{A}} where {S,A}

# Extend mandatory internal API for `AgentBasedModel`
agent_container(model::SingleContainerABM) = getfield(model, :agents)

containertype(::SingleContainerABM{S,A,C}) where {S,A,C} = C
containertype(::StandardABM{S,A,C}) where {S,A,C} = C

"""
union_types(U::Type)
Expand All @@ -38,64 +36,11 @@ Return a tuple of types within a `Union`.
union_types(T::Type) = (T,)
union_types(T::Union) = (union_types(T.a)..., union_types(T.b)...)

"""
SingleContainerABM(AgentType [, space]; properties, kwargs...) → model
A concrete version of [`AgentBasedModel`](@ref) that stores all agents in a
single container. Offers the variants:
- [`StandardABM`](@ref)
- [`UnremovableABM`](@ref)
"""
function SingleContainerABM(
::Type{A},
space::S = nothing;
agent_step!::G = dummystep,
model_step!::K = dummystep,
container::Type = Dict{Int},
scheduler::F = Schedulers.fastest,
properties::P = nothing,
rng::R = Random.default_rng(),
agents_first::Bool = true,
warn = true,
warn_deprecation = true
) where {A<:AbstractAgent,S<:SpaceType,G,K,F,P,R<:AbstractRNG}
if warn_deprecation && agent_step! == dummystep && model_step! == dummystep
@warn "From version 6.0 it is necessary to pass at least one of agent_step! or model_step!
as keywords argument when defining the model. The old version is deprecated. Passing these
functions to methods of the library which required them before version 6.0 is also deprecated
since they can be retrieved from the model instance, in particular this means it is not needed to
pass the stepping functions in step!, run!, offline_run!, ensemblerun!, abmplot, abmplot!, abmexploration
abmvideo and ABMObservable"
end
agent_validator(A, space, warn)
C = construct_agent_container(container, A)
agents = C()
return SingleContainerABM{S,A,C,G,K,F,P,R}(agents, agent_step!, model_step!, space, scheduler,
properties, rng, Ref(0), agents_first)
end

function SingleContainerABM(agent::AbstractAgent, args::Vararg{Any, N}; kwargs...) where {N}
return SingleContainerABM(typeof(agent), args...; kwargs...)
end

construct_agent_container(::Type{<:Dict}, A) = Dict{Int,A}
construct_agent_container(::Type{<:Vector}, A) = Vector{A}
construct_agent_container(container, A) = throw(
"Unrecognised container $container, please specify either Dict or Vector."
)

agenttype(::SingleContainerABM{S,A}) where {S,A} = A


"""
StandardABM <: AgentBasedModel
The most standard concrete implementation of an [`AgentBasedModel`](@ref),
as well as the default version of the generic [`AgentBasedModel`](@ref) constructor.
`StandardABM` stores agents in a dictionary mapping unique `Int` IDs to agents.
See also [`UnremovableABM`](@ref) for better performance in case number of agents can
only increase during the model evolution.
StandardABM(AgentType [, space]; properties, kwargs...) → model
Expand All @@ -118,12 +63,19 @@ first schedules agents by calling the scheduler. Then, it applies the `agent_ste
to all scheduled agents. Then, the `model_step!` function is called
(optionally, the `model_step!` function may be called before activating the agents).
`StandardABM` stores by default agents in a dictionary mapping unique `Int` IDs to agents.
For better performance, in case the number of agents can only increase during the model
evolution, a vector can be used instead, see keyword `container`.
## Keywords
- `agent_step! = dummystep`: the optional stepping function for each agent contained in the
model. For complicated models, it could be more suitable to use only `model_step!` to evolve
the model.
- `model_step! = dummystep`: the optional stepping function for the model.
- `container = Dict`: the type of container the agents are stored at. Use `Vector` if no agents are removed
during the simulation. This allows storing agents more efficiently, yielding faster retrieval and
iteration over agents.
- `properties = nothing`: additional model-level properties that the user may decide upon
and include in the model. `properties` can be an arbitrary container of data,
however it is most typically a `Dict` with `Symbol` keys, or a composite type (`struct`).
Expand All @@ -136,33 +88,54 @@ to all scheduled agents. Then, the `model_step!` function is called
- `warn=true`: some type tests for `AgentType` are done, and by default
warnings are thrown when appropriate.
"""
StandardABM(args...; kwargs...) = SingleContainerABM(args...; kwargs..., container=Dict{Int})
function StandardABM(
::Type{A},
space::S = nothing;
agent_step!::G = dummystep,
model_step!::K = dummystep,
container::Type = Dict{Int},
scheduler::F = Schedulers.fastest,
properties::P = nothing,
rng::R = Random.default_rng(),
agents_first::Bool = true,
warn = true,
warn_deprecation = true
) where {A<:AbstractAgent,S<:SpaceType,G,K,F,P,R<:AbstractRNG}
if warn_deprecation && agent_step! == dummystep && model_step! == dummystep
@warn "From version 6.0 it is necessary to pass at least one of agent_step! or model_step!
as keywords argument when defining the model. The old version is deprecated. Passing these
functions to methods of the library which required them before version 6.0 is also deprecated
since they can be retrieved from the model instance, in particular this means it is not needed to
pass the stepping functions in step!, run!, offline_run!, ensemblerun!, abmplot, abmplot!, abmexploration
abmvideo and ABMObservable"
end
agent_validator(A, space, warn)
C = construct_agent_container(container, A)
agents = C()
return StandardABM{S,A,C,G,K,F,P,R}(agents, agent_step!, model_step!, space, scheduler,
properties, rng, Ref(0), agents_first)
end

"""
UnremovableABM(AgentType [, space]; properties, kwargs...) → model
function StandardABM(agent::AbstractAgent, args::Vararg{Any, N}; kwargs...) where {N}
return StandardABM(typeof(agent), args...; kwargs...)
end

Similar to [`StandardABM`](@ref), but agents cannot be removed, only added.
This allows storing agents more efficiently in a standard Julia `Vector` (as opposed to
the `Dict` used by [`StandardABM`](@ref)), yielding faster retrieval and iteration over agents.
"""
UnremovableABM(args::Vararg{Any, N}; kwargs...) where {N} = SingleContainerABM(args...; kwargs..., container=Vector)
construct_agent_container(::Type{<:Dict}, A) = Dict{Int,A}
construct_agent_container(::Type{<:Vector}, A) = Vector{A}
construct_agent_container(container, A) = throw(
"Unrecognised container $container, please specify either Dict or Vector."
)

agenttype(::StandardABM{S,A}) where {S,A} = A

#######################################################################################
# %% Model accessing api
#######################################################################################

"""
abmscheduler(model::SingleContainerABM)
nextid(model::DictStandardABM) = getfield(model, :maxid)[] + 1
nextid(model::VecStandardABM) = nagents(model) + 1

Return the default scheduler stored in `model`.
"""
abmscheduler(model::SingleContainerABM) = getfield(model, :scheduler)

nextid(model::StandardABM) = getfield(model, :maxid)[] + 1
nextid(model::UnremovableABM) = nagents(model) + 1

function add_agent_to_model!(agent::A, model::StandardABM) where {A<:AbstractAgent}
function add_agent_to_model!(agent::A, model::DictStandardABM) where {A<:AbstractAgent}
if haskey(agent_container(model), agent.id)
error("Can't add agent to model. There is already an agent with id=$(agent.id)")
else
Expand All @@ -175,19 +148,19 @@ function add_agent_to_model!(agent::A, model::StandardABM) where {A<:AbstractAge
return
end

function add_agent_to_model!(agent::A, model::UnremovableABM) where {A<:AbstractAgent}
function add_agent_to_model!(agent::A, model::VecStandardABM) where {A<:AbstractAgent}
agent.id != nagents(model) + 1 && error("Cannot add agent of ID $(agent.id) in a vector ABM of $(nagents(model)) agents. Expected ID == $(nagents(model)+1).")
push!(agent_container(model), agent)
return
end

function remove_agent_from_model!(agent::A, model::StandardABM) where {A<:AbstractAgent}
function remove_agent_from_model!(agent::A, model::DictStandardABM) where {A<:AbstractAgent}
delete!(agent_container(model), agent.id)
return
end

function remove_agent_from_model!(agent::A, model::UnremovableABM) where {A<:AbstractAgent}
error("Cannot remove agents in `UnremovableABM`.")
function remove_agent_from_model!(agent::A, model::VecStandardABM) where {A<:AbstractAgent}
error("Cannot remove agents in a `StandardABM` with a vector container.")
end

random_id(model::StandardABM) = rand(abmrng(model), agent_container(model)).first
Expand Down Expand Up @@ -278,13 +251,11 @@ end
#######################################################################################
# %% Pretty printing
#######################################################################################
modelname(abm::ABM) = modelname(agent_container(abm))
modelname(::Dict) = "StandardABM"
modelname(::Vector) = "UnremovableABM"

function Base.show(io::IO, abm::SingleContainerABM{S,A}) where {S,A}
function Base.show(io::IO, abm::StandardABM{S,A,C}) where {S,A,C}
n = isconcretetype(A) ? nameof(A) : string(A)
s = "$(modelname(abm)) with $(nagents(abm)) agents of type $(n)"
typecontainer = C isa Dict ? Dict : Vector
s = "StandardABM with $(nagents(abm)) agents of type $(n)"
s *= "\n agents container: $(typecontainer)"
if abmspace(abm) === nothing
s *= "\n space: nothing (no spatial structure)"
else
Expand Down
9 changes: 8 additions & 1 deletion src/deprecations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# v6 deprecations

# From before the move to an interface for ABMs and making `ABM` abstract.
AgentBasedModel(args...; kwargs...) = SingleContainerABM(args...; kwargs...)
AgentBasedModel(args...; kwargs...) = StandardABM(args...; kwargs...)

macro agent(new_name, base_type, super_type, extra_fields)
# This macro was generated with the guidance of @rdeits on Discourse:
Expand Down Expand Up @@ -408,3 +408,10 @@ end
# In version 6.2 they have no reason to exist (when we remove deprecations)
agent_step_field(model::ABM) = getfield(model, :agent_step)
model_step_field(model::ABM) = getfield(model, :model_step)

function UnremovableABM(args::Vararg{Any, N}; kwargs...) where {N}
@warn "UnremovableABM is deprecated. Use StandardABM(...; container = Vector, ...) instead."
StandardABM(args...; kwargs..., container=Vector)
end


4 changes: 2 additions & 2 deletions src/models/flocking.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ function flocking(;
spacing = visual_distance / 1.5,
)
space2d = ContinuousSpace(extent; spacing)
model = UnremovableABM(Bird, space2d; agent_step! = flocking_agent_step!,
scheduler = Schedulers.Randomly())
model = StandardABM(Bird, space2d; agent_step! = flocking_agent_step!,
container = Vector, scheduler = Schedulers.Randomly())
for _ in 1:n_birds
vel = Tuple(rand(abmrng(model), 2) * 2 .- 1)
add_agent!(
Expand Down
4 changes: 2 additions & 2 deletions src/models/schelling.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ function schelling(; numagents = 320, griddims = (20, 20), min_to_be_happy = 3)
@assert numagents < prod(griddims)
space = GridSpaceSingle(griddims, periodic = false)
properties = Dict(:min_to_be_happy => min_to_be_happy)
model = UnremovableABM(SchellingAgent, space; properties, agent_step! = schelling_agent_step!,
scheduler = Schedulers.Randomly())
model = StandardABM(SchellingAgent, space; properties, agent_step! = schelling_agent_step!,
container = Vector, scheduler = Schedulers.Randomly())
for n in 1:numagents
add_agent_single!(SchellingAgent, model, false, n < numagents / 2 ? 1 : 2)
end
Expand Down
2 changes: 1 addition & 1 deletion src/submodules/io/jld2_integration.jl
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ JLD2.wconvert(::Type{SerializableAStar{D,P,M,T,C}}, t::Pathfinding.AStar{D,P,M,T
)

function from_serializable(t::SerializableABM{S,A}; kwargs...) where {S,A}
abm = SingleContainerABM(
abm = StandardABM(
A,
from_serializable(t.space; kwargs...),
container = t.agents_container,
Expand Down
Loading

0 comments on commit ba624d0

Please sign in to comment.