Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement abmtime for StandardABM #942

Merged
merged 7 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ _We tried to deprecate every major change, resulting in practically no breakage
- Allows us to develop new types of models that may have rules that are defined differently, without being based on e.g., two particular functions.
- Allows us to develop (in the future) a new model type that is optimized for multi-agent simulations.
- The `@agent` macro has been rewritten to support fields with default and const values. It has a new usage syntax now that parallelizes more Julia's native `struct` declaration. The old macro version still works but it's deprecated. Since now the macro supports these features, using `@agent` is the only supported way to create agent types for Agents.jl.
- The `UnremovableABM` type is deprecated, instead `container = Vector` should be passed when creating the model.
- The `step` argument is not needed anymore when using `collect_model_data!, collect_agent_data!` since the time of the
model is used automatically.
- Manually setting or altering the ids of agents is no longer allowed. The agent id is now considered a read-only field, and is set internally by Agents.jl to enable hidden optimizations in the future. As a consequence, `add_agent!(agent::AbstractAgent, pos::ValidPos, model::ABM)` and `add_agent!(agent::AbstractAgent, model::ABM)` have been deprecated. See issue #861 for more. Due to this, the `nextid` function is no longer public API.
- Agent types in `ContinuousSpace` now use `SVector` for their `pos` and `vel` fields rather than `NTuple`. `NTuple` usage in `ContinuousSpace` is officially deprecated, but backward compatibility is *mostly* maintained. Known breakages include the comparison of agent position and/or velocity with user-defined tuples, e.g., doing `agent.pos == (0.5, 0.5)`. This will always be `false` in v6 as `agent.pos` is an `SVector`. The rest of the functionality should all work without problems, such as moving agents to tuple-based positions etc.

Expand All @@ -18,6 +21,7 @@ _We tried to deprecate every major change, resulting in practically no breakage
- Grid and continuous spaces support boundaries with mixed periodicity, specified by tuples with a `Bool` value for each dimension, e.g. `GridSpace((5,5); periodic=(true,false))` is periodic along the first dimension but not along the second.
- Two new functions `random_id_in_position` and `random_agent_in_position` can be used to select a random id/agent in a position in discrete spaces (even with filtering).
- A new function `swap_agents` can be used to swap an agents couple in a discrete space.
- The model time/step is tracked automatically, accessible through `abmtime(model)`.

## Performance Improvements

Expand Down
12 changes: 7 additions & 5 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ abmproperties
abmrng
abmscheduler
abmspace
abmtime
```

## Available spaces
Expand Down Expand Up @@ -227,15 +228,16 @@ 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)
t = getfield(model, :time)
t0, s = t[], 0
while until(t[], t0, n, model)
if should_we_collect(s, model, when)
collect_agent_data!(df_agent, model, adata, s)
collect_agent_data!(df_agent, model, adata)
end
if should_we_collect(s, model, when_model)
collect_model_data!(df_model, model, mdata, s)
collect_model_data!(df_model, model, mdata)
end
step!(model, agent_step!, model_step!, 1)
step!(model, 1)
s += 1
end
return df_agent, df_model
Expand Down
4 changes: 0 additions & 4 deletions docs/src/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,6 @@ end

For defining your own schedulers, see [Schedulers](@ref).

!!! note "Current step number"
Notice that the current step number is not explicitly given to the model stepping function, nor is contained in the model type, because this is useful only for a subset of ABMs.
If you need the step information, implement this by adding a counting parameter into the model `properties`, and incrementing it by 1 each time the model stepping function is called.

## 4. The model

The ABM is an instance of a subtype of [`AgentBasedModel`](@ref), most typically simply a [`StandardABM`](@ref).
Expand Down
10 changes: 5 additions & 5 deletions ext/AgentsVisualizations/src/convenience.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ function init_abm_data_plots!(fig, abmobs, adata, mdata, alabels, mlabels, plotk
end
on(resetclick) do clicks
for ax in axs
vlines!(ax, [abmobs.s.val], color = "#c41818")
vlines!(ax, [abmtime(abmobs.model[])], color = "#c41818")
end
end
return nothing
Expand All @@ -83,11 +83,11 @@ function Agents.abmvideo(file, model;
recordkwargs = (compression = 20,), kwargs...
)
# add some title stuff
s = Observable(0) # counter of current step
abmtime_obs = Observable(abmtime(model))
if title ≠ "" && showstep
t = lift(x -> title*", step = "*string(x), s)
t = lift(x -> title*", step = "*string(x), abmtime_obs)
elseif showstep
t = lift(x -> "step = "*string(x), s)
t = lift(x -> "step = "*string(x), abmtime_obs)
else
t = title
end
Expand All @@ -104,7 +104,7 @@ function Agents.abmvideo(file, model;
for j in 1:frames-1
recordframe!(io)
Agents.step!(abmobs, spf)
s[] += spf; s[] = s[]
abmtime_obs[] = abmtime(model)
end
recordframe!(io)
end
Expand Down
6 changes: 2 additions & 4 deletions ext/AgentsVisualizations/src/deprecations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ function Agents.abmvideo(file, model, agent_step!, model_step! = Agents.dummyste
@warn "Passing agent_step! and model_step! to abmvideo is deprecated.
These functions should be already contained inside the model instance."
# add some title stuff
s = Observable(0) # counter of current step
if title ≠ "" && showstep
t = lift(x -> title*", step = "*string(x), s)
t = lift(x -> title*", step = "*string(x), abmtime(model))
elseif showstep
t = lift(x -> "step = "*string(x), s)
t = lift(x -> "step = "*string(x), abmtime(model))
else
t = title
end
Expand All @@ -25,7 +24,6 @@ function Agents.abmvideo(file, model, agent_step!, model_step! = Agents.dummyste
for j in 1:frames-1
recordframe!(io)
Agents.step!(abmobs, spf)
s[] += spf; s[] = s[]
end
recordframe!(io)
end
Expand Down
17 changes: 7 additions & 10 deletions ext/AgentsVisualizations/src/interaction.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function add_controls!(fig, abmobs, spu)
getfield.(Ref(abmobs), (:model, :agent_step!, :model_step!, :adata, :mdata, :adf, :mdf, :when))

init_dataframes!(model[], adata, mdata, adf, mdf)
collect_data!(model[], when, adata, mdata, adf, mdf, abmobs.s[])
collect_data!(model[], when, adata, mdata, adf, mdf)

# Create new layout for control buttons
controllayout = fig[end+1,:][1,1] = GridLayout(tellheight = true)
Expand All @@ -40,7 +40,7 @@ function add_controls!(fig, abmobs, spu)
step = Button(fig, label = "step\nmodel")
on(step.clicks) do c
Agents.step!(abmobs, speed[])
collect_data!(model[], when[], adata, mdata, adf, mdf, abmobs.s[])
collect_data!(model[], when[], adata, mdata, adf, mdf)
end
# Run button
run = Button(fig, label = "run\nmodel")
Expand All @@ -62,15 +62,12 @@ function add_controls!(fig, abmobs, spu)
model0 = deepcopy(model[]) # backup initial model state
on(reset.clicks) do c
model[] = deepcopy(model0)
s = 0 # reset step counter
Agents.step!(model[], agent_step!, model_step!, s; warn_deprecation = false)
end
# Clear button
clear = Button(fig, label = "clear\ndata")
on(clear.clicks) do c
abmobs.s[] = 0
init_dataframes!(model[], adata, mdata, adf, mdf)
collect_data!(model[], when, adata, mdata, adf, mdf, abmobs.s[])
collect_data!(model[], when, adata, mdata, adf, mdf)
end
# Layout buttons
controllayout[2, :] = Makie.hbox!(step, run, reset, clear; tellwidth = false)
Expand All @@ -89,14 +86,14 @@ function init_dataframes!(model, adata, mdata, adf, mdf)
return nothing
end

function collect_data!(model, when, adata, mdata, adf, mdf, s)
if Agents.should_we_collect(s, model, when)
function collect_data!(model, when, adata, mdata, adf, mdf)
if Agents.should_we_collect(abmtime(model), model, when)
if !isnothing(adata)
Agents.collect_agent_data!(adf[], model, adata, s)
Agents.collect_agent_data!(adf[], model, adata)
adf[] = adf[] # trigger Observable
end
if !isnothing(mdata)
Agents.collect_model_data!(mdf[], model, mdata, s)
Agents.collect_model_data!(mdf[], model, mdata)
mdf[] = mdf[] # trigger Observable
end
end
Expand Down
13 changes: 8 additions & 5 deletions ext/AgentsVisualizations/src/model_observable.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,19 @@ end

function Agents.step!(abmobs::ABMObservable, n; kwargs...)
model, adf, mdf = abmobs.model, abmobs.adf, abmobs.mdf
Agents.step!(model[], abmobs.agent_step!, abmobs.model_step!, n; warn_deprecation = false, kwargs...)
if Agents.agent_step_field(model[]) != Agents.dummystep || Agents.model_step_field(model[]) != Agents.dummystep
Agents.step!(model[], n; kwargs...)
else
Agents.step!(model[], abmobs.agent_step!, abmobs.model_step!, n; warn_deprecation = false, kwargs...)
end
notify(model)
abmobs.s[] = abmobs.s[] + n # increment step counter
if Agents.should_we_collect(abmobs.s, model[], abmobs.when)
if Agents.should_we_collect(abmtime(model[]), model[], abmobs.when)
if !isnothing(abmobs.adata)
Agents.collect_agent_data!(adf[], model[], abmobs.adata, abmobs.s[])
Agents.collect_agent_data!(adf[], model[], abmobs.adata)
notify(adf)
end
if !isnothing(abmobs.mdata)
Agents.collect_model_data!(mdf[], model[], abmobs.mdata, abmobs.s[])
Agents.collect_model_data!(mdf[], model[], abmobs.mdata)
notify(mdf)
end
end
Expand Down
22 changes: 21 additions & 1 deletion src/core/model_abstract.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# All methods, whose defaults won't apply, must be extended
# during the definition of a new ABM type.
export AgentBasedModel, ABM
export abmrng, abmscheduler, abmspace, abmproperties
export abmrng, abmscheduler, abmspace, abmtime, abmproperties

###########################################################################################
# %% Fundamental type definitions
Expand Down Expand Up @@ -98,6 +98,13 @@ Return the space instance stored in the `model`.
"""
abmspace(model::ABM) = getfield(model, :space)

"""
abmtime(model::ABM)
Return the current time of the `model`.
All models are initialized at time 0.
"""
abmtime(model::ABM) = getfield(model, :time)[]

"""
model.prop
getproperty(model::ABM, :prop)
Expand Down Expand Up @@ -171,3 +178,16 @@ remove_agent_from_model!(agent, model) = notimplemented(model)
function Base.setindex!(m::ABM, args...; kwargs...)
error("`setindex!` or `model[id] = agent` are invalid. Use `add_agent!` instead.")
end

"""
dummystep(model)

Used instead of `model_step!` in [`step!`](@ref) if no function is useful to be defined.

dummystep(agent, model)

Used instead of `agent_step!` in [`step!`](@ref) if no function is useful to be defined.
"""
dummystep(model) = nothing
dummystep(agent, model) = nothing

3 changes: 2 additions & 1 deletion src/core/model_standard.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct StandardABM{
properties::P
rng::R
maxid::Base.RefValue{Int64}
time::Base.RefValue{Int64}
agents_first::Bool
end

Expand Down Expand Up @@ -113,7 +114,7 @@ function StandardABM(
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)
properties, rng, Ref(0), Ref(0), agents_first)
end

function StandardABM(agent::AbstractAgent, args::Vararg{Any, N}; kwargs...) where {N}
Expand Down
Loading