Skip to content

Commit

Permalink
fix zombie example (#601)
Browse files Browse the repository at this point in the history
* fix zombie example

* remove deprecations from `run!`

* fix `_run!` to `run!` in ensemble.
  • Loading branch information
Datseris authored Apr 4, 2022
1 parent 7e5c3dc commit 4481014
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 63 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Agents"
uuid = "46ada45e-f475-11e8-01d0-f70cc89e6671"
authors = ["George Datseris", "Tim DuBois", "Aayush Sabharwal", "Ali Vahdati"]
version = "5.1.0"
version = "5.1.1"

[deps]
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
Expand Down
13 changes: 6 additions & 7 deletions docs/src/tutorial.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Tutorial

!!! tip "YouTube video"
* This Tutorial is also available as a YouTube video: https://youtu.be/fgwAfAa4kt0
* This Tutorial is also available as a YouTube video: https://youtu.be/fgwAfAa4kt0


In Agents.jl a central structure maps unique IDs (integers) to agent instances, similar to a dictionary. During the simulation, the model evolves in discrete steps. During one step, the user decides which agents will act, how will they act, how many times, and whether any model-level properties will be adjusted.
Expand Down Expand Up @@ -119,12 +119,12 @@ Running the model and collecting data while the model runs is done with the [`ru
run!
```

The [`run!`](@ref) function has been designed for maximum flexibility: nearly all scenarios of data collection are possible whether you need agent data, model data, aggregating model data, or arbitrary combinations.
The [`run!`](@ref) function has been designed for maximum flexibility: nearly all scenarios of data collection are possible whether you need agent data, model data, aggregated data, or arbitrary combinations.

This means that [`run!`](@ref) has not been designed for maximum performance (or minimum memory allocation). However, we also expose a simple data-collection API (see [Data collection](@ref)), that gives users even more flexibility, allowing them to make their own "data collection loops" arbitrarily calling `step!` and collecting data as, and when, needed.
Nevertheless, we also expose a simple data-collection API (see [Data collection](@ref)), that gives users even more flexibility, allowing them to make their own "data collection loops" arbitrarily calling `step!` and collecting data as, and when, needed.

As your models become more complex, it may not be advantageous to use lots of helper functions in the global scope to assist with data collection.
If this is the case in your model, here's a helpful tip to keep things clean: use a generator function to collect data.
If this is the case in your model, here's a helpful tip to keep things clean: use a generator function to collect data as instructed in the documentation string of [`run!`](@ref). For example:

```julia
function assets(model)
Expand All @@ -147,10 +147,9 @@ Each model created by [`AgentBasedModel`](@ref) provides a random number generat
For performance reasons, one should never use `rand()` without using a pool, thus throughout our examples we use `rand(model.rng)` or `rand(model.rng, 1:10, 100)`, etc.

Another benefit of this approach is deterministic models that can be ran again and yield the same output.
To do this, either always pass a specifically seeded RNG to the model creation, e.g. `MersenneTwister(1234)`, or call `seed!(model, 1234)` (with any number) after creating the model but before actually running the simulation.
To do this, either always pass a specifically seeded RNG to the model creation, e.g. `MersenneTwister(1234)`, or call `seed!(model, 1234)` after creating the model but before actually running the simulation.

Passing `RandomDevice()` will use the system's entropy source (coupled with hardware like [TrueRNG](https://ubld.it/truerng_v3) will invoke a true random source, rather than pseudo-random methods like `MersenneTwister`). Models using this method cannot be repeatable, but avoid potential biases of pseudo-randomness.

## An educative example
A simple, education-oriented example of using the basic Agents.jl API is given in [Schelling's segregation model](@ref), also discussing in detail how to visualize your ABMs.
For a quick reference concerning the main concepts of agent based modelling, and how the Agents.jl examples implement each one, take a look at the [Overview of Examples](@ref) page.
A simple, education-oriented example of using the basic Agents.jl API is given in [Schelling's segregation model](@ref).
7 changes: 4 additions & 3 deletions examples/zombies.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# # Zombie Outbreak
# ```@raw html
# <video width="auto" controls autoplay loop>
# <source src="https://raw.githubusercontent.com/JuliaDynamics/JuliaDynamics/master/videos/agents/zombies.mp4?raw=true" type="video/mp4">
# <source src="../outbreak.mp4" type="video/mp4">
# </video>
# ```
#
Expand Down Expand Up @@ -95,10 +95,11 @@ using InteractiveDynamics
using CairoMakie
CairoMakie.activate!() # hide
ac(agent) = agent.infected ? :green : :black
as(agent) = agent.infected ? 6 : 5
as(agent) = agent.infected ? 10 : 8
model = initialise()

abmvideo("outbreak.mp4", model, agent_step!; framerate = 15, frames = 200, as, ac)
abmvideo("outbreak.mp4", model, agent_step!;
title = "Zombie outbreak", framerate = 15, frames = 200, as, ac)

# ```@raw html
# <video width="auto" controls autoplay loop>
Expand Down
64 changes: 15 additions & 49 deletions src/simulations/collect.jl
Original file line number Diff line number Diff line change
Expand Up @@ -66,32 +66,33 @@ two `DataFrame`s, one for agent-level data and one for model-level data.
* `mdata::Vector` means "model data to collect" and works exactly like `adata`.
For the model, no aggregation is possible (nothing to aggregate over).
* `mdata::Function` use a generator function that accepts `model` as input and
provides a `Vector`. Useful in combination with an [`ensemblerun!`](@ref) call that
Alternatively, `mdata` can also be a function. This is a "generator" function,
that accepts `model` as input and provides a `Vector` that represents `mdata`.
Useful in combination with an [`ensemblerun!`](@ref) call that
requires a generator function.
By default both keywords are `nothing`, i.e. nothing is collected/aggregated.
## Mixed-Models
For mixed-models, the `adata` keyword has some additional options & properties.
An additional column `agent_type` will be placed in the output
dataframe.
For mixed-models, the `adata` keyword has some additional options & properties.
An additional column `agent_type` will be placed in the output
dataframe.
In the case that data is needed for one agent type that does not exist
in a second agent type, `missing` values will be added to the dataframe.
In the case that data is needed for one agent type that does not exist
in a second agent type, `missing` values will be added to the dataframe.
**Warning:** Since this option is inherently type unstable, try to avoid this
in a performance critical situation.
**Warning:** Since this option is inherently type unstable, try to avoid this
in a performance critical situation.
Aggregate functions will fail if `missing` values are not handled explicitly.
If `a1.weight` but `a2` (type: Agent2) has no `weight`, use
`a2(a) = a isa Agent2; adata = [(:weight, sum, a2)]` to filter out the missing results.
Aggregate functions will fail if `missing` values are not handled explicitly.
If `a1.weight` but `a2` (type: Agent2) has no `weight`, use
`a2(a) = a isa Agent2; adata = [(:weight, sum, a2)]` to filter out the missing results.
## Other keywords
* `when=true` : at which steps `s` to perform the data collection and processing.
A lot of flexibility is offered based on the type of `when`. If `when::Vector`,
then data are collect if `s ∈ when`. Otherwise data are collected if `when(model, s)`
A lot of flexibility is offered based on the type of `when`. If `when::AbstractVector`,
then data are collected if `s ∈ when`. Otherwise data are collected if `when(model, s)`
returns `true`. By default data are collected in every step.
* `when_model = when` : same as `when` but for model data.
* `obtainer = identity` : method to transfer collected data to the `DataFrame`.
Expand All @@ -107,41 +108,6 @@ run!(model::ABM, agent_step!, n::Int = 1; kwargs...) =
run!(model::ABM, agent_step!, dummystep, n; kwargs...)

function run!(
model::ABM,
agent_step!,
model_step!,
n;
replicates::Int = 0,
parallel::Bool = false,
kwargs...,
)

r = replicates
if r > 0
@warn("""
Running replicate simulations with `run!` is deprecated. It will be removed in a
future version of Agents.jl. Please use the new `ensemblerun!` function instead.
""")
models = [deepcopy(model) for _ in 1:r]
adf, mdf = ensemblerun!(models, agent_step!, model_step!, n; parallel, kwargs...)
rename!(adf, :ensemble => :replicate)
rename!(mdf, :ensemble => :replicate)
return adf, mdf
else
# TODO: When deprecations are removed, the _run! function has no reason to exist,
# it can be internalized inside `run!`.
return _run!(model, agent_step!, model_step!, n; kwargs...)
end
end

###################################################
# Core data collection loop
###################################################
"""
_run!(model, agent_step!, model_step!, n; kwargs...)
Core function that loops over stepping a model and collecting data at each step.
"""
function _run!(
model,
agent_step!,
model_step!,
Expand Down
6 changes: 3 additions & 3 deletions src/simulations/ensemblerun.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ end

function series_ensemble(models, agent_step!, model_step!, n; kwargs...)
@assert models[1] isa ABM
df_agent, df_model = _run!(models[1], agent_step!, model_step!, n; kwargs...)
df_agent, df_model = run!(models[1], agent_step!, model_step!, n; kwargs...)
add_ensemble_index!(df_agent, 1)
add_ensemble_index!(df_model, 1)

for m in 2:length(models)
df_agentTemp, df_modelTemp =
_run!(models[m], agent_step!, model_step!, n; kwargs...)
run!(models[m], agent_step!, model_step!, n; kwargs...)
add_ensemble_index!(df_agentTemp, m)
add_ensemble_index!(df_modelTemp, m)
append!(df_agent, df_agentTemp)
Expand All @@ -76,7 +76,7 @@ end

function parallel_ensemble(models, agent_step!, model_step!, n; kwargs...)
all_data = pmap(
j -> _run!(models[j], agent_step!, model_step!, n; kwargs...),
j -> run!(models[j], agent_step!, model_step!, n; kwargs...),
1:length(models),
)

Expand Down

0 comments on commit 4481014

Please sign in to comment.