Something went wrong on our end
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
nature_tests.jl 10.01 KiB
### Persefone.jl - a model of agricultural landscapes and ecosystems in Europe.
###
### These are the tests for the nature model (excluding individual species).
###
@testset "Habitat macros" begin
# set up the testing landscape
model = inittestmodel()
model.landscape[6,6] = Pixel(Ps.agriculture, 1, [])
species::Dict{String,Any} = Dict("name"=>"test_animal")
add_agent!((6,6), FarmPlot, model,
[(6,6)], model.crops["winter wheat"], Ps.janfirst,
0.0, 0.0, 0.0, 0.0, Vector{Ps.EventType}())
add_agent!((3,3), Animal, model, species, Ps.male, 1)
add_agent!((4,4), Animal, model, species, Ps.female, 1)
# create a set of habitat descriptors
h1 = @habitat(@landcover() == Ps.water)
h2 = @habitat(@cropname() == "winter wheat" &&
@cropheight() < 2)
h3 = @habitat(@distanceto(Ps.water) > 2 &&
@distancetoedge() <= 2)
h4 = @habitat(@countanimals(species="test_animal", radius=1) == 1)
# test the descriptors
@test h1((6,4), model) == true
@test h1((5,4), model) == false
@test h2((6,6), model) == true
@test h2((5,6), model) == false
@test h3((3,3), model) == true
@test h3((5,4), model) == false
@test h3((6,1), model) == false
@test h4((2,2), model) == true
@test h4((3,3), model) == false
@test h4((1,1), model) == false
end
@testset "Species initialisation" begin
model = inittestmodel()
spec = "test_animal"
species::Dict{String,Any} = Dict("name"=>spec)
# create a set of initialisation functions
initfun1 = Ps.initrandompopulation(10)
initfun2 = Ps.initrandompopulation(8*8*3, asexual=true)
initfun3 = Ps.initpopulation(@habitat(@landcover() == Ps.grass), pairs=true)
initfun4 = Ps.initpopulation(@habitat(@landcover() == Ps.water &&
@countanimals(species="test_animal", radius=0) < 5),
popsize=10)
# apply and test the initialisation functions
@test_logs (:info, "Initialised 10 $(spec)s.") initfun1(species, model)
@test all(a -> a.sex in (Ps.male, Ps.female), allagents(model))
genocide!(model)
@test_logs (:info, "Initialised 192 $(spec)s.") initfun2(species, model)
@test Ps.countanimals((1,1), model, species=spec, radius=0) ==
Ps.countanimals((6,6), model, species=spec, radius=0) == 3
@test all(a -> a.sex == Ps.hermaphrodite, allagents(model))
genocide!(model)
@test_logs (:info, "Initialised 36 $(spec)s.") initfun3(species, model)
@test Ps.countanimals((2,2), model, species=spec, radius=2) ==
Ps.countanimals((5,3), model, species=spec, radius=1) == 0
@test Ps.countanimals((5,5), model, species=spec, radius=0) ==
Ps.countanimals((6,6), model, species=spec, radius=0) == 2
a1, a2 = Ps.nearby_animals((6,6), model, 0)
@test a1.sex != a2.sex
genocide!(model)
@test_logs((:warn, "There are not enough suitable locations for $(spec) in the landscape."),
(:info, "Initialised 5 $(spec)s."),
initfun4(species, model))
@test Ps.countanimals((1,1), model, species=spec, radius=4) == 0
@test Ps.countanimals((6,4), model, species=spec, radius=0) == 5
end
@testset "Species macros" begin
# create a model landscape and a test species
model = inittestmodel()
@species Mermaid begin
ageofmaturity = 2
pesticidemortality = 1.0
@initialise(@habitat(@landcover() == Persefone.water), pairs=true)
@phase life begin
@debug "$(Persefone.animalid(animal)) is swimming happily in its pond."
@respond Persefone.pesticide @kill(@trait(pesticidemortality), "poisoning")
@respond Persefone.harvesting @setphase(drought)
@debug "Animal: $animal"
if @trait(sex) == Persefone.female && @countanimals() < 3 &&
@trait(age) >= @trait(ageofmaturity) && @landcover() == Persefone.water
@reproduce()
end
end
@phase drought begin
n = sum(1 for a in @neighbours(0))
@debug "$(Persefone.animalid(animal)) is experiencing drought with $n neighbour(s)."
@respond Persefone.sowing @setphase(life)
end
end
# test a complete mermaid life cycle
pond = (6,4)
mermaid = Mermaid(model)
@test typeof(Mermaid) <: Function
@test typeof(mermaid["life"]) <: Function
@test typeof(mermaid["initialise!"]) <: Function
@test typeof(mermaid[mermaid["phase"]]) <: Function
@test mermaid["phase"] == "life"
@test_logs (:info, "Initialised 2 Mermaids.") mermaid["initialise!"](mermaid, model)
@test Ps.countanimals((1,1), model, radius=4) == 0
@test Ps.countanimals(pond, model) == 2
@test model[1].age == 0
createevent!(model, [pond], Ps.harvesting)
@test_logs((:debug, "Mermaid 1 is swimming happily in its pond."),
(:debug, "Mermaid 2 is swimming happily in its pond."),
min_level=Logging.Debug, match_mode=:any,
stepsimulation!(Ps.withtestlogger(model)))
@test model[1].age == 1
@test model[2].traits["phase"] == "drought"
createevent!(model, [pond], Ps.sowing)
@test_logs((:debug, "Mermaid 1 is experiencing drought with 1 neighbour(s)."),
(:debug, "Mermaid 2 is experiencing drought with 1 neighbour(s)."),
min_level=Logging.Debug, match_mode=:any,
stepsimulation!(Ps.withtestlogger(model)))
@test_logs((:debug, "Mermaid 1 is swimming happily in its pond."),
(:debug, "Mermaid 1 has reproduced."),
(:debug, "Mermaid 2 is swimming happily in its pond."),
min_level=Logging.Debug, match_mode=:any,
stepsimulation!(Ps.withtestlogger(model)))
@test Ps.countanimals(pond, model) == 3
createevent!(model, [pond], Ps.pesticide)
@test_logs((:debug, "Mermaid 1 is swimming happily in its pond."),
(:debug, "Mermaid 1 has died from poisoning."),
(:debug, "Mermaid 2 is swimming happily in its pond."),
(:debug, "Mermaid 2 has died from poisoning."),
(:debug, "Mermaid 3 is swimming happily in its pond."),
(:debug, "Mermaid 3 has died from poisoning."),
min_level=Logging.Debug, match_mode=:any,
stepsimulation!(Ps.withtestlogger(model)))
@test Ps.countanimals(pond, model) == 0
# test @rand (this is done more easily outside of @species)
@test typeof(@rand()) == Float64
@test @rand([true, true])
end
@testset "Insect submodel" begin
# create a set of pixels and dates for testing
model = inittestmodel()
date1 = Date("2023-05-08") # day 128 (season begin)
date2 = Date("2023-07-06") # day 187 (insect max)
date3 = Date("2023-09-27") # day 270 (season end)
p1 = Pixel(Ps.agriculture, 1, [])
p2 = Pixel(Ps.agriculture, 1, [Ps.pesticide])
p3 = Pixel(Ps.grass, 1, [])
p4 = Pixel(Ps.soil, 1, [Ps.fertiliser, Ps.pesticide])
p5 = Pixel(Ps.forest, 1, [])
p6 = Pixel(Ps.water, 1, [])
# check whether the model calculates the same numbers I did by hand
model.date = date1
@test Ps.insectbiomass(p1, model) ≈ 0.00411 atol=0.0001
@test Ps.insectbiomass(p2, model) == 0.0
@test Ps.insectbiomass(p3, model) ≈ 0.15411 atol=0.0001
@test Ps.insectbiomass(p4, model) ≈ 0.00411 atol=0.0001
@test Ps.insectbiomass(p5, model) ≈ 0.30411 atol=0.0001
@test Ps.insectbiomass(p6, model) == 0.0
model.date = date2
@test Ps.insectbiomass(p1, model) == 0.3
@test Ps.insectbiomass(p2, model) == 0.15
@test Ps.insectbiomass(p3, model) == 0.45
@test Ps.insectbiomass(p4, model) == 0.3
@test Ps.insectbiomass(p5, model) == 0.6
@test Ps.insectbiomass(p6, model) == 0.0
model.date = date3
@test Ps.insectbiomass(p1, model) == 0.0
@test Ps.insectbiomass(p2, model) == 0.0
@test Ps.insectbiomass(p3, model) == 0.0
@test Ps.insectbiomass(p4, model) == 0.0
@test Ps.insectbiomass(p5, model) ≈ 0.01443 atol=0.0001
@test Ps.insectbiomass(p6, model) == 0.0
end
@testset "Energy submodel" begin
# DEB data for the skylark (https://bio.vu.nl/thb/deb/deblab/add_my_pet/entries_web/Alauda_arvensis/Alauda_arvensis_res.html)
skylarkparams = Ps.DEBparameters(0.0, # F_m XXX is unknown
0.8, # y_EX Sibly et al. (2013)
0.8075, # y_VE
0.04761, # v
0.0, # J_ET XXX is unknown
4699,# J_EM XXX seems awfully high?
0.9886, # k
0.95,# k_R
0.0, # M_E0 XXX is unknown
3.4, # M_Hb
25 # M_Hp XXX this seems too low?
)
#TODO
end
@testset "Skylark submodel" begin
# set up a modified test landscape
model = inittestmodel()
model.landscape[3,7] = Pixel(Ps.forest, missing, [])
species = Ps.Skylark(model)
# test migration
@test_logs((:info, "Initialised 2 Skylarks."),
(:debug, "Skylark 1 has migrated."),
(:debug, "Skylark 2 has migrated."),
min_level=Logging.Debug, match_mode=:any,
species["initialise!"](species, Ps.withtestlogger(model)))
@test nagents(model) == 0
@test length(model.migrants) == 2
@test model.migrants[1].first.sex != model.migrants[2].first.sex
for a in model.migrants
leave, arrive = a.first.migrationdates
@test leave[1] in (9, 10) || (leave[1] == 11 && leave[2] <= 15)
@test (arrive[1] == 2 && arrive[2] >= 15) || (arrive[1] == 3 && arrive[2] <= 15)
end
model.date = Date(year(model.date), 3, 17)
@test_logs((:debug, "Skylark 1 has returned."),
(:debug, "Skylark 2 has returned."),
min_level=Logging.Debug, match_mode=:any,
Ps.updatenature!(Ps.withtestlogger(model)))
#TODO
end