plot(1:100, (1:100) .^ 2, seriestype = :scatter, color = :blue)
Structured populations
Fixed definition of structured population. The correct definition is: a population is structured if \(P(Y \mid X) \neq P(Y)\).
Plan
- So far, we have had agents interacting randomly
- We did this by:
- initializing a population,
pop
, using an array comprehension - using
rand(pop)
to sample random agents - using our own function
interact!
to make two agents interact
- initializing a population,
- Today: using Agents.jl to work with structured populations
Positional vs. keyword arguments
- First, though, a technical remark about function arguments
- We’ve seen function calls like this:
- Here,
1:100
and(1:100) .^ 2
are positional argumentsseriestype = :scatter
andcolor = :blue
are keyword arguments
- You can swap the order of the latter but not of the former
- To create keyword arguments in your own function, you separate the list of keyword and positional arguments with a semicolon (
;
):
function my_fun(pos1, pos2; keyword1, keyword2)
...
end
- If you don’t want any positional arguments, you have to write the following!
function my_fun(; keyword1, keyword2)
...
end
- Keyword (but not positional) arguments can have default values:
function my_fun(; firstname = "John", lastname)
println(firstname * " " * lastname)
end
my_fun(firstname = "Jane", lastname = "Doe")
my_fun(lastname = "Doe")
Jane Doe
John Doe
Structured populations
- I define a population to be structured whenever speakers do not interact fully at random
- Formally:
- let \(P(X)\) = probability of sampling agent \(X\) for an interaction
- let \(P(Y \mid X)\) = (conditional) probability of sampling agent \(Y\), given that \(X\) was already sampled
- then population is structured if \(P(Y \mid X) \neq P(Y)\)
- Example: \(P(\text{D. Trump} \mid \text{Henri}) = 0\) even though \(P(\text{D. Trump}) > 0\)
Implementation
- It would be possible for us to write code for structured populations from scratch
- However, this would be more of an exercise in programming than in ABMs…
- We’re better off, here, using code written by other people
- Enter the Agents.jl package, an ecosystem/framework for ABMs in Julia
- You should already have Agents installed. If not, now’s the time to:
using Pkg
Pkg.add("Agents")
Agents.jl basic steps
- Decide on model space (e.g. social network)
- Define agent type(s) (using special
@agent
keyword) - Define rules that evolve the model
- Initialize your model with
AgentBasedModel
- Evolve, visualize and collect data
- This may seem intimidating at first, but is really quite simple!
- Let’s walk through an example: variational learners in space
Grid space
- For this, we will reuse (with some modifications) our code for variational learners
- And assume that individual learners/agents occupy the nodes of a grid, also known as a two-dimensional regular lattice:
- Point: interactions only occur along links in this grid
Grid space: implementation
- To implement this in Julia with Agents.jl:
= (50, 50)
dims = GridSpaceSingle(dims) space
(50, 50)
is a data structure known as a tupleGridSpaceSingle
comes from Agents.jl and defines a grid space in which each node can carry at most one agent (hence,Single
)
Tuples are similar to arrays. However, there are important differences. The most important of them is that arrays are mutable (the values of their elements can be changed after creation), while tuples are never mutable. Compare:
= [10,20]
arr = (10,20)
tup 1] = 30 # arr is now [30,20]
arr[1] = 30 # throws an error tup[
The developers of Agents.jl have decided that the argument of GridSpaceSingle
has to be a tuple (this decision makes sense in this case—as an immutable type, a tuple is more efficient than an array of similar size).
If you are running into errors trying to initialize a GridSpaceSingle
, the problem is probably that you are trying to pass it the wrong type of argument. Compare:
GridSpaceSingle([50, 50]) # trying to pass array; error
GridSpaceSingle(50, 50) # trying to pass two separate numbers; error
GridSpaceSingle((50, 50)) # passing a tuple; works
Agent redefinition
- To make use of the machinery provided by Agents.jl, we replace:
mutable struct VariationalLearner
::Float64
p::Float64
gamma::Float64
P1::Float64
P2end
- with this:
@agent struct VariationalLearner(GridAgent{2})
::Float64
p::Float64
gamma::Float64
P1::Float64
P2end
@agent
is a special “macro” (more on these later) that introduces all agents in Agents.jlGridAgent{2}
instructs Agents.jl that this agent is to be used in a 2-dimensional grid space
Stepping rule
- We next need a function that evolves i.e. steps the model
- This is a function that takes a single agent and the model as arguments
- We can make use of the
interact!
function we have already written:
function VL_step!(agent, model)
= random_nearby_agent(agent, model)
interlocutor interact!(interlocutor, agent)
end
Model initialization
- We now have all the ingredients we need to initialize the ABM:
= StandardABM(VariationalLearner, space;
model = VL_step!) agent_step!
StandardABM with 0 agents of type VariationalLearner
agents container: Dict
space: GridSpaceSingle with size (50, 50), metric=chebyshev, periodic=true
scheduler: fastest
In the above function call, you cannot write agent_step!=VL_step!
(i.e. without spaces around the =
sign). This is because, if you do so, Julia parses this as agent_step != VL_step!
, which is not what we want.
In other words, when using keyword arguments with exclamation points in their names, make it a habit to separate the equals sign with spaces.
- This creates a sort of an “empty” container (it has no agents yet). To add agents, we call:
add_agent_single!(model; p = 0.1, gamma = 0.01,
= 0.4, P2 = 0.1) P1
VariationalLearner(1, (4, 27), 0.1, 0.01, 0.4, 0.1)
- Note: the values of the agent’s internal fields (
p
,gamma
etc.) are specified as keyword arguments!
- We have a space of 50 x 50 = 2,500 nodes
- Let’s add 2,499 more agents:
for i in 1:2499
add_agent_single!(model; p = 0.1, gamma = 0.01,
= 0.4, P2 = 0.1)
P1 end
- Check number of agents:
nagents(model)
2500
Stepping the model
- Stepping the model is now easy:
step!(model)
StandardABM with 2500 agents of type VariationalLearner
agents container: Dict
space: GridSpaceSingle with size (50, 50), metric=chebyshev, periodic=true
scheduler: fastest
- Or, for a desired number of steps:
step!(model, 10)
StandardABM with 2500 agents of type VariationalLearner
agents container: Dict
space: GridSpaceSingle with size (50, 50), metric=chebyshev, periodic=true
scheduler: fastest
Stepping in Agents.jl is controlled by a so-called scheduler. You can decide which scheduler to use when initializing your model; for now, we will stick to the default scheduler.
This is important to know: when the default scheduler steps a model, every agent gets updated. In the case of our model, this means that every agent undergoes exactly one interaction as the “listening” party, i.e. every agent gets to learn from exactly one interaction during one time step.
Plotting the population
- With Agents.jl, we also have access to a number of functions that can be used for purposes of visualization
abmplot(model)
plotsmodel
in its current state. It has two return values:- the first one is the plot itself
- the second one contains metadata which we usually don’t need to care about
- To actually see the plot, you have to call the first return value explicitly:
= abmplot(model)
fig, meta fig
┌ Warning: Found `resolution` in the theme when creating a `Scene`. The `resolution` keyword for `Scene`s and `Figure`s has been deprecated. Use `Figure(; size = ...` or `Scene(; size = ...)` instead, which better reflects that this is a unitless size and not a pixel resolution. The key could also come from `set_theme!` calls or related theming functions.
└ @ Makie ~/.julia/packages/Makie/iRM0c/src/scenes.jl:220
- So all we get is a purple square… what gives?
- The problem is that we haven’t yet instructed
abmplot
how we want our model to be visualized - We could in principle be interested in plotting various kinds of things
- For our model here, it makes sense to plot the value of
p
, i.e. each learner’s internal “grammatical state” - This is done with a function that maps an agent to its
p
- In this case, the function is as simple as:
function getp(a)
return a.p
end
getp (generic function with 1 method)
Julia also allows one-liner function definitions. These are often useful when defining very simple functions, like getp
. The equivalent one-liner definition here is:
getp(a) = a.p
getp (generic function with 1 method)
- We now pass our
getp
function as the value of theagent_color
keyword argument toabmplot
:
= abmplot(model; agent_color = getp)
fig, meta fig
┌ Warning: Found `resolution` in the theme when creating a `Scene`. The `resolution` keyword for `Scene`s and `Figure`s has been deprecated. Use `Figure(; size = ...` or `Scene(; size = ...)` instead, which better reflects that this is a unitless size and not a pixel resolution. The key could also come from `set_theme!` calls or related theming functions.
└ @ Makie ~/.julia/packages/Makie/iRM0c/src/scenes.jl:220
- We can see how the population changes as we evolve the model further (the new
as
keyword argument specifies the size of the dots that represent the agents):
step!(model, 1000)
= abmplot(model; agent_color = getp, as = 12)
fig2, meta fig2
┌ Warning: Found `resolution` in the theme when creating a `Scene`. The `resolution` keyword for `Scene`s and `Figure`s has been deprecated. Use `Figure(; size = ...` or `Scene(; size = ...)` instead, which better reflects that this is a unitless size and not a pixel resolution. The key could also come from `set_theme!` calls or related theming functions.
└ @ Makie ~/.julia/packages/Makie/iRM0c/src/scenes.jl:220
┌ Warning: Keywords `as, am, ac` has been deprecated in favor of
│ `agent_size, agent_marker, agent_color`
└ @ AgentsVisualizations ~/.julia/packages/Agents/8JW8b/ext/AgentsVisualizations/src/abmplot.jl:70
- And further:
step!(model, 1000)
= abmplot(model; agent_color = getp, as = 12)
fig3, meta fig3
┌ Warning: Found `resolution` in the theme when creating a `Scene`. The `resolution` keyword for `Scene`s and `Figure`s has been deprecated. Use `Figure(; size = ...` or `Scene(; size = ...)` instead, which better reflects that this is a unitless size and not a pixel resolution. The key could also come from `set_theme!` calls or related theming functions.
└ @ Makie ~/.julia/packages/Makie/iRM0c/src/scenes.jl:220
- Making an animation/video allows us to visualize the evolution dynamically
- This is achieved with the
abmvideo
function
abmvideo("vid.mp4", model; agent_color = getp, as = 12,
= 100, framerate = 10) frames
A more interesting example
- Above, we initialized the population so that everybody had
p = 0.1
in the beginning - What about the following: at time \(t=0\),
- three people have
p = 1
(uses \(G_1\) all the time), - every other person has
p = 0
(uses \(G_2\) all the time)?
- three people have
- Will we see \(G_1\) spread across the population?
- Let’s try!
- We first reinitialize the model:
= GridSpaceSingle((50, 50))
space2 = StandardABM(VariationalLearner, space2;
model2 = VL_step!)
agent_step!
for i in 1:3
add_agent_single!(model2; p = 1.0, gamma = 0.1,
= 0.4, P2 = 0.1)
P1 end
for i in 4:2500
add_agent_single!(model2; p = 0.0, gamma = 0.1,
= 0.4, P2 = 0.1)
P1 end
Here it is important that we create a new space (I’ve called it space2
). Otherwise Agents.jl would try to add agents to our old space, which is already full!
- Visualize initial state of population:
= abmplot(model2; agent_color = getp, as = 12)
fig, meta fig
┌ Warning: Found `resolution` in the theme when creating a `Scene`. The `resolution` keyword for `Scene`s and `Figure`s has been deprecated. Use `Figure(; size = ...` or `Scene(; size = ...)` instead, which better reflects that this is a unitless size and not a pixel resolution. The key could also come from `set_theme!` calls or related theming functions.
└ @ Makie ~/.julia/packages/Makie/iRM0c/src/scenes.jl:220
- Animate for 1,000 iterations (every agent gets to learn 1,000 times):
abmvideo("interesting.mp4", model2; agent_color = getp,
= 12, frames = 1000, framerate = 20) as
Next time
- Social networks, and how to implement them in Agents.jl
- Wrapping up some technical topics: modules, abstract types and inheritance