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

Update to DynamicSumTypes 3 #1055

Merged
merged 56 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
d3c4d47
Update to DynamicSumTypes 3: remove @multiagent
Tortar Jul 7, 2024
5081b27
fixes
Tortar Jul 7, 2024
e6266aa
update other benchmark
Tortar Jul 7, 2024
c034393
more
Tortar Jul 7, 2024
d056d2a
Update Project.toml
Tortar Jul 7, 2024
aa0301a
Update tutorial.md
Tortar Jul 7, 2024
73dd697
Update tutorial.jl
Tortar Jul 7, 2024
563a04e
Update tutorial.jl
Tortar Jul 7, 2024
edf06e3
Update tutorial.md
Tortar Jul 7, 2024
60c8964
Update tutorial.jl
Tortar Jul 7, 2024
fe6f8f7
Update tutorial.md
Tortar Jul 7, 2024
9a2417c
Update tutorial.md
Tortar Jul 7, 2024
a1a1bda
Update tutorial.md
Tortar Jul 7, 2024
ac842b9
Update tutorial.jl
Tortar Jul 7, 2024
a31bcc1
Update performance_tips.md
Tortar Jul 7, 2024
df599b4
Update performance_tips.md
Tortar Jul 8, 2024
bf4cf03
Update sum_faster_than_multi.jl
Tortar Jul 8, 2024
b935641
Update sum_faster_than_multi.jl
Tortar Jul 9, 2024
1ecd073
Update sum_faster_than_multi.jl
Tortar Jul 9, 2024
b3399f7
Update sum_faster_than_multi.jl
Tortar Jul 11, 2024
61198a1
Update Project.toml
Tortar Jul 11, 2024
7fb6e69
Update ci.yml
Tortar Jul 11, 2024
373a148
Update ci.yml
Tortar Jul 12, 2024
df01092
Update ci.yml
Tortar Jul 12, 2024
b9c67e5
Update ci.yml
Tortar Jul 12, 2024
324952c
Update ci.yml
Tortar Jul 18, 2024
8f86d2f
Merge branch 'main' into rem
Tortar Jul 19, 2024
196078f
Update agents.jl
Tortar Jul 30, 2024
e1e36ba
Update deprecations.jl
Tortar Jul 30, 2024
58617e1
Update agents.jl
Tortar Jul 30, 2024
569599a
Update deprecations.jl
Tortar Jul 30, 2024
0e9aaea
Update agents.jl
Tortar Jul 30, 2024
98a8919
Update performance_tips.md
Tortar Jul 30, 2024
1c9f62e
Rename sum_faster_than_multi.jl to multiagent_vs_union.jl
Tortar Jul 30, 2024
2d9f1d1
Update performance_tips.md
Tortar Jul 30, 2024
1fb11a8
Update tutorial.jl
Tortar Jul 30, 2024
44bccc0
Update tutorial.md
Tortar Jul 30, 2024
be6d4bd
Update CHANGELOG.md
Tortar Jul 30, 2024
782f0be
improvements
Tortar Jul 30, 2024
6099eb4
fix
Tortar Jul 30, 2024
7419e99
fix
Tortar Jul 30, 2024
499d40f
fix
Tortar Jul 30, 2024
248edec
f
Tortar Jul 30, 2024
24f9401
readd multiagent
Tortar Jul 30, 2024
9a0ac45
Update docs/src/tutorial.jl
Tortar Jul 30, 2024
7e83193
Update tutorial.jl
Tortar Jul 30, 2024
df38eda
Update tutorial.jl
Tortar Jul 30, 2024
0245c6d
Update tutorial.md
Tortar Jul 30, 2024
51e95ff
Add constructor function
Tortar Aug 6, 2024
f17588e
Update CHANGELOG.md
Tortar Aug 6, 2024
0a497df
Update CHANGELOG.md
Tortar Aug 6, 2024
50a2373
address code review
Tortar Aug 6, 2024
1290af0
Merge branch 'rem' of https://github.com/JuliaDynamics/Agents.jl into…
Tortar Aug 6, 2024
3c0f700
a
Tortar Aug 6, 2024
26e56e4
ex
Tortar Aug 6, 2024
4d50ed0
ex2
Tortar Aug 6, 2024
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
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# v6.1

The `@multiagent` macro introduced in Agents.jl v6.0 has been completely overhauled. We found a better methodology to create performant multi-agent types that can re-use existing agent types. Please read the docstring of the new `@multiagent` and consult the updated tutorial in v6.1 for more details.

All `@multiagent`-specific functions that were introduced in v6.0, such as `kindof`, have also been deprecated, see the main tutorial.

For `EventQueueABM`, that was an experimental feature that used to work only with `kindof`, it is also now changed to work with the standard `typeof` of the base Julia language.

# v6 - New Major release!

## Potentially BREAKING changes
Expand All @@ -23,7 +31,7 @@ _We tried to deprecate every major change, resulting in practically no breakage
no performance difference between having 1 agent type at the cost of each agent occupying
more memory that in the `Union` case. In `:opt_memory` each agent is optimized to occupy practically
the same memory as the `Union` case, however this comes at a cost of performance versus having 1 type.
- `@multiagent` kinds support multiple dispatch like syntax with the `@dispatch` macro.
`@multiagent` kinds support multiple dispatch like syntax with the `@dispatch` macro.
- A new experimental model type `EventQueueABM` has been implemented. It operates in continuous time through
the scheduling of events at arbitrary time points, in contrast with the discrete time nature of a `StandardABM`.
- Both the visualization and the model abstract interface have been refactored to improve the user
Expand Down
4 changes: 2 additions & 2 deletions 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", "Adriano Meligrana"]
version = "6.0.17"
version = "6.1.0"

[deps]
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
Expand Down Expand Up @@ -47,7 +47,7 @@ DataFrames = "0.21, 0.22, 1"
DataStructures = "0.18"
Distributed = "1"
Distributions = "0.25"
DynamicSumTypes = "2"
DynamicSumTypes = "3"
Downloads = "1"
GraphMakie = "0.5"
Graphs = "1.4"
Expand Down
1 change: 1 addition & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ ColorTypes = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
DiffEqCallbacks = "459566f4-90b8-5000-8ac3-15dfb0a30def"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
DynamicSumTypes = "5fcdbb90-de43-509e-b9a6-c4d43f29cf26"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
DocumenterTools = "35a29f4d-8980-5a13-9543-d66fff28ecb8"
DrWatson = "634d3b9d-ee7a-5ddf-bec9-22491ea816e1"
Expand Down
6 changes: 1 addition & 5 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,8 @@ AgentEvent

```@docs
@agent
AbstractAgent
@multiagent
Tortar marked this conversation as resolved.
Show resolved Hide resolved
kindof
allkinds
@dispatch
@finalize_dispatch
AbstractAgent
```

### Minimal agent types
Expand Down
2 changes: 1 addition & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ using CairoMakie, Agents
Please see the online [CHANGELOG](https://github.com/JuliaDynamics/Agents.jl/blob/main/CHANGELOG.md) for a full list of changes.
The most noteworthy ones are:

- A new `@multiagent` macro allows to run multi-agent simulations much more efficiently.
- A new macro `@multiagent` allows to run multi-agent simulations more efficiently.
- A new experimental model type `EventQueueABM` has been implemented. It operates in continuous time through the scheduling of events at arbitrary time points. It is a generalization of "Gillespie-like" models.
- `AgentBasedModel` defines an API that can be extended by other models.
- Stronger inheritance capabilities in `@agent`.
Expand Down
24 changes: 18 additions & 6 deletions docs/src/performance_tips.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,11 @@ For an example of how this is done, see the [Forest fire](@ref) model, which is
## [Multiple agent types: `@multiagent` versus `Union` types](@id multi_vs_union)

Due to the way Julia's type system works, and the fact that agents are grouped in a container mapping IDs to agent instances, using a `Union` for different agent types always creates a performance hit because it leads to type instability.
On the other hand, a `Union` of different types allows utilizing Julia's multiple dispatch system.

The [`@multiagent`](@ref) macro does not make multiple types. It only makes one large type and defines convenience "constructors" on top of it, giving the illusion that multiple types exist. Therefore it completely eliminates type instability.
[`@multiagent`](@ref) has two versions. In `:opt_speed` the created agents are optimized such as there is virtually no performance difference between having 1 agent type at the cost of each agent occupying more memory that in the `Union` case.
In `:opt_memory` each agent is optimized to occupy practically the same memory as the `Union` case, however this comes at a cost of performance versus having 1 type.
The [`@multiagent`](@ref) macro enclose all types in a single one making working with it type stable.

In the following script, which you can find in `test/performance/variable_agent_types_simple_dynamics.jl`, we create a basic money-exchange ABM with many different agent types (up to 15), while having the simulation rules the same regardless of how many agent types are there.
We then compare the performance of the three versions for multiple agent types, incrementally employing more agents from 2 to 15.
We then compare the performance of the two versions for multiple agent types, incrementally employing more agents from 2 to 15.
Here are the results of how much time it took to run each version:

```@example performance
Expand All @@ -138,5 +135,20 @@ include(t)
```

We see that Unions of up to three different Agent types do not suffer much.
Hence, if you have less than four agent types in your model, using different types is still a valid option and allows you to utilize multiple dispatch.
Hence, if you have less than four agent types in your model, using different types is still a valid option.
For more agent types however we recommend using the [`@multiagent`](@ref) macro.

Finally, we also have a more realistic benchmark of the two approaches at `test/performance/multiagent_vs_union.jl` where the
result of running the model with the two methodologies are

```@example performance_2
using Agents
x = pathof(Agents)
t = joinpath(dirname(dirname(x)), "test", "performance", "multiagent_vs_union.jl")
include(t)
```

In reality, we benchmarked the models also in Julia>=1.11 and from that version on a `Union` is considerably
more performant. Though, there is still a general 1.5-2x advantage in many cases in favour of [`@multiagent`](@ref),
so we suggest to use [`@multiagent`](@ref) only when the speed of the multi-agent simulation is really critical.

158 changes: 39 additions & 119 deletions docs/src/tutorial.jl
Original file line number Diff line number Diff line change
Expand Up @@ -680,12 +680,11 @@ adf

# In realistic modelling situations it is often the case the the ABM is composed
# of different types of agents. Agents.jl supports two approaches for multi-agent ABMs.
# The first uses the `Union` type, and the second
# uses the [`@multiagent`](@ref) command. `@multiagent` is recommended
# as default, because in many cases it will have performance advantages over the `Union` approach
# without having tangible disadvantages. However, you should read through
# the [comparison of the two approaches](@ref multi_vs_union) to
# be better informed on which one to choose.
# The first uses the `Union` type, and the second uses the [`@multiagent`](@ref)
# command.
# This approach is recommended as default, because in many cases it will have performance advantages
# over the `Union` approach without having tangible disadvantages. However, we strongly recommend you
Tortar marked this conversation as resolved.
Show resolved Hide resolved
# to read through the [comparison of the two approaches](@ref sum_vs_union).

# _Note that using multiple agent types is a possibility entirely orthogonal to
# the type of `AgentBasedModel` or the type of space. Everything we describe here
Expand All @@ -695,7 +694,7 @@ adf

# The simplest way to add more agent types is to make more of them with
# [`@agent`](@ref) and then give a `Union` of agent types as the agent type when
# making the `AgentBasedModel`. This is the most "native Julia" approach.
# making the `AgentBasedModel`.

# For example, let's say that a new type of agent enters
# the simulation; a politician that would "attract" a preferred demographic.
Expand All @@ -708,161 +707,81 @@ end
# and, when making the model we would specify

model = StandardABM(
Union{SchellingAgent, Politician}, # type of agents
Union{Schelling, Politician}, # type of agents
space; # space they live in
)

# Naturally, we would have to define a new agent stepping function that would
# act differently depending on the agent type. This could be done by making
# a function that calls other functions depending on the type, such as

function union_step!(agent, model)
if typeof(agent) <: AgentSchelling
schelling_step!(agent, model)
elseif typeof(agent) <: Politician
politician_step!(agent, model)
end
function agent_step!(agent::Schelling, model)
## stuff.
end

function agent_step!(agent::Politician, model)
## other stuff.
end

# and then passing

model = StandardABM(
Union{SchellingAgent, Politician}, # type of agents
Union{Schelling, Politician}, # type of agents
space; # space they live in
agent_step! = union_step!
agent_step!
)

# This approach also works with the [`@multiagent`](@ref) possibility we discuss below.
# `Union` types however also offer the unique possibility of utilizing fully the Julia's
# [multiple dispatch system](https://docs.julialang.org/en/v1/manual/methods/).
# Hence, we can use the same function name and add dispatch to it, such as:

function dispatch_step!(agent::SchellingAgent, model)
## stuff.
end

function dispatch_step!(agent::Politician, model)
## other stuff.
end

# and give `dispatch_step!` to the `agent_step!` keyword during model creation.

# ## Multiple agent types with `@multiagent`

# [`@multiagent`](@ref) is a macro, and hence not "native Julia" syntax like `Union`,
# however it has been designed to be as similar to [`@agent`](@ref) as possible.
# The syntax to use it is like so:

@multiagent struct MultiSchelling{X}(GridAgent{2})
@subagent struct Civilian # can't re-define existing `Schelling` name
mood::Bool = false
group::Int
end
@subagent struct Governor{X} # can't redefine existing `Politician` name
group::Int
influence::X
end
end

# This macro created three names into scope:

(MultiSchelling, Civilian, Governor)

# however, only one of these names is an actual Julia type:
# By using `@multiagent` it is often possible to improve the
# computational performance of simulations requiring multiple types,
# while almost everything works the same

fieldnames(MultiSchelling)
@multiagent MultiSchelling(Schelling, Politician) <: AbstractAgent

# that contains all fields of all subtypes without duplication, while
# Now you can create instances with

fieldnames(Civilian)
p = constructor(MultiSchelling, Politician)(model; pos = random_position(model), preferred_demographic = 1)

# doesn't have any fields. Instead,
# you should think of `Civilian` and `Governor` as just convenience functions that have been
# defined for you to "behave like" types. That's why we call these **kinds** and not
# **types**.
# agents are then all of type `MultiSchelling`

# E.g., you can initialize
typeof(p)

civ = Civilian(; id = 2, pos = (2, 2), group = 2) # default `mood`
# and hence you can't use only `typeof` to differentiate them. But you can use

# or

gov = Governor(; id = 3 , pos = (2, 2), group = 2, influence = 0.5)

# exactly as if these were types made with [`@agent`](@ref).
# These are all of type `MultiSchelling`

typeof(gov)

# and hence you can't use `typeof` to differentiate them. But you can use

kindof(gov)

# instead.

# Since these kinds are not truly different Julia types, multiple dispatch cannot
# be used to create different agent stepping functions for them.
# The simplest "native Julia" solution here is to create a function where `if`
# clauses decide what to do:

function multi_step!(agent, model)
if kindof(agent) == :Civilian
civilian_step!(agent, model)
elseif kindof(agent) == :Governor
politician_step!(agent, model)
end
end
variantof(p)

function civilian_step!(agent, model)
## stuff.
end

function politician_step!(agent, model)
## other stuff.
end
# instead. Hence, the agent stepping function should become something like

# This however can be made to look much more like multiple dispatch with the
# introduction of another macro, `@dispatch`:
agent_step!(agent, model) = agent_step!(agent, model, variant(agent))
Tortar marked this conversation as resolved.
Show resolved Hide resolved

@dispatch function multi_step!(agent::Civilian, model)
function agent_step!(agent, model, ::Schelling)
## stuff.
end

@dispatch function multi_step!(agent::Politician, model)
function agent_step!(agent, model, ::Politician)
## other stuff.
end

# This essentially reconstructs the version previously described with the `if`
# clauses. In general you can use this macro with anything you would dispatch
# on, but this allows also kinds, unlike normal multiple dispatch, for example
# this would also work:

@dispatch function sub_multi_step!(k::Int, agent::Civilian)
## some more stuff.
end

# After we defined the functions with `@dispatch` or the `if` clauses, we can create the model
# and you need to give `MultiSchelling` as the type of agents in model initialization

model = StandardABM(
MultiSchelling, # the multi-agent supertype is given as the type
MultiSchelling, # the multiagent type is given as the type
space;
agent_step! = multi_step!
agent_step!
)

# ## Adding agents of different types to the model

# Regardless of whether you went down the `Union` or `@multiagent` route,
# the API of Agents.jl has been designed such that there is no difference in subsequent
# usage. To add agents to a model, we use the existing [`add_agent_single!`](@ref)
# command, but now specifying as a first argument the type of agent to add.
# usage.

# For example, in the union case we provide the `Union` type when we create the model,

model = StandardABM(Union{SchellingAgent, Politician}, space)
model = StandardABM(Union{Schelling, Politician}, space)

# we add them by specifying the type

add_agent_single!(SchellingAgent, model; group = 1, mood = true)
add_agent_single!(Schelling, model; group = 1, mood = true)

# or

Expand All @@ -872,17 +791,18 @@ add_agent_single!(Politician, model; preferred_demographic = 1)

collect(allagents(model))

# For the `@multiagent` case, there is really no difference. We have
# For the `@multiagent` case, there is really no difference apart from
# the usage of a custom `constructor` function. We have

model = StandardABM(MultiSchelling, space)

# we add

add_agent_single!(Civilian, model; group = 1)
add_agent_single!(constructor(MultiSchelling, Schelling), model; group = 1)

# or

add_agent_single!(Governor, model; influence = 0.5, group = 1)
add_agent_single!(constructor(MultiSchelling, Politician), model; preferred_demographic = 1)

# and we see

Expand Down
Loading
Loading