Skip to content

Commit

Permalink
Lab & HW 03 (#125)
Browse files Browse the repository at this point in the history
  • Loading branch information
nmheim authored Oct 12, 2023
1 parent 414e09e commit 764b96a
Show file tree
Hide file tree
Showing 4 changed files with 651 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ lecture_02 = [

lecture_03 = [
"Lecture" => "./lecture_03/lecture.md"
"Lab" => "./lecture_03/lab.md"
"Homework" => "./lecture_03/hw.md"
]

lecture_04 = [
Expand Down
151 changes: 151 additions & 0 deletions docs/src/lecture_03/Lab03Ecosystem.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
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,"🌿")

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

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

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

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



########## 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
n = nameof(A)
d[n] = haskey(d,n) ? d[n]+agent_count(a) : agent_count(a)
return d
end
reduce(op, w.agents |> values, init=Dict{Symbol,Float64}())
end
100 changes: 100 additions & 0 deletions docs/src/lecture_03/hw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Homework 3

In this homework we will implement a function `find_food` and practice the use of closures.
The solution of lab 3 can be found
[here](https://github.com/JuliaTeachingCTU/Scientific-Programming-in-Julia/blob/2023W/docs/src/lecture_03/Lab03Ecosystem.jl).
You can use this file and add the code that you write for the homework to it.

## How to submit?

Put all your code (including your or the provided solution of lab 2)
in a script named `hw.jl`. Zip only this file (not its parent folder) and
upload it to BRUTE.

```@setup block
projdir = dirname(Base.active_project())
include(joinpath(projdir,"src","lecture_03","Lab03Ecosystem.jl"))
function find_food(a::Animal, w::World)
as = filter(x -> eats(a,x), w.agents |> values |> collect)
isempty(as) ? nothing : rand(as)
end
eats(::Animal{Sheep},g::Plant{Grass}) = g.size > 0
eats(::Animal{Wolf},::Animal{Sheep}) = true
eats(::Agent,::Agent) = false
function every_nth(f::Function, n::Int)
i = 1
function callback(args...)
# display(i) # comment this out to see out the counter increases
if i == n
f(args...)
i = 1
else
i += 1
end
end
end
nothing # hide
```


## Agents looking for food

```@raw html
<div class="admonition is-category-homework">
<header class="admonition-header">Homework:</header>
<div class="admonition-body">
```
Implement a method `find_food(a::Animal, w::World)` returns one randomly chosen
agent from all `w.agents` that can be eaten by `a` or `nothing` if no food could
be found. This means that if e.g. the animal is a `Wolf` you have to return one
random `Sheep`, etc.

*Hint*: You can write a general `find_food` method for all animals and move the
parts that are specific to the concrete animal types to a separate function.
E.g. you could define a function `eats(::Animal{Wolf}, ::Animal{Sheep}) = true`, etc.

You can check your solution with the public test:
```@repl block
sheep = Sheep(1,pf=1.0)
world = World([Grass(2), sheep])
find_food(sheep, world) isa Plant{Grass}
```
```@raw html
</div></div>
```

## Callbacks & Closures

```@raw html
<div class="admonition is-category-homework">
<header class="admonition-header">Homework:</header>
<div class="admonition-body">
```
Implement a function `every_nth(f::Function,n::Int)` that takes an inner
function `f` and uses a closure to construct an outer function `g` that only
calls `f` every `n`th call to `g`. For example, if `n=3` the inner function `f` be called
at the 3rd, 6th, 9th ... call to `g` (not at the 1st, 2nd, 4th, 5th, 7th... call).

**Hint**: You can use splatting via `...` to pass on an unknown number of
arguments from the outer to the inner function.
```@raw html
</div></div>
```
You can use `every_nth` to log (or save) the agent count only every couple of
steps of your simulation. Using `every_nth` will look like this:
```@repl block
w = World([Sheep(1), Grass(2), Wolf(3)])
# `@info agent_count(w)` is executed only every 3rd call to logcb(w)
logcb = every_nth(w->(@info agent_count(w)), 3);
logcb(w); # x->(@info agent_count(w)) is not called
logcb(w); # x->(@info agent_count(w)) is not called
logcb(w); # x->(@info agent_count(w)) *is* called
```
```@raw html
</div></div>
```
Loading

0 comments on commit 764b96a

Please sign in to comment.