From fd279720f5458f5c6c389a5b445d948f3e97b4ea Mon Sep 17 00:00:00 2001
From: Daniel Vedder <daniel.vedder@idiv.de>
Date: Fri, 13 Jan 2023 09:50:36 +0100
Subject: [PATCH] Simplified phase handling in @species

---
 src/core/input.jl         |  3 ++
 src/nature/nature.jl      | 90 +++++++++++++++++++++++----------------
 src/nature/populations.jl |  3 ++
 3 files changed, 59 insertions(+), 37 deletions(-)

diff --git a/src/core/input.jl b/src/core/input.jl
index 7009844..318a71d 100644
--- a/src/core/input.jl
+++ b/src/core/input.jl
@@ -61,6 +61,9 @@ function getsettings(configfile::String, seed::Union{Int64,Nothing}=nothing)
         outdir = defaultoutdir*"_"*string(Dates.today())*"_s"*string(settings["core"]["seed"])
         settings["core"]["outdir"] = outdir
     end
+    if settings["core"]["startdate"] > settings["core"]["enddate"]
+        Base.error("Enddate is earlier than startdate.")
+    end
     settings
 end
 
diff --git a/src/nature/nature.jl b/src/nature/nature.jl
index 30370f7..caa5018 100644
--- a/src/nature/nature.jl
+++ b/src/nature/nature.jl
@@ -17,11 +17,10 @@ by trait dictionaries passed by them during initialisation.
 """
 @agent Animal GridAgent{2} begin
     #XXX is it (performance-)wise to use a dict for the traits?
-    # Doesn't this rather obviate the point of having an agent struct?
+    # Doesn't that rather obviate the point of having an agent struct?
     traits::Dict{String,Any}
     sex::Sex
     age::Int32
-    #XXX keep track of parents and/or offspring?
 end
 
 """
@@ -71,7 +70,7 @@ custom syntax to describe species' biology:
 ```julia
 @species name begin
 
-    @initialise!(phase1, @habitat(...))
+    @initialise!(@habitat(...))
     speciesvar1 = 3.14
     ...
 
@@ -95,6 +94,7 @@ macro species(name, body)
     quote
         Core.@__doc__ function $(esc(name))($(esc(:model))::AgentBasedModel)
             $(esc(:name)) = string($(QuoteNode(name)))
+            $(esc(:phase)) = ""
             $(esc(body))
             vardict = Base.@locals
             speciesdict = Dict{String,Any}()
@@ -107,20 +107,17 @@ macro species(name, body)
 end
 
 """
-    @initialise!(phase, habitatdescriptor; kwargs...)
+    @initialise!(habitatdescriptor; kwargs...)
 
-Call this macro within the body of `@species`. It sets the phase that each individual
-of this species will be assigned at birth, and passes the given habitat descriptor
+Call this macro within the body of `@species`. It passes the given habitat descriptor
 function and keyword arguments on to `initpopulation()` when setting up the simulation.
 
-Note: if this macro is not used, `phase` and `initialise!` must be set manually in
-the species definition.
+Note: if this macro is not used, the variable `initialise!` must be set manually in the
+species definition.
 """
-macro initialise!(phase, habitatdescriptor, kwargs...)
-    quote
-        $(esc(:phase)) = String($phase)
-        $(esc(:initialise!)) = initpopulation($habitatdescriptor, $(kwargs...))
-    end
+macro initialise!(habitatdescriptor, kwargs...)
+    #FIXME does the habitatdescriptor need to be `esc`aped due to scoping issues?
+    :($(esc(:initialise!)) = initpopulation($habitatdescriptor, $(kwargs...)))
 end
 
 """
@@ -145,47 +142,64 @@ variables:
     information).
 
 Several utility macros can be used within the body of `@phase` as a short-hand for
-common expressions: `@respond`, `@trait`, `@here`, `@kill`, `@reproduce`, `@neighbours`.
+common expressions: `@trait`, `@changephase`, `@respond`, `@here`, `@kill`,
+`@reproduce`, `@neighbours`.
 
-To transition an individual to another phase, simply redefine its phase variable:
-`@trait(phase) = "newphase"`.
+Note that the first phase that is defined in a species definition block will be
+the phase that animals are assigned at birth, unless the variable `phase` is
+explicitly defined by the user in the species definition block.
 """
 macro phase(name, body)
     #XXX make this documentable?
     #FIXME Somehow, errors in the phase body are not shown?
     quote
-        $(esc(name)) = function(animal::Animal, model::AgentBasedModel)
-            $(esc(:pos)) = animal.pos
-            $body
+        $(esc(name)) = function($(esc(:animal))::Animal, $(esc(:model))::AgentBasedModel)
+            $(esc(:pos)) = $(esc(:animal)).pos
+            #@debug "Executing phase "*$(String(name))*":\n"*$(esc(body))
+            $(esc(body)) #FIXME isn't being executed
         end
+        ($(esc(:phase)) == "") && ($(esc(:phase)) = $(String(name)))
     end
 end
 
 """
-    @respond(eventname, body)
+    @trait(traitname)
 
-Define how an animal responds to a landscape event that affects its current position.
+A utility macro to quickly access an animal's trait value.
 This can only be used nested within `@phase`.
 """
-macro respond(eventname, body)
-    quote
-        if $(esc(eventname)) in @here(events)
-            $body
-        end
+macro trait(traitname)
+    #XXX This would error if called in the first part of a species definition block
+    # (i.e. outside of a @phase block). Although this is specified in the documentation,
+    # it is unexpected and liable to be overlooked. Can we add a third clause to
+    # compensate for that?
+    if traitname in fieldnames(Animal)
+        :($(esc(:animal)).$(traitname))
+    else
+        :($(esc(:animal)).traits[string($(QuoteNode(traitname)))])
     end
 end
 
 """
-    @trait(traitname)
+    @changephase(newphase)
 
-A utility macro to quickly access an animal's trait value.
+Switch this animal over to a different phase. This can only be used nested within `@phase`.
+"""
+macro changephase(newphase)
+    :($(esc(:animal)).traits["phase"] = $(String(newphase)))
+end
+
+"""
+    @respond(eventname, body)
+
+Define how an animal responds to a landscape event that affects its current position.
 This can only be used nested within `@phase`.
 """
-macro trait(traitname)
-    if traitname in fieldnames(Animal)
-        :(animal.$(traitname))
-    else
-        :(animal.traits[string($(QuoteNode(traitname)))])
+macro respond(eventname, body)
+    quote
+        if $(esc(eventname)) in @here(events)
+            $(esc(body))
+        end
     end
 end
 
@@ -196,7 +210,7 @@ A utility macro to quickly access a property of the animal's current position.
 This can only be used nested within `@phase`.
 """
 macro here(property)
-    :(model.landscape[animal.pos...].$(property))
+    :($(esc(:model)).landscape[$(esc(:animal)).pos...].$(property))
 end
 
 """
@@ -206,7 +220,7 @@ Kill this animal. This is a thin wrapper around `kill!()`, and passes on any arg
 This can only be used nested within `@phase`.
 """
 macro kill(args...)
-    :(kill!(animal, model, $(args...)))
+    :(kill!($(esc(:animal)), $(esc(:model)), $(args...)))
 end
 
 """
@@ -216,7 +230,7 @@ Let this animal reproduce. This is a thin wrapper around `reproduce!()`, and pas
 any arguments. This can only be used nested within `@phase`.
 """
 macro reproduce(args...)
-    :(reproduce!(animal, model, $(args...)))
+    :(reproduce!($(esc(:animal)), $(esc(:model)), $(args...)))
 end
 
 """
@@ -226,7 +240,7 @@ Return an iterator over all animals in the given radius around this animal, excl
 This can only be used nested within `@phase`.
 """
 macro neighbours(radius)
-    :(nearby_animals(animal, model, $radius))
+    :(nearby_animals($(esc(:animal)), $(esc(:model)), $radius))
 end
 
 """
@@ -328,3 +342,5 @@ This is a utility wrapper that can only be used nested within `@phase` or `@habi
 macro countanimals(speciesname, radius=0)
     :(countanimals($(esc(:pos)), $(esc(:model)), $speciesname, $radius))
 end
+
+##TODO add movement macros
diff --git a/src/nature/populations.jl b/src/nature/populations.jl
index fb66d1c..ce1d793 100644
--- a/src/nature/populations.jl
+++ b/src/nature/populations.jl
@@ -136,8 +136,11 @@ Count the number of animals of the given species in this location (optionally su
 function countanimals(pos::Tuple{Int64,Int64}, model::AgentBasedModel,
                       speciesname::String, radius::Int64=0)
     n = 0
+    #XXX can we ignore capitalisation in the spelling of `speciesname`?
     for a in nearby_animals(pos, model, radius)
         a.traits["name"] == speciesname && (n += 1)
     end
     return n
 end
+
+##TODO add movement functions
-- 
GitLab