From 21ea890b0d857096d7183e787d85ef08433416d6 Mon Sep 17 00:00:00 2001 From: Daniel Vedder <daniel.vedder@idiv.de> Date: Fri, 6 Jan 2023 23:03:33 +0100 Subject: [PATCH] Wrote tests for the habitat macros, lots of bug fixes Everything now seems to work as intended :-) (Although a lot of the nature tests are still missing.) --- src/core/simulation.jl | 4 +-- src/nature/nature.jl | 2 +- src/nature/populations.jl | 25 ++++++------- src/nature/species/skylark.jl | 2 +- src/nature/species/wolpertinger.jl | 4 +-- test/landscape_tests.jl | 6 ++-- test/nature_tests.jl | 56 +++++++++++++++++++++--------- 7 files changed, 59 insertions(+), 40 deletions(-) diff --git a/src/core/simulation.jl b/src/core/simulation.jl index f8c94f9..e9b9663 100644 --- a/src/core/simulation.jl +++ b/src/core/simulation.jl @@ -55,7 +55,7 @@ end Wrap up the simulation. Currently doesn't do anything except print some information. """ function finalise(model::AgentBasedModel) - @info "Simulated $(model.date-param("core.startdate")) days." + @info "Simulated $(model.date-param("core.startdate"))." @info "Simulation run completed at $(Dates.now()),\nwrote output to $(param("core.outdir"))." #XXX is there anything to do here? #genocide!(model) @@ -67,7 +67,7 @@ end Carry out a complete simulation run. """ function simulate(config::String=PARAMFILE) - model = initialise(config) + model = initialise(config) #TODO add seed value runtime = Dates.value(param("core.enddate")-param("core.startdate"))+1 step!(model, dummystep, stepsimulation!, runtime) finalise(model) diff --git a/src/nature/nature.jl b/src/nature/nature.jl index f0d647a..e15e65b 100644 --- a/src/nature/nature.jl +++ b/src/nature/nature.jl @@ -100,7 +100,7 @@ of type `AgentBasedModel`). """ macro species(name, body) quote - Core.@__doc__ function $(esc(name))(model::AgentBasedModel) + Core.@__doc__ function $(esc(name))($(esc(:model))::AgentBasedModel) $(esc(:name)) = string($(QuoteNode(name))) $(esc(body)) vardict = Base.@locals diff --git a/src/nature/populations.jl b/src/nature/populations.jl index 62ba4e1..8a58529 100644 --- a/src/nature/populations.jl +++ b/src/nature/populations.jl @@ -38,18 +38,18 @@ function initpopulation(habitatdescriptor::Function; for y in shuffle!(Vector(1:height)) if habitatdescriptor((x,y), model) if pairs - add_agent!(Animal, (x,y), model, species, female, 0) - add_agent!(Animal, (x,y), model, species, male, 0) + add_agent!((x,y), Animal, model, species, female, 0) + add_agent!((x,y), Animal, model, species, male, 0) n += 2 else sex = asexual ? hermaphrodite : rand([male, female]) - add_agent!(Animal, (x,y), model, species, sex, 0) + add_agent!((x,y), Animal, model, species, sex, 0) n += 1 end end - (n >= popsize) && break + (popsize > 0 && n >= popsize) && break end - (n >= popsize) && break + (popsize > 0 && n >= popsize) && break end if lastn == n # prevent an infinite loop - we don't have a Cray... @warn "There are not enough suitable locations for $(specname) in the landscape." @@ -62,18 +62,13 @@ function initpopulation(habitatdescriptor::Function; end """ - initrandompopulation(popsize, asexual=true) + initrandompopulation(popsize; kwargs...) A simplified version of `initpopulation()`. Creates a function that initialises -`popsize` individuals, spread at random across the landscape. If `popsize` is less -than 1, it is interpreted as a population density (i.e. 1 animal per `popsize` pixels). +`popsize` individuals, spread at random across the landscape. """ -function initrandompopulation(popsize::Union{Int64,Float64}; kwargs...) - if popsize < 1.0 - x, y = size(model.landscape) - popsize = round(x*y*popsize) - end - initpopulation(@habitat(true), popsize=Int(popsize), kwargs...) +function initrandompopulation(popsize::Int64; kwargs...) + initpopulation(@habitat(true); popsize=popsize, kwargs...) end #XXX initpopulation with dispersal from an original source? @@ -137,7 +132,7 @@ function countanimals(pos::Tuple{Int64,Int64}, model::AgentBasedModel, speciesname::String, radius::Int64=0) n = 0 for a in nearby_animals(pos, model, radius) - model[a].traits.name == speciesname && (n += 1) + a.traits["name"] == speciesname && (n += 1) end return n end diff --git a/src/nature/species/skylark.jl b/src/nature/species/skylark.jl index 0f877bd..5293ea0 100644 --- a/src/nature/species/skylark.jl +++ b/src/nature/species/skylark.jl @@ -14,7 +14,7 @@ At the moment, this implementation is still in development. """ @species Skylark begin - popdensity = 1/10000 + popsize = Int(round(1/10000*reduce(*, size(model.landscape)))) lifeexpectancy = 365*5 eggharvestmortality = 0.9 diff --git a/src/nature/species/wolpertinger.jl b/src/nature/species/wolpertinger.jl index 67a4914..de906cf 100644 --- a/src/nature/species/wolpertinger.jl +++ b/src/nature/species/wolpertinger.jl @@ -11,13 +11,13 @@ It is purported to have the body of a hare, the wings of a bird, and the antlers of a deer. """ @species Wolpertinger begin - popdensity = 1/100000 + popsize = Int(round(1/100000*reduce(*, size(model.landscape)))) fecundity = 0.02 mortality = 0.015 maxspeed = 5 crowdingfactor = maxspeed*2 - initialise! = initrandompopulation(popdensity) + initialise! = initrandompopulation(popsize) phase = "lifephase" """ diff --git a/test/landscape_tests.jl b/test/landscape_tests.jl index b899d53..ae6b3b1 100644 --- a/test/landscape_tests.jl +++ b/test/landscape_tests.jl @@ -15,18 +15,18 @@ Create a 6x6 landscape with three land cover types for testing: F F G G G G F F G G G G """ -function smalltestlandscape() +function smalltestlandscape(agenttype::Type=Animal) landscape = Matrix{Pixel}(undef, 6, 6) for x in 1:6 for y in 1:6 (x in (1:2) || y in (1:4)) ? lc = Ps.forest : lc = Ps.grass - landscape[x,y] = Pixel(lc, 0, []) + landscape[x,y] = Pixel(lc, missing, []) end end landscape[6,4] = Pixel(Ps.water, 0, []) space = GridSpace(size(landscape), periodic=false) properties = Dict{Symbol,Any}(:landscape=>landscape) - return AgentBasedModel(Animal, space, properties=properties, warn=false) + return AgentBasedModel(agenttype, space, properties=properties, warn=false) end @testset "Landscape initialisation" begin diff --git a/test/nature_tests.jl b/test/nature_tests.jl index 0cabc1d..73debd7 100644 --- a/test/nature_tests.jl +++ b/test/nature_tests.jl @@ -8,39 +8,63 @@ end @testset "Habitat macros" begin - #TODO + # set up the testing landscape + model = smalltestlandscape(Union{Animal,FarmPlot}) + 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(Ps.@landcover() == Ps.water) + h2 = @habitat(Ps.@croptype() == Ps.wheat && + Ps.@cropheight() < 2) + h3 = @habitat(Ps.@distanceto(water) > 2 && + Ps.@distancetoedge() <= 2) + h4 = @habitat(Ps.@countanimals("test_animal", 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() - specname = "test_animal" - species = Dict("name"=>specname) + 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(Ps.@landcover() == Ps.grass), pairs=true) initfun4 = Ps.initpopulation(@habitat(Ps.@landcover() == Ps.water && - Ps.@countanimals(specname, 0) <= 5), + Ps.@countanimals("test_animal", 0) < 5), popsize=10) # apply and test the initialisation functions - @test_logs (:info, "Initialised 10 $(specname)s.") initfun1(species, model) + @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 $(specname)s.") initfun2(species, model) - @test countanimals((1,1), model, specname, 0) == countanimals((6,6), model, specname, 0) == 3 - @test Vector(nearby_animals((1,1), model, 0))[1].sex == Ps.hermaphrodite + @test_logs (:info, "Initialised 108 $(spec)s.") initfun2(species, model) + @test Ps.countanimals((1,1), model, spec, 0) == Ps.countanimals((6,6), model, spec, 0) == 3 + @test all(a -> a.sex == Ps.hermaphrodite, allagents(model)) genocide!(model) - @test_logs (:info, "Initialised 16 $(specname)s.") initfun3(species, model) - @test countanimals((2,2), model, specname, 2) == countanimals((5,3), model, specname, 1) == 0 - @test countanimals((5,5), model, specname, 0) == countanimals((6,6), model, specname, 0) == 2 - a1, a2 = Vector(nearby_animals((6,6), model, 0)) + @test_logs (:info, "Initialised 16 $(spec)s.") initfun3(species, model) + @test Ps.countanimals((2,2), model, spec, 2) == Ps.countanimals((5,3), model, spec, 1) == 0 + @test Ps.countanimals((5,5), model, spec, 0) == Ps.countanimals((6,6), model, spec, 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 $(specname) in the landscape."), - (:info, "Initialised 5 $(specname)s."), + @test_logs((:warn, "There are not enough suitable locations for $(spec) in the landscape."), + (:info, "Initialised 5 $(spec)s."), initfun4(species, model)) - @test countanimals((1,1), model, specname, 4) == 0 - @test countanimals((6,4), model, specname, 0) == 5 + @test Ps.countanimals((1,1), model, spec, 4) == 0 + @test Ps.countanimals((6,4), model, spec, 0) == 5 end @testset "Population functions" begin -- GitLab