Skip to content

Commit

Permalink
improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Tortar committed Jul 30, 2024
1 parent be6d4bd commit 782f0be
Show file tree
Hide file tree
Showing 10 changed files with 77 additions and 25 deletions.
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:

- Integration with `DynamicSumTypes.@sumtype` 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
2 changes: 1 addition & 1 deletion docs/src/tutorial.jl
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ 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 (this subsection), and the second uses the [`@sumtype`](@ref)
# The first uses the `Union` type (this subsection), and the second uses the [`@multiagent`](@ref)
# command (next subsection) available in [DynamicSumTypes.jl](https://github.com/JuliaDynamics/DynamicSumTypes.jl).
# 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
Expand Down
2 changes: 1 addition & 1 deletion docs/src/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,7 @@ _**And this concludes the main tutorial!**_

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 (this subsection), and the second uses the [`@sumtype`](@ref)
The first uses the `Union` type (this subsection), and the second uses the [`@multiagent`](@ref)
command (next subsection) available in [DynamicSumTypes.jl](https://github.com/JuliaDynamics/DynamicSumTypes.jl).
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
Expand Down
3 changes: 1 addition & 2 deletions examples/rabbit_fox_hawk.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
# and reproduce. Eating food (grass or rabbits) replenishes `energy` by a fixed amount.
using Agents, Agents.Pathfinding
using Random
using DynamicSumTypes
import ImageMagick
using FileIO: load

Expand All @@ -37,7 +36,7 @@ end
energy::Float64
end

@sumtype Animal(Rabbit, Fox, Hawk) <: AbstractAgent
@multiagent Animal(Rabbit, Fox, Hawk) <: AbstractAgent

# A utility function to find the euclidean norm of a Vector
eunorm(vec) = sum(vec .^ 2)
Expand Down
1 change: 1 addition & 0 deletions src/Agents.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ using Graphs
using DataFrames
using MacroTools
using DynamicSumTypes
export variant, variantof
import ProgressMeter
using Random
using StaticArrays: SVector
Expand Down
54 changes: 54 additions & 0 deletions src/core/agents.jl
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,60 @@ function compute_base_fields(base_type_spec)
return base_fields
end

###########################################################################################
# @multiagent
###########################################################################################

"""
@multiagent YourAgentType(AgentTypesToMerge) [<: OptionalSupertype]
Define multiple agent "subtypes", which are variants of a unique type `YourAgentType`.
This means that all "subtypes" are enclosed in the overarching type. Then, You cannot
distinguish them on the basis of `typeof`, but need to use instead the `variantof`
function. The `allvariants` function for a convenient way to obtain all variants types.
See the [Tutorial](@ref) or the [performance comparison versus `Union` types](@ref multiagent_vs_union)
for why it is often better to use `@multiagent` than making multiple agent types.
## Examples
Let's say you have this definition:
```
@agent struct Wolf
energy::Float64 = 0.5
ground_speed::Float64
const fur_color::Symbol
end
@agent struct Hawk{T}
energy::Float64 = 0.1
ground_speed::Float64
flight_speed::T
end
@multiagent Animal(Wolf, Hawk{Float64})
```
Then you can create `Wolf` and `Hawk` agents like so
```
hawk_1 = Animal'.Hawk(1, (1, 1), 1.0, 2.0, 3)
hawk_2 = Animal'.Hawk(; id = 2, pos = (1, 2), ground_speed = 2.3, flight_speed = 2)
wolf_1 = Animal'.Wolf(3, (2, 2), 2.0, 3.0, :black)
wolf_2 = Animal'.Wolf(; id = 4, pos = (2, 1), ground_speed = 2.0, fur_color = :white)
```
The way to retrieve the variant of the agent is through the function `variantof` e.g.
```
variantof(hawk_1) # Hawk
variantof(wolf_2) # Wolf
```
You can also access the enclosed variant instance with the variant `function`
```
variant(hawk_1) # Hawk(1, (1, 1), 1.0, 2.0, 3.0)
variant(wolf_1) # Wolf(3, (2, 2), 2.0, 3.0, :black)
```
See the [rabbit_fox_hawk](@ref) example to see how to use this macro in a model.
"""
macro multiagent(typedef)
if typedef.head == :struct
@warn "This version of @multiagent is deprecated because the underlying package
Expand Down
2 changes: 1 addition & 1 deletion src/core/model_event_queue.jl
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Here is how to construct an `EventQueueABM`:
Create an instance of an [`EventQueueABM`](@ref).
The model expects agents of type `AgentType(s)` living in the given `space`.
`AgentType(s)` is the result of [`@agent`](@ref) or `@sumtype` or
`AgentType(s)` is the result of [`@agent`](@ref) or `@multiagent` or
a `Union` of agent types.
`space` is a subtype of `AbstractSpace`, see [Space](@ref Space) for all available spaces.
Expand Down
2 changes: 1 addition & 1 deletion src/core/model_standard.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ To construct a `StandardABM` use the syntax:
StandardABM(AgentType(s) [, space]; properties, agent_step!, model_step!, kwargs...)
The model expects agents of type `AgentType(s)` living in the given `space`.
`AgentType(s)` is the result of [`@agent`](@ref) or `@sumtype` or
`AgentType(s)` is the result of [`@agent`](@ref) or `@multiagent` or
a `Union` of agent types.
`space` is a subtype of `AbstractSpace`, see [Space](@ref Space) for all available spaces.
Expand Down
14 changes: 6 additions & 8 deletions test/performance/multiagent_vs_union.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This file compares approaching multi-agent models in two ways:
# 1) using different Types to represent different agents. This leads to type
# instabilities.
# 2) using @sumtype to enclose all types in a single one. This removes type
# 2) using @multiagent to enclose all types in a single one. This removes type
# instabilities.

# The result is that (2) is much faster.
Expand Down Expand Up @@ -93,8 +93,6 @@ end

################### DEFINITION 2 ###############

using DynamicSumTypes

agent_step!(agent, model2) = agent_step!(agent, model2, variant(agent))

agent_step!(agent, model2, ::GridAgentOne) = randomwalk!(agent, model2)
Expand Down Expand Up @@ -123,7 +121,7 @@ function agent_step!(agent, model2, ::GridAgentSix)
agent.eight += sum(rand(abmrng(model2), (0, 1)) for a in nearby_agents(agent, model2))
end

@sumtype GridAgentAll(
@multiagent GridAgentAll(
GridAgentOne, GridAgentTwo, GridAgentThree, GridAgentFour, GridAgentFive, GridAgentSix
) <: AbstractAgent

Expand Down Expand Up @@ -157,16 +155,16 @@ t2 = @belapsed step!($model2, 50)

# Results:
# multiple types: 1.849 s (34267900 allocations: 1.96 GiB)
# @sumtype: 545.119 ms (22952850 allocations: 965.93 MiB)
# @multiagent: 545.119 ms (22952850 allocations: 965.93 MiB)

m1 = Base.summarysize(model1)
m2 = Base.summarysize(model2)

# Results:
# multiple types: 543.496 KiB
# @sumtype: 546.360 KiB
# @multiagent: 546.360 KiB

println("Time to step the model with multiple types: $(t1) s")
println("Time to step the model with @sumtype: $(t2) s")
println("Time to step the model with @multiagent: $(t2) s")
println("Memory occupied by the model with multiple types: $(m1/1000) Kib")
println("Memory occupied by the model with @sumtype: $(m2/1000) Kib")
println("Memory occupied by the model with @multiagent: $(m2/1000) Kib")
20 changes: 10 additions & 10 deletions test/performance/variable_agent_types_simple_dynamics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# are split in a varying number of agents. It shows how much of a
# performance hit is to have many different agent types.

using Agents, DynamicSumTypes, Random, BenchmarkTools
using Agents, Random, BenchmarkTools

@agent struct Agent1(GridAgent{2})
money::Int
Expand Down Expand Up @@ -68,13 +68,13 @@ end
money::Int
end

@sumtype AgentAll2(Agent1, Agent2) <: AbstractAgent
@sumtype AgentAll3(Agent1, Agent2, Agent3) <: AbstractAgent
@sumtype AgentAll4(Agent1, Agent2, Agent3, Agent4) <: AbstractAgent
@sumtype AgentAll5(Agent1, Agent2, Agent3, Agent4, Agent5) <: AbstractAgent
@sumtype AgentAll10(Agent1, Agent2, Agent3, Agent4, Agent5, Agent6,
@multiagent AgentAll2(Agent1, Agent2) <: AbstractAgent
@multiagent AgentAll3(Agent1, Agent2, Agent3) <: AbstractAgent
@multiagent AgentAll4(Agent1, Agent2, Agent3, Agent4) <: AbstractAgent
@multiagent AgentAll5(Agent1, Agent2, Agent3, Agent4, Agent5) <: AbstractAgent
@multiagent AgentAll10(Agent1, Agent2, Agent3, Agent4, Agent5, Agent6,
Agent7, Agent8, Agent9, Agent10) <: AbstractAgent
@sumtype AgentAll15(Agent1, Agent2, Agent3, Agent4, Agent5, Agent6,
@multiagent AgentAll15(Agent1, Agent2, Agent3, Agent4, Agent5, Agent6,
Agent7, Agent8, Agent9, Agent10, Agent11, Agent12, Agent13, Agent14, Agent15) <: AbstractAgent

function initialize_model_1(;n_agents=600,dims=(5,5))
Expand Down Expand Up @@ -184,15 +184,15 @@ end
println("relative time of model with 1 type: 1.0")
for (n, t1, t2) in zip(n_types, times_n, times_multi_s)
println("relative time of model with $n types: $t1")
println("relative time of model with $n @sumtype: $t2")
println("relative time of model with $n @multiagent: $t2")
end

using CairoMakie
fig, ax = CairoMakie.scatterlines(n_types, times_n; label = "Union");
scatterlines!(ax, n_types, times_multi_s; label = "@sumtype")
scatterlines!(ax, n_types, times_multi_s; label = "@multiagent")
ax.xlabel = "# types"
ax.ylabel = "time relative to 1 type"
ax.title = "Union types vs @sumtype"
ax.title = "Union types vs @multiagent"
axislegend(ax; position = :lt)
ax.yticks = 0:1:ceil(Int, maximum(times_n))
ax.xticks = [2, 3, 4, 5, 10, 15]
Expand Down

0 comments on commit 782f0be

Please sign in to comment.