Skip to content

Commit 79e70cf

Browse files
TortarDatseris
andauthored
Use composition for multiagents (#1089)
* Use composition for multiagents * Update tutorial.md * Update event_rock_paper_scissors.jl * Update tutorial.jl * Update tutorial.md * delete tutorial markdown * improve multiagent tutorial * Update tutorial.jl * Update agents.jl * Update rabbit_fox_hawk.jl * Apply suggestions from code review * Update Project.toml --------- Co-authored-by: Datseris <[email protected]>
1 parent 44f775b commit 79e70cf

File tree

6 files changed

+78
-936
lines changed

6 files changed

+78
-936
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ celllistmap.mp4
1515
test/adata.arrow
1616
test/mdata.arrow
1717
*.csv
18-
*.arrow
18+
*.arrow
19+
tutorial.md

docs/src/tutorial.jl

Lines changed: 60 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -680,10 +680,10 @@ adf
680680

681681
# In realistic modelling situations it is often the case the the ABM is composed
682682
# of different types of agents. Agents.jl supports two approaches for multi-agent ABMs.
683-
# The first uses the `Union` type, and the second uses the [`@multiagent`](@ref)
684-
# command.
685-
# This approach is recommended as default, because in many cases it will have performance advantages
686-
# over the `Union` approach without having tangible disadvantages. However, we strongly recommend you
683+
# The first uses the `Union` type from Base Julia, and the second uses the [`@multiagent`](@ref)
684+
# command that we have developed to accelerate handling different types.
685+
# The `@multiagent` approach is recommended as default, because in many cases it will have performance advantages
686+
# over the `Union` approach without having tangible disadvantages. However, we strongly recommend you
687687
# to read through the [comparison of the two approaches](https://juliadynamics.github.io/Agents.jl/stable/performance_tips/#multi_vs_union).
688688

689689
# _Note that using multiple agent types is a possibility entirely orthogonal to
@@ -694,7 +694,7 @@ adf
694694

695695
# The simplest way to add more agent types is to make more of them with
696696
# [`@agent`](@ref) and then give a `Union` of agent types as the agent type when
697-
# making the `AgentBasedModel`.
697+
# making the `AgentBasedModel`.
698698

699699
# For example, let's say that a new type of agent enters
700700
# the simulation; a politician that would "attract" a preferred demographic.
@@ -704,16 +704,10 @@ adf
704704
preferred_demographic::Int
705705
end
706706

707-
# and, when making the model we would specify
708-
709-
model = StandardABM(
710-
Union{Schelling, Politician}, # type of agents
711-
space; # space they live in
712-
)
713-
714-
# Naturally, we would have to define a new agent stepping function that would
715-
# act differently depending on the agent type. This could be done by making
716-
# a function that calls other functions depending on the type, such as
707+
# When defining the agent stepping function, it would (likely)
708+
# act differently depending on the agent type. This could be done by utilizing
709+
# [Julia's Multiple Dispatch](https://docs.julialang.org/en/v1/manual/methods/)
710+
# to add a method to the agent stepping function depending on the type
717711

718712
function agent_step!(agent::Schelling, model)
719713
## stuff.
@@ -723,33 +717,62 @@ function agent_step!(agent::Politician, model)
723717
## other stuff.
724718
end
725719

726-
# and then passing
720+
721+
# When making the model we specify the `Union` type for the agents
722+
723+
model = StandardABM(
724+
Union{Schelling, Politician}, # type of agents
725+
space; # space they live in
726+
)
727727

728728
model = StandardABM(
729729
Union{Schelling, Politician}, # type of agents
730730
space; # space they live in
731731
agent_step!
732732
)
733733

734+
735+
# When adding agents to the moedel example, we can explicitly make agents
736+
# with their constructors and add them. However, it is recommended
737+
# to use the automated [`add_agent!`](@ref) function and provide as a first argument
738+
# the type of agent to add. For example
739+
740+
add_agent_single!(Schelling, model; group = 1, mood = true)
741+
742+
# or
743+
744+
add_agent_single!(Politician, model; preferred_demographic = 1)
745+
746+
model
747+
734748
# ## Multiple agent types with `@multiagent`
735749

736-
# By using `@multiagent` it is often possible to improve the
750+
# By using `@multiagent` it is often possible to improve the
737751
# computational performance of simulations requiring multiple types,
738-
# while almost everything works the same
752+
# while almost everything works the same. First we make a
753+
# multi-agent type from existing agent types
739754

740755
@multiagent MultiSchelling(Schelling, Politician) <: AbstractAgent
741756

742-
# Now you can create instances with
757+
MultiSchelling
758+
759+
# This `MultiSchelling` is not a union type; it is an advanced construct
760+
# that wraps multipe types. When making a multi-agent directly (although it is not recommended,
761+
# use `add_agent!` instead), we can wrap the agent type in the multiagent type like so
762+
763+
p = MultiSchelling(Politician(; id = 1, pos = random_position(model), preferred_demographic = 1))
764+
765+
# or
743766

744-
p = constructor(MultiSchelling, Politician)(model; pos = random_position(model), preferred_demographic = 1)
767+
h = MultiSchelling(Schelliing(; id = 1, pos = random_position(model), mood = true, group = 1))
745768

746-
# agents are then all of type `MultiSchelling`
769+
# As you can tell, both of these are of the same `Type`:
747770

748-
typeof(p)
771+
typeof(p), typeof(h)
749772

750-
# and hence you can't use only `typeof` to differentiate them. But you can use
773+
# and hence you can't use `typeof` to differentiate them. But you can use
751774

752-
variantof(p)
775+
variantof(p), variantof(h)
753776

754777
# instead. Hence, the agent stepping function should become something like
755778

@@ -763,50 +786,38 @@ function agent_step!(agent, model, ::Politician)
763786
## other stuff.
764787
end
765788

766-
# and you need to give `MultiSchelling` as the type of agents in model initialization
789+
# to utilize Julia's dispatch system.
790+
791+
# When constructing the model, we must give the multi-type as the agent type,
792+
# akin to giving the `Union` as the type before
767793

768794
model = StandardABM(
769795
MultiSchelling, # the multiagent type is given as the type
770796
space;
771797
agent_step!
772798
)
773799

774-
# Regardless of whether you went down the `Union` or `@multiagent` route,
775-
# the API of Agents.jl has been designed such that there is no difference in subsequent
776-
# usage.
800+
# Now, when it comes to adding agents to the model, we use the same approach
801+
# as with the `Union` types but we pass a constructor function as a first argument
802+
# to [`add_agent!`](@ref). The "type" here must actually be the constructor
777803

778-
# For example, in the union case we provide the `Union` type when we create the model,
804+
add_agent_single!(constructor(MultiSchelling, Schelling), model; group = 1)
779805

780-
model = StandardABM(Union{Schelling, Politician}, space)
806+
# Thankfully, Julia's function composition `∘` simplifies this, and we can do instead
781807

782-
# we add them by specifying the type
783-
784-
add_agent_single!(Schelling, model; group = 1, mood = true)
808+
add_agent_single!(MultiSchelling Schelling, model; group = 1)
785809

786810
# or
787811

788-
add_agent_single!(Politician, model; preferred_demographic = 1)
812+
add_agent_single!(MultiSchelling Politician, model; preferred_demographic = 1)
789813

790814
# and we see
791815

792816
collect(allagents(model))
793817

794-
# For the `@multiagent` case, there is really no difference apart from
795-
# the usage of a custom `constructor` function. We have
818+
# Because the `∘` is more elegant, we will be using it when using `@multiagent`.
796819

797-
model = StandardABM(MultiSchelling, space)
798-
799-
# we add
800-
801-
add_agent_single!(constructor(MultiSchelling, Schelling), model; group = 1)
802-
803-
# or
804-
805-
add_agent_single!(constructor(MultiSchelling, Politician), model; preferred_demographic = 1)
806-
807-
# and we see
808-
809-
collect(allagents(model))
820+
# ---
810821

811822
# And that's the end of the tutorial!!!
812823
# You can visit other examples to see other types of usage of Agents.jl,

0 commit comments

Comments
 (0)