Skip to content
Snippets Groups Projects
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