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