diff --git a/src/nature/nature.jl b/src/nature/nature.jl index 4003ed1b4d05091003d3c49e856e2678685db8ac..bae14283b4809845a8caf4fd5e116f65991fd36c 100644 --- a/src/nature/nature.jl +++ b/src/nature/nature.jl @@ -126,13 +126,14 @@ animal is in the relevant phase. When it is called, it has access to the followi variables: - `animal` a reference to the animal itself. This provides access to `animal.age`, `animal.sex`, and `animal.traits` (a dict that gives access to all species parameters). +- `pos` gives the animal's current position as a coordinate tuple. - `model` a reference to the model world (an object of type `AgentBasedModel`). This allows access to `model.date` (the current simulation date) and `model.landscape` (a two-dimensional array of pixels containing geographic information). Several utility macros can be used within the body of `@phase` as a short-hand for -common expressions: `@respond`, `@trait`, `@here`, `@kill`, `@reproduce`. +common expressions: `@respond`, `@trait`, `@here`, `@kill`, `@reproduce`, `@neighbours`. To transition an individual to another phase, simply redefine its phase variable: `@trait(phase) = "newphase"`. @@ -140,7 +141,12 @@ To transition an individual to another phase, simply redefine its phase variable macro phase(name, body) #XXX make this documentable? #FIXME Somehow, errors in the phase body are not shown? - :($(esc(name)) = function(animal::Animal, model::AgentBasedModel) $body end) + quote + $(esc(name)) = function(animal::Animal, model::AgentBasedModel) + $(esc(:pos)) = animal.pos + $body + end + end end """ @@ -201,9 +207,15 @@ macro reproduce(args...) :(reproduce!(animal, model, $(args...))) end -#XXX add a macro @f to call a function with animal and model as first parameters? -# e.g. @f(nearby_agents, distance) -# -> rather create wrapper macros +""" + @neighbours(radius) + +Return an iterator over all animals in the given radius around this animal, excluding itself. +This can only be used nested within `@phase`. +""" +macro neighbours(radius) + :(nearby_animals(animal, model, $radius)) +end """ @habitat @@ -236,11 +248,7 @@ circumvented by directly creating an equivalent function. macro habitat(body) quote function($(esc(:pos)), $(esc(:model))) - if $(esc(body)) - return true - else - return false - end + ($(esc(body))) ? return true : return false end end end @@ -249,7 +257,7 @@ end @landcover Returns the local landcover. This is a utility wrapper that can only be used -nested within `@habitat`. +nested within `@phase` or `@habitat`. """ macro landcover() :(landcover($(esc(:pos)), $(esc(:model)))) @@ -259,7 +267,7 @@ end @croptype Return the local croptype, or nothing if there is no crop here. -This is a utility wrapper that can only be used nested within `@habitat`. +This is a utility wrapper that can only be used nested within `@phase` or `@habitat`. """ macro croptype() :(croptype($(esc(:pos)), $(esc(:model)))) @@ -269,27 +277,27 @@ end @cropheight Return the height of the crop at this position, or 0 if there is no crop here. -This is a utility wrapper that can only be used nested within `@habitat`. +This is a utility wrapper that can only be used nested within `@phase` or `@habitat`. """ macro cropheight() :(cropheight($(esc(:pos)), $(esc(:model)))) end """ - @distanceto(habitattype) + @distanceto(habitat) -Calculate the distance to the closest habitat of the specified type. -This is a utility wrapper that can only be used nested within `@habitat`. +Calculate the distance to the closest habitat of the specified type or descriptor. +This is a utility wrapper that can only be used nested within `@phase` or `@habitat`. """ -macro distanceto(habitattype) - :(distanceto($(esc(:pos)), $(esc(:model)), $habitattype)) +macro distanceto(habitat) + :(distanceto($(esc(:pos)), $(esc(:model)), $habitat)) end """ @distancetoedge Calculate the distance to the closest neighbouring habitat. -This is a utility wrapper that can only be used nested within `@habitat`. +This is a utility wrapper that can only be used nested within `@phase` or `@habitat`. """ macro distancetoedge() :(distancetoedge($(esc(:pos)), $(esc(:model)))) @@ -299,7 +307,7 @@ end @countanimals(speciesname) Count the number of animals of the given species in this location. -This is a utility wrapper that can only be used nested within `@habitat`. +This is a utility wrapper that can only be used nested within `@phase` or `@habitat`. """ macro countanimals(speciesname) #XXX this also counts the enquiring agent diff --git a/src/nature/populations.jl b/src/nature/populations.jl index d08d40473c7f12b33dfcdd2215b412ffa4af39f8..28b9528ba4ea486116a9d9dac99eb5c910749977 100644 --- a/src/nature/populations.jl +++ b/src/nature/populations.jl @@ -60,14 +60,35 @@ function kill!(animal::Animal, model::AgentBasedModel, probability::Float64=1.0, end """ - countanimals(model, pos, speciesname) + nearby_animals(pos, model, radius) -Count the number of animals of the given species in this location. +Return an iterator over all animals in the given radius around this position. """ -macro countanimals(model::AgentBasedModel, pos::Tuple{Int64,Int64}, speciesname::String) +function nearby_animals(pos::Tuple{Int64,Int64}, model::AgentBasedModel, radius::Int64) + neighbours = (model[id] for id in nearby_ids(pos, model, radius)) + Iterators.filter(a -> typeof(a) == Animal, neighbours) +end + +""" + nearby_animals(animal, model, radius) + +Return an iterator over all animals in the given radius around this animal, excluding itself. +""" +function nearby_animals(animal::Animal, model::AgentBasedModel, radius::Int64) + neighbours = (model[id] for id in nearby_ids(animal.pos, model, radius)) + Iterators.filter(a -> typeof(a) == Animal && a.id != animal.id, neighbours) +end + +""" + countanimals(pos, model, speciesname, radius=0) + +Count the number of animals of the given species in this location (optionally supplying a radius). +""" +function countanimals(pos::Tuple{Int64,Int64}, model::AgentBasedModel, + speciesname::String, radius::Int64=0) n = 0 - for a in nearby_ids(pos, model, 0) - typeof(model[a]) == Animal && model[a].traits.name == speciesname && (n += 1) + for a in nearby_animals(pos, model, radius) + model[a].traits.name == speciesname && (n += 1) end return n end