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