Skip to content

Commit

Permalink
Lab & HW 04
Browse files Browse the repository at this point in the history
  • Loading branch information
nmheim committed Oct 19, 2023
1 parent a4e964c commit b6dfb81
Show file tree
Hide file tree
Showing 4 changed files with 692 additions and 0 deletions.
198 changes: 198 additions & 0 deletions docs/src/lecture_04/Lab04Ecosystem.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
using StatsBase

abstract type Species end

abstract type PlantSpecies <: Species end
abstract type Grass <: PlantSpecies end

abstract type AnimalSpecies <: Species end
abstract type Sheep <: AnimalSpecies end
abstract type Wolf <: AnimalSpecies end

abstract type Agent{S<:Species} end

# instead of Symbols we can use an Enum for the sex field
# using an Enum here makes things easier to extend in case you
# need more than just binary sexes and is also more explicit than
# just a boolean
@enum Sex female male

########## World #############################################################

mutable struct World{A<:Agent}
agents::Dict{Int,A}
max_id::Int
end

function World(agents::Vector{<:Agent})
max_id = maximum(a.id for a in agents)
World(Dict(a.id=>a for a in agents), max_id)
end

# optional: overload Base.show
function Base.show(io::IO, w::World)
println(io, typeof(w))
for (_,a) in w.agents
println(io," $a")
end
end


########## Animals ###########################################################

mutable struct Animal{A<:AnimalSpecies} <: Agent{A}
const id::Int
energy::Float64
const Δenergy::Float64
const reprprob::Float64
const foodprob::Float64
const sex::Sex
end

function (A::Type{<:AnimalSpecies})(id::Int,E::T,ΔE::T,pr::T,pf::T,s::Sex) where T
Animal{A}(id,E,ΔE,pr,pf,s)
end

# get the per species defaults back
randsex() = rand(instances(Sex))
Sheep(id; E=4.0, ΔE=0.2, pr=0.8, pf=0.6, s=randsex()) = Sheep(id, E, ΔE, pr, pf, s)
Wolf(id; E=10.0, ΔE=8.0, pr=0.1, pf=0.2, s=randsex()) = Wolf(id, E, ΔE, pr, pf, s)


function Base.show(io::IO, a::Animal{A}) where {A<:AnimalSpecies}
e = a.energy
d = a.Δenergy
pr = a.reprprob
pf = a.foodprob
s = a.sex == female ? "" : ""
print(io, "$A$s #$(a.id) E=$e ΔE=$d pr=$pr pf=$pf")
end

# note that for new species we will only have to overload `show` on the
# abstract species/sex types like below!
Base.show(io::IO, ::Type{Sheep}) = print(io,"🐑")
Base.show(io::IO, ::Type{Wolf}) = print(io,"🐺")


########## Plants #############################################################

mutable struct Plant{P<:PlantSpecies} <: Agent{P}
const id::Int
size::Int
const max_size::Int
end

# constructor for all Plant{<:PlantSpecies} callable as PlantSpecies(...)
(A::Type{<:PlantSpecies})(id, s, m) = Plant{A}(id,s,m)
(A::Type{<:PlantSpecies})(id, m) = (A::Type{<:PlantSpecies})(id,rand(1:m),m)

# default specific for Grass
Grass(id; max_size=10) = Grass(id, rand(1:max_size), max_size)

function Base.show(io::IO, p::Plant{P}) where P
x = p.size/p.max_size * 100
print(io,"$P #$(p.id) $(round(Int,x))% grown")
end

Base.show(io::IO, ::Type{Grass}) = print(io,"🌿")


########## Eating / Dying / Reproducing ########################################

function eat!(wolf::Animal{Wolf}, sheep::Animal{Sheep}, w::World)
wolf.energy += sheep.energy * wolf.Δenergy
kill_agent!(sheep,w)
end
function eat!(sheep::Animal{Sheep}, grass::Plant{Grass}, ::World)
sheep.energy += grass.size * sheep.Δenergy
grass.size = 0
end
eat!(::Animal, ::Nothing, ::World) = nothing

kill_agent!(a::Agent, w::World) = delete!(w.agents, a.id)

function find_mate(a::Animal, w::World)
ms = filter(x->mates(x,a), w.agents |> values |> collect)
isempty(ms) ? nothing : sample(ms)
end
mates(a::Animal{A}, b::Animal{A}) where A<:AnimalSpecies = a.sex != b.sex
mates(::Agent, ::Agent) = false

function reproduce!(a::Animal{A}, w::World) where A
m = find_mate(a,w)
if !isnothing(m)
a.energy = a.energy / 2
vals = [getproperty(a,n) for n in fieldnames(Animal) if n [:id, :sex]]
new_id = w.max_id + 1
ŝ = Animal{A}(new_id, vals..., randsex())
w.agents[ŝ.id] = ŝ
w.max_id = new_id
end
end

# finding food / who eats who
function find_food(a::Animal, w::World)
as = filter(x -> eats(a,x), w.agents |> values |> collect)
isempty(as) ? nothing : sample(as)
end
eats(::Animal{Sheep},g::Plant{Grass}) = g.size > 0
eats(::Animal{Wolf},::Animal{Sheep}) = true
eats(::Agent,::Agent) = false


########## Stepping through time #############################################

function agent_step!(p::Plant, ::World)
if p.size < p.max_size
p.size += 1
end
end
function agent_step!(a::Animal, w::World)
a.energy -= 1
if rand() <= a.foodprob
dinner = find_food(a,w)
eat!(a, dinner, w)
end
if a.energy <= 0
kill_agent!(a,w)
return
end
if rand() <= a.reprprob
reproduce!(a,w)
end
return a
end

function world_step!(world::World)
# make sure that we only iterate over IDs that already exist in the
# current timestep this lets us safely add agents
ids = copy(keys(world.agents))

for id in ids
# agents can be killed by other agents, so make sure that we are
# not stepping dead agents forward
!haskey(world.agents,id) && continue

a = world.agents[id]
agent_step!(a,world)
end
end


########## Counting agents ####################################################

agent_count(p::Plant) = p.size / p.max_size
agent_count(::Animal) = 1
agent_count(as::Vector{<:Agent}) = sum(agent_count,as)

function agent_count(w::World)
function op(d::Dict,a::A) where A<:Agent
if A in keys(d)
d[A] += agent_count(a)
else
d[A] = agent_count(a)
end
return d
end
foldl(op, w.agents |> values |> collect, init=Dict())
end
39 changes: 39 additions & 0 deletions docs/src/lecture_04/grass-sheep-wolf.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Plots
include("Lab04Ecosystem.jl")

function make_counter()
n = 0
counter() = n += 1
end

function create_world()
n_grass = 1_000
n_sheep = 40
n_wolves = 4

nextid = make_counter()

World(vcat(
[Grass(nextid()) for _ in 1:n_grass],
[Sheep(nextid()) for _ in 1:n_sheep],
[Wolf(nextid()) for _ in 1:n_wolves],
))
end
world = create_world();

counts = Dict(n=>[c] for (n,c) in agent_count(world))
for _ in 1:100
world_step!(world)
for (n,c) in agent_count(world)
push!(counts[n],c)
end
end

plt = plot()
tolabel(::Type{Animal{Sheep}}) = "Sheep"
tolabel(::Type{Animal{Wolf}}) = "Wolf"
tolabel(::Type{Plant{Grass}}) = "Grass"
for (A,c) in counts
plot!(plt, c, label=tolabel(A), lw=2)
end
display(plt)
61 changes: 61 additions & 0 deletions docs/src/lecture_04/hw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Homework 4

In this homework you will have to write two additional `@testset`s for the
Ecosystem. One testset should be contained in a file `test/sheep.jl` and verify
that the function `eat!(::Animal{Sheep}, ::Plant{Grass}, ::World)` works correctly. Another
testset should be in the file `test/wolf.jl` and veryfiy that the function
`eat!(::Animal{Wolf}, ::Animal{Sheep}, ::World)` works correctly.

## How to submit?

Zip the whole package folder `Ecosystem.jl` and upload it to BRUTE.
The package has to include at least the following files:

```
├── src
│ └── Ecosystem.jl
└── test
├── sheep.jl # contains only a single @testset
├── wolf.jl # contains only a single @testset
└── runtests.jl
```
Thet `test/runtests.jl` file can look like this:
```
using Test
using Ecosystem
include("sheep.jl")
include("wolf.jl")
# ...
```

## Test `Sheep`

```@raw html
<div class="admonition is-category-homework">
<header class="admonition-header">Homework:</header>
<div class="admonition-body">
```
1. Create a `Sheep` with food probability $p_f=1$
2. Create *fully grown* `Grass` and a `World` with the two agents.
3. Execute `eat!(::Animal{Sheep}, ::Plant{Grass}, ::World)`
4. `@test` that the size of the `Grass` now has `size == 0`
```@raw html
</div></div>
```


## Test `Wolf`

```@raw html
<div class="admonition is-category-homework">
<header class="admonition-header">Homework:</header>
<div class="admonition-body">
```
1. Create a `Wolf` with food probability $p_f=1$
2. Create a `Sheep` and a `World` with the two agents.
3. Execute `eat!(::Animal{Wolf}, ::Animal{Sheep}, ::World)`
4. `@test` that the World only has one agent left in the agents dictionary
```@raw html
</div></div>
```
Loading

0 comments on commit b6dfb81

Please sign in to comment.