move some logic into their own funcs(move,eat,reproduce)
parent
0165c839cd
commit
f0bfe3cb87
283
predator_prey.jl
283
predator_prey.jl
|
@ -46,17 +46,63 @@
|
||||||
# example, we could have only one type and one additional filed to separate them.
|
# example, we could have only one type and one additional filed to separate them.
|
||||||
# Nevertheless, for the sake of example, we will use two different types.)
|
# Nevertheless, for the sake of example, we will use two different types.)
|
||||||
using Agents, Random
|
using Agents, Random
|
||||||
|
using CairoMakie
|
||||||
@agent struct Sheep(GridAgent{2})
|
@agent struct Sheep(GridAgent{2})
|
||||||
energy::Float64
|
energy::Float64
|
||||||
reproduction_prob::Float64
|
reproduction_prob::Float64
|
||||||
Δenergy::Float64
|
Δenergy::Float64
|
||||||
|
#perception::Int32
|
||||||
|
#speed::Float64
|
||||||
|
#endurance::Float64
|
||||||
|
end
|
||||||
|
function move!(sheep::Sheep,model)
|
||||||
|
randomwalk!(sheep, model)
|
||||||
|
sheep.energy -= 1
|
||||||
|
end
|
||||||
|
function eat!(sheep::Sheep, model)
|
||||||
|
if model.fully_grown[sheep.pos...]
|
||||||
|
sheep.energy += sheep.Δenergy
|
||||||
|
model.fully_grown[sheep.pos...] = false
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
function reproduce!(sheep::Sheep, model)
|
||||||
|
if rand(abmrng(model)) ≤ sheep.reproduction_prob
|
||||||
|
sheep.energy /= 2
|
||||||
|
replicate!(sheep, model)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@agent struct Wolf(GridAgent{2})
|
@agent struct Wolf(GridAgent{2})
|
||||||
energy::Float64
|
energy::Float64
|
||||||
reproduction_prob::Float64
|
reproduction_prob::Float64
|
||||||
Δenergy::Float64
|
Δenergy::Float64
|
||||||
|
#perception::Int32
|
||||||
|
#speed::Float64
|
||||||
|
#endurance::Float64
|
||||||
|
end
|
||||||
|
function move!(wolf::Wolf,model)
|
||||||
|
randomwalk!(wolf, model; ifempty=false)
|
||||||
|
wolf.energy -= 1
|
||||||
|
end
|
||||||
|
function eat!(wolf::Wolf, model)
|
||||||
|
dinner = first_sheep_in_position(wolf.pos, model)
|
||||||
|
if !isnothing(dinner)
|
||||||
|
remove_agent!(dinner, model)
|
||||||
|
wolf.energy += wolf.Δenergy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function reproduce!(wolf::Wolf, model)
|
||||||
|
if rand(abmrng(model)) ≤ wolf.reproduction_prob
|
||||||
|
wolf.energy /= 2
|
||||||
|
replicate!(wolf, model)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function first_sheep_in_position(pos, model)
|
||||||
|
ids = ids_in_position(pos, model)
|
||||||
|
j = findfirst(id -> model[id] isa Sheep, ids)
|
||||||
|
isnothing(j) ? nothing : model[ids[j]]::Sheep
|
||||||
end
|
end
|
||||||
|
|
||||||
# The function `initialize_model` returns a new model containing sheep, wolves, and grass
|
# The function `initialize_model` returns a new model containing sheep, wolves, and grass
|
||||||
|
@ -119,56 +165,23 @@ end
|
||||||
# Notice how the function `sheepwolf_step!`, which is our `agent_step!`,
|
# Notice how the function `sheepwolf_step!`, which is our `agent_step!`,
|
||||||
# is dispatched to the appropriate agent type via Julia's Multiple Dispatch system.
|
# is dispatched to the appropriate agent type via Julia's Multiple Dispatch system.
|
||||||
function sheepwolf_step!(sheep::Sheep, model)
|
function sheepwolf_step!(sheep::Sheep, model)
|
||||||
randomwalk!(sheep, model)
|
move!(sheep, model)
|
||||||
sheep.energy -= 1
|
|
||||||
if sheep.energy < 0
|
if sheep.energy < 0
|
||||||
remove_agent!(sheep, model)
|
remove_agent!(sheep, model)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
eat!(sheep, model)
|
eat!(sheep, model)
|
||||||
if rand(abmrng(model)) ≤ sheep.reproduction_prob
|
reproduce!(sheep, model)
|
||||||
sheep.energy /= 2
|
|
||||||
replicate!(sheep, model)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function sheepwolf_step!(wolf::Wolf, model)
|
function sheepwolf_step!(wolf::Wolf, model)
|
||||||
randomwalk!(wolf, model; ifempty=false)
|
move!(wolf, model)
|
||||||
wolf.energy -= 1
|
|
||||||
if wolf.energy < 0
|
if wolf.energy < 0
|
||||||
remove_agent!(wolf, model)
|
remove_agent!(wolf, model)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
## If there is any sheep on this grid cell, it's dinner time!
|
eat!(wolf, model)
|
||||||
dinner = first_sheep_in_position(wolf.pos, model)
|
reproduce!(wolf, model)
|
||||||
!isnothing(dinner) && eat!(wolf, dinner, model)
|
|
||||||
if rand(abmrng(model)) ≤ wolf.reproduction_prob
|
|
||||||
wolf.energy /= 2
|
|
||||||
replicate!(wolf, model)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function first_sheep_in_position(pos, model)
|
|
||||||
ids = ids_in_position(pos, model)
|
|
||||||
j = findfirst(id -> model[id] isa Sheep, ids)
|
|
||||||
isnothing(j) ? nothing : model[ids[j]]::Sheep
|
|
||||||
end
|
|
||||||
|
|
||||||
# Sheep and wolves have separate `eat!` functions. If a sheep eats grass, it will acquire
|
|
||||||
# additional energy and the grass will not be available for consumption until regrowth time
|
|
||||||
# has elapsed. If a wolf eats a sheep, the sheep dies and the wolf acquires more energy.
|
|
||||||
function eat!(sheep::Sheep, model)
|
|
||||||
if model.fully_grown[sheep.pos...]
|
|
||||||
sheep.energy += sheep.Δenergy
|
|
||||||
model.fully_grown[sheep.pos...] = false
|
|
||||||
end
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
function eat!(wolf::Wolf, sheep::Sheep, model)
|
|
||||||
remove_agent!(sheep, model)
|
|
||||||
wolf.energy += wolf.Δenergy
|
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# The behavior of grass function differently. If it is fully grown, it is consumable.
|
# The behavior of grass function differently. If it is fully grown, it is consumable.
|
||||||
|
@ -187,108 +200,110 @@ function grass_step!(model)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
sheepwolfgrass = initialize_model()
|
function run()
|
||||||
|
sheepwolfgrass = initialize_model()
|
||||||
|
|
||||||
# ## Running the model
|
# ## Running the model
|
||||||
# %% #src
|
# %% #src
|
||||||
# We will run the model for 500 steps and record the number of sheep, wolves and consumable
|
# We will run the model for 500 steps and record the number of sheep, wolves and consumable
|
||||||
# grass patches after each step. First: initialize the model.
|
# grass patches after each step. First: initialize the model.
|
||||||
|
|
||||||
using CairoMakie
|
CairoMakie.activate!() # hide
|
||||||
CairoMakie.activate!() # hide
|
|
||||||
|
|
||||||
# To view our starting population, we can build an overview plot using [`abmplot`](@ref).
|
# To view our starting population, we can build an overview plot using [`abmplot`](@ref).
|
||||||
# We define the plotting details for the wolves and sheep:
|
# We define the plotting details for the wolves and sheep:
|
||||||
offset(a) = a isa Sheep ? (-0.1, -0.1*rand()) : (+0.1, +0.1*rand())
|
offset(a) = a isa Sheep ? (-0.1, -0.1*rand()) : (+0.1, +0.1*rand())
|
||||||
ashape(a) = a isa Sheep ? :circle : :utriangle
|
ashape(a) = a isa Sheep ? :circle : :utriangle
|
||||||
acolor(a) = a isa Sheep ? RGBAf(1.0, 1.0, 1.0, 0.8) : RGBAf(0.2, 0.2, 0.3, 0.8)
|
acolor(a) = a isa Sheep ? RGBAf(1.0, 1.0, 1.0, 0.8) : RGBAf(0.2, 0.2, 0.3, 0.8)
|
||||||
|
|
||||||
# and instruct [`abmplot`](@ref) how to plot grass as a heatmap:
|
# and instruct [`abmplot`](@ref) how to plot grass as a heatmap:
|
||||||
grasscolor(model) = model.countdown ./ model.regrowth_time
|
grasscolor(model) = model.countdown ./ model.regrowth_time
|
||||||
# and finally define a colormap for the grass:
|
# and finally define a colormap for the grass:
|
||||||
heatkwargs = (colormap = [:brown, :green], colorrange = (0, 1))
|
heatkwargs = (colormap = [:brown, :green], colorrange = (0, 1))
|
||||||
|
|
||||||
# and put everything together and give it to [`abmplot`](@ref)
|
# and put everything together and give it to [`abmplot`](@ref)
|
||||||
plotkwargs = (;
|
plotkwargs = (;
|
||||||
agent_color = acolor,
|
agent_color = acolor,
|
||||||
agent_size = 25,
|
agent_size = 25,
|
||||||
agent_marker = ashape,
|
agent_marker = ashape,
|
||||||
offset,
|
offset,
|
||||||
agentsplotkwargs = (strokewidth = 1.0, strokecolor = :black),
|
agentsplotkwargs = (strokewidth = 1.0, strokecolor = :black),
|
||||||
heatarray = grasscolor,
|
heatarray = grasscolor,
|
||||||
heatkwargs = heatkwargs,
|
heatkwargs = heatkwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
sheepwolfgrass = initialize_model()
|
sheepwolfgrass = initialize_model()
|
||||||
|
|
||||||
fig, ax, abmobs = abmplot(sheepwolfgrass; plotkwargs...)
|
fig, ax, abmobs = abmplot(sheepwolfgrass; plotkwargs...)
|
||||||
fig
|
fig
|
||||||
|
|
||||||
# Now, lets run the simulation and collect some data. Define datacollection:
|
# Now, lets run the simulation and collect some data. Define datacollection:
|
||||||
sheep(a) = a isa Sheep
|
sheep(a) = a isa Sheep
|
||||||
wolf(a) = a isa Wolf
|
wolf(a) = a isa Wolf
|
||||||
count_grass(model) = count(model.fully_grown)
|
count_grass(model) = count(model.fully_grown)
|
||||||
# Run simulation:
|
# Run simulation:
|
||||||
sheepwolfgrass = initialize_model()
|
sheepwolfgrass = initialize_model()
|
||||||
steps = 1000
|
steps = 1000
|
||||||
adata = [(sheep, count), (wolf, count)]
|
adata = [(sheep, count), (wolf, count)]
|
||||||
mdata = [count_grass]
|
mdata = [count_grass]
|
||||||
adf, mdf = run!(sheepwolfgrass, steps; adata, mdata)
|
adf, mdf = run!(sheepwolfgrass, steps; adata, mdata)
|
||||||
|
|
||||||
# The following plot shows the population dynamics over time.
|
# The following plot shows the population dynamics over time.
|
||||||
# Initially, wolves become extinct because they consume the sheep too quickly.
|
# Initially, wolves become extinct because they consume the sheep too quickly.
|
||||||
# The few remaining sheep reproduce and gradually reach an
|
# The few remaining sheep reproduce and gradually reach an
|
||||||
# equilibrium that can be supported by the amount of available grass.
|
# equilibrium that can be supported by the amount of available grass.
|
||||||
function plot_population_timeseries(adf, mdf)
|
function plot_population_timeseries(adf, mdf)
|
||||||
figure = Figure(size = (600, 400))
|
figure = Figure(size = (600, 400))
|
||||||
ax = figure[1, 1] = Axis(figure; xlabel = "Step", ylabel = "Population")
|
ax = figure[1, 1] = Axis(figure; xlabel = "Step", ylabel = "Population")
|
||||||
sheepl = lines!(ax, adf.time, adf.count_sheep, color = :cornsilk4)
|
sheepl = lines!(ax, adf.time, adf.count_sheep, color = :cornsilk4)
|
||||||
wolfl = lines!(ax, adf.time, adf.count_wolf, color = RGBAf(0.2, 0.2, 0.3))
|
wolfl = lines!(ax, adf.time, adf.count_wolf, color = RGBAf(0.2, 0.2, 0.3))
|
||||||
grassl = lines!(ax, mdf.time, mdf.count_grass, color = :green)
|
grassl = lines!(ax, mdf.time, mdf.count_grass, color = :green)
|
||||||
figure[1, 2] = Legend(figure, [sheepl, wolfl, grassl], ["Sheep", "Wolves", "Grass"])
|
figure[1, 2] = Legend(figure, [sheepl, wolfl, grassl], ["Sheep", "Wolves", "Grass"])
|
||||||
figure
|
figure
|
||||||
|
end
|
||||||
|
|
||||||
|
plot_population_timeseries(adf, mdf)
|
||||||
|
|
||||||
|
# Altering the input conditions, we now see a landscape where sheep, wolves and grass
|
||||||
|
# find an equilibrium
|
||||||
|
# %% #src
|
||||||
|
stable_params = (;
|
||||||
|
n_sheep = 140,
|
||||||
|
n_wolves = 20,
|
||||||
|
dims = (30, 30),
|
||||||
|
Δenergy_sheep = 5,
|
||||||
|
sheep_reproduce = 0.31,
|
||||||
|
wolf_reproduce = 0.06,
|
||||||
|
Δenergy_wolf = 30,
|
||||||
|
seed = 71758,
|
||||||
|
)
|
||||||
|
|
||||||
|
sheepwolfgrass = initialize_model(;stable_params...)
|
||||||
|
adf, mdf = run!(sheepwolfgrass, 2000; adata, mdata)
|
||||||
|
plot_population_timeseries(adf, mdf)
|
||||||
|
|
||||||
|
# Finding a parameter combination that leads to long-term coexistence was
|
||||||
|
# surprisingly difficult. It is for such cases that the
|
||||||
|
# [Optimizing agent based models](@ref) example is useful!
|
||||||
|
# %% #src
|
||||||
|
|
||||||
|
# ## Video
|
||||||
|
# Given that we have defined plotting functions, making a video is as simple as
|
||||||
|
sheepwolfgrass = initialize_model(;stable_params...)
|
||||||
|
|
||||||
|
abmvideo(
|
||||||
|
"sheepwolf.mp4",
|
||||||
|
sheepwolfgrass;
|
||||||
|
frames = 100,
|
||||||
|
framerate = 8,
|
||||||
|
title = "Sheep Wolf Grass",
|
||||||
|
plotkwargs...,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ```@raw html
|
||||||
|
# <video width="auto" controls autoplay loop>
|
||||||
|
# <source src="../sheepwolf.mp4" type="video/mp4">
|
||||||
|
# </video>
|
||||||
|
# ```
|
||||||
end
|
end
|
||||||
|
run()
|
||||||
plot_population_timeseries(adf, mdf)
|
|
||||||
|
|
||||||
# Altering the input conditions, we now see a landscape where sheep, wolves and grass
|
|
||||||
# find an equilibrium
|
|
||||||
# %% #src
|
|
||||||
stable_params = (;
|
|
||||||
n_sheep = 140,
|
|
||||||
n_wolves = 20,
|
|
||||||
dims = (30, 30),
|
|
||||||
Δenergy_sheep = 5,
|
|
||||||
sheep_reproduce = 0.31,
|
|
||||||
wolf_reproduce = 0.06,
|
|
||||||
Δenergy_wolf = 30,
|
|
||||||
seed = 71758,
|
|
||||||
)
|
|
||||||
|
|
||||||
sheepwolfgrass = initialize_model(;stable_params...)
|
|
||||||
adf, mdf = run!(sheepwolfgrass, 2000; adata, mdata)
|
|
||||||
plot_population_timeseries(adf, mdf)
|
|
||||||
|
|
||||||
# Finding a parameter combination that leads to long-term coexistence was
|
|
||||||
# surprisingly difficult. It is for such cases that the
|
|
||||||
# [Optimizing agent based models](@ref) example is useful!
|
|
||||||
# %% #src
|
|
||||||
|
|
||||||
# ## Video
|
|
||||||
# Given that we have defined plotting functions, making a video is as simple as
|
|
||||||
sheepwolfgrass = initialize_model(;stable_params...)
|
|
||||||
|
|
||||||
abmvideo(
|
|
||||||
"sheepwolf.mp4",
|
|
||||||
sheepwolfgrass;
|
|
||||||
frames = 100,
|
|
||||||
framerate = 8,
|
|
||||||
title = "Sheep Wolf Grass",
|
|
||||||
plotkwargs...,
|
|
||||||
)
|
|
||||||
|
|
||||||
# ```@raw html
|
|
||||||
# <video width="auto" controls autoplay loop>
|
|
||||||
# <source src="../sheepwolf.mp4" type="video/mp4">
|
|
||||||
# </video>
|
|
||||||
# ```
|
|
||||||
|
|
Loading…
Reference in New Issue