Skip to content
Snippets Groups Projects
Commit f61ba894 authored by xo30xoqa's avatar xo30xoqa
Browse files

Wrote @habitat and associated macros

Not yet functional - the necessary functions still need to be implemented.
parent dbb3c9a5
Branches
No related tags found
No related merge requests found
......@@ -101,16 +101,41 @@ end
Return the land cover class at this position (utility wrapper).
"""
function landcover(model::AgentBasedModel, pos::Tuple{Int64,Int64})
function landcover(pos::Tuple{Int64,Int64}, model::AgentBasedModel)
model.landscape[pos...].landcover
end
"""
farmplot(model, position)
Return the farm plot at this position (utility wrapper).
Return the farm plot at this position, or nothing if there is none (utility wrapper).
"""
function farmplot(model::AgentBasedModel, pos::Tuple{Int64,Int64})
model[model.landscape[pos...].fieldid]
function farmplot(pos::Tuple{Int64,Int64}, model::AgentBasedModel)
ismissing(model.landscape[pos...].fieldid) ? nothing :
model[model.landscape[pos...].fieldid]
end
"""
distanceto(model, pos, habitattype)
Calculate the distance from the given location to the closest habitat of the specified type.
Caution: can be computationally expensive!
"""
function distanceto(pos::Tuple{Int64,Int64}, model::AgentBasedModel, habitattype::LandCover)
#XXX can I make this check for both land cover type and crop type?
# (Or even take a full habitat descriptor?)
#TODO
end
"""
distancetoedge(model, pos)
Calculate the distance from the given location to the closest neighbouring habitat.
Caution: can be computationally expensive!
"""
function distancetoedge(pos::Tuple{Int64,Int64}, model::AgentBasedModel)
lc = landcover(model, pos)
dist = 1
#TODO
end
......@@ -3,6 +3,9 @@
### This file is responsible for managing the crop growth modules.
###
"The crop types simulated by the model"
@enum CropType fallow wheat barley maize rapeseed
#XXX not sure whether it makes sense to have this as an agent type,
# or perhaps better a grid property?
"""
......@@ -11,9 +14,9 @@
This represents one field, i.e. a collection of pixels with the same management.
This is the spatial unit with which the crop growth model and the farm model work.
"""
@agent FarmPlot NoSpaceAgent begin
@agent FarmPlot GridAgent{2} begin
pixels::Vector{Tuple{Int64, Int64}}
croptype::String
croptype::CropType
height::Float64
biomass::Float64
#TODO
......@@ -49,7 +52,7 @@ function initfields!(model::AgentBasedModel)
model.landscape[x,y].fieldid = objectid
push!(model[objectid].pixels, (x,y))
else
fp = add_agent!(FarmPlot, model, [(x,y)], "fallow", 0.0, 0.0)
fp = add_agent!(FarmPlot, model, [(x,y)], fallow, 0.0, 0.0)
model.landscape[x,y].fieldid = fp.id
convertid[rawid] = fp.id
n += 1
......@@ -86,5 +89,25 @@ function averagefieldsize(model::AgentBasedModel)
push!(sizes, size(a.pixels)[1]/conversionfactor)
end
round(sum(sizes)/size(sizes)[1], digits=2)
#sizes
end
"""
croptype(model, position)
Return the crop at this position, or nothing if there is no crop here (utility wrapper).
"""
function croptype(pos::Tuple{Int64,Int64}, model::AgentBasedModel)
ismissing(model.landscape[pos...].fieldid) ? nothing :
model[model.landscape[pos...].fieldid].croptype
end
"""
cropheight(model, position)
Return the height of the crop at this position, or nothing if there is no crop here
(utility wrapper).
"""
function cropheight(pos::Tuple{Int64,Int64}, model::AgentBasedModel)
ismissing(model.landscape[pos...].fieldid) ? nothing :
model[model.landscape[pos...].fieldid].height
end
......@@ -25,7 +25,9 @@ function initrandompopulation(popsize::Union{Int64,Float64}, asexual::Bool=true)
return initfunc
end
#TODO initpopulation
#TODO initpopulation with habitat descriptor
#TODO initpopulation with dispersal from an original source
#TODO initpopulation based on known occurences in real-life
"""
reproduce!(animal, model, n=1)
......@@ -57,3 +59,16 @@ function kill!(animal::Animal, model::AgentBasedModel, probability::Float64=1.0,
end
return false
end
"""
countanimals(model, pos, speciesname)
Count the number of animals of the given species in this location.
"""
macro countanimals(model::AgentBasedModel, pos::Tuple{Int64,Int64}, speciesname::String)
n = 0
for a in nearby_ids(pos, model, 0)
typeof(model[a]) == Animal && model[a].traits.name == speciesname && (n += 1)
end
return n
end
......@@ -3,6 +3,9 @@
### This file is responsible for managing the animal modules.
###
### FUNCTIONS AND TYPES INTEGRATING THE NATURE MODEL WITH THE REST OF PERSEPHONE
## An enum used to assign a sex to each animal
@enum Sex hermaphrodite male female
......@@ -30,6 +33,31 @@ function animalid(a::Animal)
return "$(a.traits["name"]) $(a.id)"
end
"""
stepagent!(animal, model)
Update an animal by one day, executing it's currently active phase function.
"""
function stepagent!(animal::Animal, model::AgentBasedModel)
animal.age += 1
animal.traits[animal.traits["phase"]](animal,model)
end
"""
initnature!(model)
Initialise the model with all simulated animal populations.
"""
function initnature!(model::AgentBasedModel)
# The config file determines which species are simulated in this run
for speciesname in param("nature.targetspecies")
species = @eval $(Symbol(speciesname))($model)
species["initialise!"](species, model)
end
# Initialise the data output
initecologicaldata()
end
### MACROS IMPLEMENTING THE DOMAIN-SPECIFIC LANGUAGE FOR DEFINING SPECIES
......@@ -104,7 +132,7 @@ 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`
common expressions: `@respond`, `@trait`, `@here`, `@kill`, `@reproduce`.
To transition an individual to another phase, simply redefine its phase variable:
`@trait(phase) = "newphase"`.
......@@ -122,7 +150,6 @@ This can only be used nested within `@phase`.
"""
macro respond(eventname, body)
quote
#TODO test this
if $(esc(eventname)) in @here(events)
$body
end
......@@ -175,30 +202,105 @@ 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
"""
@habitat
Specify habitat suitability for spatial ecological processes.
### FUNCTIONS INTEGRATING THE NATURE MODEL WITH THE REST OF PERSEPHONE
This macro works by creating an anonymous function that takes in a model object
and a position, and returns `true` or `false` depending on the conditions
specified in the macro body.
Several utility macros can be used within the body of `@habitat` as a short-hand for
common expressions: `@landcover`, `@croptype`, `@cropheight`, `@distanceto`,
`@distancetoedge`, `@countanimals`. The variables `model` and `pos` can be used
for checks that don't have a macro available.
Two example uses of `@habitat` might look like this:
```julia
movementhabitat = @habitat(@landcover() in (grass agriculture soil))
nestinghabitat = @habitat((@landcover() == grass ||
(@landcover() == agriculture && @croptype() != maize &&
@cropheight() < 10)) &&
@distanceto(forest) > 20)
```
For more complex habitat suitability checks, the use of this macro can be
circumvented by directly creating an equivalent function.
"""
stepagent!(animal, model)
macro habitat(body)
quote
function($(esc(:pos)), $(esc(:model)))
if $(esc(body))
return true
else
return false
end
end
end
end
Update an animal by one day, executing it's currently active phase function.
"""
function stepagent!(animal::Animal, model::AgentBasedModel)
animal.age += 1
animal.traits[animal.traits["phase"]](animal,model)
@landcover
Returns the local landcover. This is a utility wrapper that can only be used
nested within `@habitat`.
"""
macro landcover()
:(landcover($(esc(:pos)), $(esc(:model))))
end
"""
initnature!(model)
@croptype
Initialise the model with all simulated animal populations.
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`.
"""
function initnature!(model::AgentBasedModel)
# The config file determines which species are simulated in this run
for speciesname in param("nature.targetspecies")
species = @eval $(Symbol(speciesname))($model)
species["initialise!"](species, model)
end
# Initialise the data output
initecologicaldata()
macro croptype()
:(croptype($(esc(:pos)), $(esc(:model))))
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`.
"""
macro cropheight()
:(cropheight($(esc(:pos)), $(esc(:model))))
end
"""
@distanceto(habitattype)
Calculate the distance to the closest habitat of the specified type.
This is a utility wrapper that can only be used nested within `@habitat`.
"""
macro distanceto(habitattype)
:(distanceto($(esc(:pos)), $(esc(:model)), $habitattype))
end
"""
@distancetoedge
Calculate the distance to the closest neighbouring habitat.
This is a utility wrapper that can only be used nested within `@habitat`.
"""
macro distancetoedge()
:(distancetoedge($(esc(:pos)), $(esc(:model))))
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`.
"""
macro countanimals(speciesname)
#XXX this also counts the enquiring agent
:(countanimals($(esc(:pos)), $(esc(:model)), $speciesname))
end
......@@ -4,7 +4,7 @@
###
##XXX At the moment, this is just a skeleton to show what I want to be able to interpret
## with the @species and @phase macros
## with the @species, @phase, and @habitat macros (and their helper macros)
@species Skylark begin
......@@ -16,6 +16,11 @@
initialise! = initrandompopulation(popsize)
phase = "egg"
habitats = @habitat((@landcover() == grass ||
(@landcover() == agriculture && @croptype() != maize &&
@cropheight() < 10)) &&
@distanceto(forest) > 20)
@phase egg begin
@kill(@trait(eggpredationmortality), "predation")
......
......@@ -3,4 +3,8 @@
### These are the tests for the nature model (excluding individual species).
###
@testset "Species macros" begin
#TODO
end
#TODO
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment