From 1e389e15765fabf88cfe5b3694d9c17d7b3de17e Mon Sep 17 00:00:00 2001
From: Daniel Vedder <daniel.vedder@idiv.de>
Date: Sat, 28 Oct 2023 19:01:52 +0200
Subject: [PATCH] Wrote tests for skylark migration

---
 Project.toml                  |  2 +-
 src/Persefone.jl              |  2 +-
 src/nature/nature.jl          |  2 +-
 src/nature/species/skylark.jl | 13 +++++++++----
 test/io_tests.jl              |  7 +++++--
 test/landscape_tests.jl       |  3 ++-
 test/nature_tests.jl          | 33 ++++++++++++++++++++++++++++++---
 test/runtests.jl              | 27 ++++++++++++++++-----------
 8 files changed, 65 insertions(+), 24 deletions(-)

diff --git a/Project.toml b/Project.toml
index 10e11cc..7ed919c 100644
--- a/Project.toml
+++ b/Project.toml
@@ -1,7 +1,7 @@
 name = "Persefone"
 uuid = "039acd1d-2a07-4b33-b082-83a1ff0fd136"
 authors = ["Daniel Vedder <daniel.vedder@idiv.de>"]
-version = "0.3.6"
+version = "0.4.0"
 
 [deps]
 Agents = "46ada45e-f475-11e8-01d0-f70cc89e6671"
diff --git a/src/Persefone.jl b/src/Persefone.jl
index e02955f..cc6fef7 100644
--- a/src/Persefone.jl
+++ b/src/Persefone.jl
@@ -110,7 +110,7 @@ include("nature/ecologicaldata.jl")
 # for f in readdir("nature/species", join=true)
 #     endswith(f, ".jl") && include(f)
 # end
-#include("nature/species/skylark.jl")
+include("nature/species/skylark.jl")
 include("nature/species/wolpertinger.jl")
 include("nature/species/wyvern.jl")
 
diff --git a/src/nature/nature.jl b/src/nature/nature.jl
index b9e96ee..4fca51f 100644
--- a/src/nature/nature.jl
+++ b/src/nature/nature.jl
@@ -95,7 +95,7 @@ Run processes that affect all animals.
 function updatenature!(model::AgentBasedModel)
     # The migrant pool is sorted by date of return, so we can simply look at the top
     # of the stack to check whether any animals are returning today.
-    while !isempty(model.migrants) && model.migrants[1].second == model.date
+    while !isempty(model.migrants) && model.migrants[1].second <= model.date
         add_agent_pos!(model.migrants[1].first, model)
         @debug "$(animalid(model.migrants[1].first)) has returned."
         deleteat!(model.migrants, 1)
diff --git a/src/nature/species/skylark.jl b/src/nature/species/skylark.jl
index 98d7193..5b0a2e8 100644
--- a/src/nature/species/skylark.jl
+++ b/src/nature/species/skylark.jl
@@ -25,7 +25,7 @@ At the moment, this implementation is still in development.
     migrationdates = ()
 
     habitats = @habitat((@landcover() == grass || 
-                         (@landcover() == agriculture && @croptype() != maize)) &&
+                         (@landcover() == agriculture && @cropname() != "maize")) &&
                         @distanceto(forest) > 5)
     
     @initialise(habitats, pairs=true, initfunction=initskylark)
@@ -69,13 +69,18 @@ Initialise a skylark individual. Selects migration dates and checks if the
 bird should currently be on migration.
 """
 function initskylark(animal::Animal, model::AgentBasedModel)
+    @debug "Added $(animalid(animal)) at $(animal.pos)"
     animal.migrationdates = migrationdates(animal, model)
     leave = animal.migrationdates[1]
     arrive = animal.migrationdates[2]
     m, d = monthday(model.date)
     migrate = (((m < arrive[1]) || (m == arrive[1] && d < arrive[2])) ||
                ((m > leave[1]) || (m == leave[1] && d >= leave[2])))
-    migrate && @migrate(Date(year(model.date)+year(1), arrive[1], arrive[2]))
+    if migrate
+        returndate = Date(year(model.date), arrive[1], arrive[2])
+        model.date != @param(core.startdate) && (returndate += Year(1))
+        @migrate(returndate)
+    end
     #TODO other stuff?
 end
 
@@ -90,7 +95,7 @@ function migrationdates(skylark::Animal, model::AgentBasedModel)
     minarrive = skylark.sex == male ? (2, 15) : (3, 1)
     deltaleave = @rand(0:45) #XXX ought to be normally distributed
     deltaarrive = @rand(0:15) #XXX ought to be normally distributed
-    leave = monthday(Date(2000, minleave[1], minleave[2]) + Day(deltaleave))
-    arrive = monthday(Date(2000, minarrive[1], minarrive[2]) + Day(deltaarrive))
+    leave = monthday(Date(2001, minleave[1], minleave[2]) + Day(deltaleave))
+    arrive = monthday(Date(2001, minarrive[1], minarrive[2]) + Day(deltaarrive))
     (leave, arrive)
 end
diff --git a/test/io_tests.jl b/test/io_tests.jl
index aa45dd2..734ee78 100644
--- a/test/io_tests.jl
+++ b/test/io_tests.jl
@@ -67,6 +67,8 @@ end
 @testset "Model object serialization" begin
     model = inittestmodel()
     Ps.createdatadir(@param(core.outdir), true)
+    originalenddate = @param(core.enddate)
+    @param(core.enddate) = Date(year(model.date), 2, 5)
     @test_logs((:debug, "Saved model object to results_testsuite/test.dat."),
                min_level=Logging.Debug, match_mode=:any,
                savemodelobject(model, "test"))
@@ -75,11 +77,12 @@ end
     @test model.date == model2.date
     @test model.settings == model2.settings
     @test length(model.agents) == length(model2.agents)
-    simulate!(model)
-    simulate!(model2)
+    simulate!(Ps.withtestlogger(model))
+    simulate!(Ps.withtestlogger(model2))
     @test model.date == model2.date
     @test length(model.agents) == length(model2.agents)
     rm(@param(core.outdir), force=true, recursive=true)
+    @param(core.enddate) = originalenddate
 end
 
 @testset "Output visualisation" begin
diff --git a/test/landscape_tests.jl b/test/landscape_tests.jl
index aaf1f8e..3a29f87 100644
--- a/test/landscape_tests.jl
+++ b/test/landscape_tests.jl
@@ -42,7 +42,8 @@ end
 
 @testset "Landscape functions" begin
     model = inittestmodel()
-    @test Ps.distanceto((2,3), model, Ps.forest) == 0
+    @test Ps.distanceto((2,3), model, Ps.agriculture) == 0
+    @test Ps.distanceto((2,3), model, Ps.forest) == 2
     @test Ps.distanceto((2,3), model, Ps.grass) == 2
     @test Ps.distanceto((2,3), model, Ps.water) == 4
     @test Ps.distanceto((2,3), model, Ps.soil) == Inf
diff --git a/test/nature_tests.jl b/test/nature_tests.jl
index 1d6276b..dddbb64 100644
--- a/test/nature_tests.jl
+++ b/test/nature_tests.jl
@@ -39,7 +39,7 @@ end
     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)
+    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),
@@ -48,12 +48,12 @@ end
     @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_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 16 $(spec)s.") initfun3(species, 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) ==
@@ -192,3 +192,30 @@ end
     #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
diff --git a/test/runtests.jl b/test/runtests.jl
index 78d3293..4636903 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -58,20 +58,25 @@ end
 """
     smalltestlandscape()
 
-Create a 6x6 landscape with three land cover types for testing:
+Create a 8x8 landscape with five land cover types for testing:
 
-    F F F F F F
-    F F F F F F
-    F F F F F F
-    F F F F F W
-    F F G G G G
-    F F G G G G
+    A A A A A A A A
+    A A A A A A A A
+    A A A A A A A A
+    A A A A A W A A
+    F F G G G G G G
+    F F G G G G G G
+    F F G G G G G G
+    B B B B B B B B
 """
 function smalltestlandscape()
-    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 = Matrix{Pixel}(undef, 8, 8)
+    for x in 1:8
+        for y in 1:8
+            (y in (1:4)) ? lc = Ps.agriculture :
+                (x in (1:2)) ? lc = Ps.forest :
+                (y == 8) ? lc = Ps.builtup :
+                lc = Ps.grass
             landscape[x,y] = Pixel(lc, missing, [])
         end
     end
-- 
GitLab