Skip to content

Commit d877356

Browse files
committed
Add lecture 5
1 parent 44476eb commit d877356

40 files changed

+5578
-5
lines changed

docs_vitepress/make.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ pages = [
5151
"Lab" => "lab.md",
5252
"Homework" => "hw.md",
5353
]),
54-
# "5: Performance benchmarking" => add_prefix("lecture_05", [
55-
# "Lecture" => "lecture.md",
56-
# "Lab" => "lab.md",
57-
# "Homework" => "hw.md",
58-
# ]),
54+
"5: Performance benchmarking" => add_prefix("lecture_05", [
55+
"Lecture" => "lecture.md",
56+
"Lab" => "lab.md",
57+
"Homework" => "hw.md",
58+
]),
5959
# "6: Lanuage introspection" => add_prefix("lecture_06", [
6060
# "Lecture" => "lecture.md",
6161
# "Lab" => "lab.md",
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
using StatsBase
2+
3+
abstract type Species end
4+
5+
abstract type PlantSpecies <: Species end
6+
abstract type Grass <: PlantSpecies end
7+
8+
abstract type AnimalSpecies <: Species end
9+
abstract type Sheep <: AnimalSpecies end
10+
abstract type Wolf <: AnimalSpecies end
11+
12+
abstract type Agent{S<:Species} end
13+
14+
abstract type Sex end
15+
abstract type Female <: Sex end
16+
abstract type Male <: Sex end
17+
18+
########## World #############################################################
19+
20+
mutable struct World{A<:Agent}
21+
agents::Dict{Int,A}
22+
max_id::Int
23+
end
24+
25+
function World(agents::Vector{<:Agent})
26+
ids = [a.id for a in agents]
27+
length(unique(ids)) == length(agents) || error("Not all agents have unique IDs!")
28+
29+
types = unique(typeof.(agents))
30+
dict = Dict{Int,Union{types...}}(a.id => a for a in agents)
31+
World(dict, maximum(ids))
32+
end
33+
34+
# optional: overload Base.show
35+
function Base.show(io::IO, w::World)
36+
println(io, typeof(w))
37+
for (_,a) in w.agents
38+
println(io," $a")
39+
end
40+
end
41+
42+
43+
########## Animals ###########################################################
44+
45+
mutable struct Animal{A<:AnimalSpecies,S<:Sex} <: Agent{A}
46+
const id::Int
47+
energy::Float64
48+
const Δenergy::Float64
49+
const reprprob::Float64
50+
const foodprob::Float64
51+
end
52+
53+
function (A::Type{<:AnimalSpecies})(id::Int,E::T,ΔE::T,pr::T,pf::T,S::Type{<:Sex}) where T
54+
Animal{A,S}(id,E,ΔE,pr,pf)
55+
end
56+
57+
# get the per species defaults back
58+
randsex() = rand(subtypes(Sex))
59+
Sheep(id; E=4.0, ΔE=0.2, pr=0.8, pf=0.6, S=randsex()) = Sheep(id, E, ΔE, pr, pf, S)
60+
Wolf(id; E=10.0, ΔE=8.0, pr=0.1, pf=0.2, S=randsex()) = Wolf(id, E, ΔE, pr, pf, S)
61+
62+
63+
function Base.show(io::IO, a::Animal{A,S}) where {A,S}
64+
e = a.energy
65+
d = a.Δenergy
66+
pr = a.reprprob
67+
pf = a.foodprob
68+
print(io, "$A$S #$(a.id) E=$e ΔE=$d pr=$pr pf=$pf")
69+
end
70+
71+
# note that for new species we will only have to overload `show` on the
72+
# abstract species/sex types like below!
73+
Base.show(io::IO, ::Type{Sheep}) = print(io,"🐑")
74+
Base.show(io::IO, ::Type{Wolf}) = print(io,"🐺")
75+
Base.show(io::IO, ::Type{Male}) = print(io,"")
76+
Base.show(io::IO, ::Type{Female}) = print(io,"")
77+
78+
79+
########## Plants #############################################################
80+
81+
mutable struct Plant{P<:PlantSpecies} <: Agent{P}
82+
const id::Int
83+
size::Int
84+
const max_size::Int
85+
end
86+
87+
# constructor for all Plant{<:PlantSpecies} callable as PlantSpecies(...)
88+
(A::Type{<:PlantSpecies})(id, s, m) = Plant{A}(id,s,m)
89+
(A::Type{<:PlantSpecies})(id, m) = (A::Type{<:PlantSpecies})(id,rand(1:m),m)
90+
91+
# default specific for Grass
92+
Grass(id; max_size=10) = Grass(id, rand(1:max_size), max_size)
93+
94+
function Base.show(io::IO, p::Plant{P}) where P
95+
x = p.size/p.max_size * 100
96+
print(io,"$P #$(p.id) $(round(Int,x))% grown")
97+
end
98+
99+
Base.show(io::IO, ::Type{Grass}) = print(io,"🌿")
100+
101+
102+
########## Eating / Dying / Reproducing ########################################
103+
104+
function eat!(wolf::Animal{Wolf}, sheep::Animal{Sheep}, w::World)
105+
wolf.energy += sheep.energy * wolf.Δenergy
106+
kill_agent!(sheep,w)
107+
end
108+
function eat!(sheep::Animal{Sheep}, grass::Plant{Grass}, ::World)
109+
sheep.energy += grass.size * sheep.Δenergy
110+
grass.size = 0
111+
end
112+
eat!(::Animal, ::Nothing, ::World) = nothing
113+
114+
kill_agent!(a::Agent, w::World) = delete!(w.agents, a.id)
115+
116+
function find_agent(::Type{A}, w::World) where A<:Agent
117+
dict = filter(x -> isa(x,A), w.agents |> values |> collect)
118+
as = dict |> values |> collect
119+
isempty(as) ? nothing : sample(as)
120+
end
121+
122+
find_food(::Animal{Wolf}, w::World) = find_agent(Animal{Sheep}, w)
123+
find_food(::Animal{Sheep}, w::World) = find_agent(Plant{Grass}, w)
124+
125+
find_mate(::Animal{A,Female}, w::World) where A<:AnimalSpecies = find_agent(Animal{A,Male}, w)
126+
find_mate(::Animal{A,Male}, w::World) where A<:AnimalSpecies = find_agent(Animal{A,Female}, w)
127+
128+
function reproduce!(a::Animal{A}, w::World) where A
129+
m = find_mate(a,w)
130+
if !isnothing(m)
131+
a.energy = a.energy / 2
132+
vals = [getproperty(a,n) for n in fieldnames(Animal) if n [:id, :sex]]
133+
new_id = w.max_id + 1
134+
ŝ = A(new_id, vals..., randsex())
135+
w.agents[ŝ.id] = ŝ
136+
w.max_id = new_id
137+
return
138+
end
139+
end
140+
141+
142+
########## Stepping through time #############################################
143+
144+
function agent_step!(p::Plant, ::World)
145+
if p.size < p.max_size
146+
p.size += 1
147+
end
148+
end
149+
function agent_step!(a::Animal, w::World)
150+
a.energy -= 1
151+
if rand() <= a.foodprob
152+
dinner = find_food(a,w)
153+
eat!(a, dinner, w)
154+
end
155+
if a.energy <= 0
156+
kill_agent!(a,w)
157+
return
158+
end
159+
if rand() <= a.reprprob
160+
reproduce!(a,w)
161+
end
162+
return a
163+
end
164+
165+
function world_step!(world::World)
166+
# make sure that we only iterate over IDs that already exist in the
167+
# current timestep this lets us safely add agents
168+
ids = copy(keys(world.agents))
169+
170+
for id in ids
171+
# agents can be killed by other agents, so make sure that we are
172+
# not stepping dead agents forward
173+
!haskey(world.agents,id) && continue
174+
175+
a = world.agents[id]
176+
agent_step!(a,world)
177+
end
178+
end
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using BenchmarkTools
2+
using Random
3+
Random.seed!(0)
4+
5+
include("Ecosystem.jl")
6+
7+
sheep = Sheep(1,1,1,1,1,Female)
8+
sheep2 = Sheep(3001,1,1,1,1,Male)
9+
world = World(vcat([sheep,sheep2], [Grass(i) for i=2:3000]))
10+
11+
# check that something is returned
12+
@info "check returns" find_food(sheep, world) reproduce!(sheep, world)
13+
14+
# check type stability
15+
@code_warntype find_food(sheep, world)
16+
@code_warntype reproduce!(sheep, world)
17+
18+
# benchmark
19+
sheep = Sheep(1,1,1,1,1,Female)
20+
sheep2 = Sheep(3001,1,1,1,1,Male)
21+
world = World(vcat([sheep,sheep2], [Grass(i) for i=2:3000]))
22+
@btime find_food($sheep, $world)
23+
24+
sheep = Sheep(1,1,1,1,1,Female)
25+
sheep2 = Sheep(3001,1,1,1,1,Male)
26+
world = World(vcat([sheep,sheep2], [Grass(i) for i=2:3000]))
27+
@btime reproduce!($sheep, $world)

0 commit comments

Comments
 (0)