### Persefone - a socio-economic-ecological model of European agricultural landscapes. ### ### These are the tests for the nature model (excluding individual species). ### @testset "Habitat macros" begin # set up the testing landscape model = smalltestlandscape() model.landscape[6,6] = Pixel(Ps.agriculture, 1, []) species::Dict{String,Any} = Dict("name"=>"test_animal") add_agent!((6,6), FarmPlot, model, [(6,6)], Ps.wheat, 1.2, 3.4) 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(@croptype() == Ps.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 = smalltestlandscape() spec = "test_animal" species::Dict{String,Any} = Dict("name"=>spec) # create a set of initialisation functions initfun1 = Ps.initrandompopulation(10) initfun2 = Ps.initrandompopulation(6*6*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 108 $(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 16 $(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 = smalltestlandscape() @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.harvest @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.harvest) @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 = smalltestlandscape() 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) ≈ 4.11 atol=0.01 @test Ps.insectbiomass(p2, model) == 0.0 @test Ps.insectbiomass(p3, model) ≈ 154.11 atol=0.01 @test Ps.insectbiomass(p4, model) ≈ 4.11 atol=0.01 @test Ps.insectbiomass(p5, model) ≈ 304.11 atol=0.01 @test Ps.insectbiomass(p6, model) == 0.0 model.date = date2 @test Ps.insectbiomass(p1, model) == 300.0 @test Ps.insectbiomass(p2, model) == 150.0 @test Ps.insectbiomass(p3, model) == 450.0 @test Ps.insectbiomass(p4, model) == 300.0 @test Ps.insectbiomass(p5, model) == 600.0 @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) ≈ 14.43 atol=0.01 @test Ps.insectbiomass(p6, model) == 0.0 end