Skip to content
Snippets Groups Projects
Select Git revision
  • f61ba8948d6a16987f6c477f62551b9143ab1732
  • master default protected
  • development
  • marco/aquacrop-regional-params
  • precompile-statements
  • precompile-tools
  • tmp-faster-loading
  • skylark
  • testsuite
  • code-review
  • v0.7.0
  • v0.6.1
  • v0.6.0
  • v0.5.5
  • v0.5.4
  • v0.5.3
  • v0.5.2
  • v0.2
  • v0.3.0
  • v0.4.1
  • v0.5
21 results

nature.jl

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    nature.jl 8.92 KiB
    ### Persephone - a socio-economic-ecological model of European agricultural landscapes.
    ###
    ### 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
    
    """
        Animal
    
    This is the generic agent type for all animals. Species are differentiated
    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?
        traits::Dict{String,Any}
        sex::Sex
        age::Int32
        #XXX keep track of parents and/or offspring?
    end
    
    """
        animalid(animal)
    
    A small utility function to return a string with the species name and ID of an animal.
    """
    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
    
    """
        @species(name, body)
    
    A macro used to create new species definitions for the nature model.
    This is effectively a simple domain-specific language, establishing a
    custom syntax to describe species' biology:
    
    ```julia
    @species name begin
        initialise! = initpopulation()
        phase = "phase1"
        ...
        @phase phase1 begin
            ...
        end
    end
    ```
    
    The definition body (enclosed in the begin/end block) has two sections.
    First comes a list of species-specific parameters, which are assigned
    just like normal variables. Second come one or more phase definitions,
    that describe the behaviour of the species during various parts of its
    life cycle (see the documentation to `@phase` for details).
    
    There are two parameters that all species must define:
    - `initialise!` should specify a function that will be used to create
        the starting population for this species. This function must take
        two arguments, a species dict and an `AgentBasedModel` object.
        The easiest way to create this function is by using `initpopultion()`.
    - `phase` should be a string specifying the name of the first phase
        that individuals of this species will be assigned to on birth.
    
    Access to the rest of the model is given by the `model` variable (an object
    of type `AgentBasedModel`).
    """
    macro species(name, body)
        quote
            Core.@__doc__ function $(esc(name))(model::AgentBasedModel)
                $(esc(:name)) = string($(QuoteNode(name)))
                $(esc(body))
                vardict = Base.@locals
                speciesdict = Dict{String,Any}()
                for k in keys(vardict)
                    speciesdict[string(k)] = vardict[k]
                end
                return speciesdict
            end
        end
    end
    
    """
        @phase(name, body)
    
    This macro is designed to be used within a species definition block (i.e. within
    the body of a call to `@species`).
    
    The idea behind this is that species show very different behaviour during different
    phases of their lives. Therefore, `@phase` can be used define the behaviour for one
    such phase, and the conditions under which the animal transitions to another phase.
    
    `@phase` works by creating a function that will be called by the model if the
    animal is in the relevant phase. When it is called, it has access to the following
    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).
    - `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`.
    
    To transition an individual to another phase, simply redefine its phase variable:
    `@trait(phase) = "newphase"`.
    """
    macro phase(name, body)
        #FIXME Somehow, errors in the phase body are not shown?
        :($(esc(name)) = function(animal::Animal, model::AgentBasedModel) $body end)
    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 respond(eventname, body)
        quote
            if $(esc(eventname)) in @here(events)
                $body
            end
        end
    end
    
    """
        @trait(traitname)
    
    A utility macro to quickly access an animal's trait value.
    This can only be used nested within `@phase`.
    """
    macro trait(traitname)
        if traitname in fieldnames(Animal)
            :(animal.$(traitname))
        else
            :(animal.traits[string($(QuoteNode(traitname)))])
        end
    end
    
    """
        @here(property)
    
    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))
    end
    
    """
        @kill
    
    Kill this animal. This is a thin wrapper around `kill!()`, and passes on any arguments.
    This can only be used nested within `@phase`.
    """
    macro kill(args...)
        :(kill!(animal, model, $(args...)))
    end
    
    """
        @reproduce
    
    Let this animal reproduce. This is a thin wrapper around `reproduce!()`, and passes on
    any arguments. This can only be used nested within `@phase`.
    """
    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
    
    """
        @habitat
    
    Specify habitat suitability for spatial ecological processes.
    
    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.
    """
    macro habitat(body)
        quote
            function($(esc(:pos)), $(esc(:model)))
                if $(esc(body))
                    return true
                else
                    return false
                end
            end
        end
    end
    
    """
        @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
    
    """
        @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`.
    """
    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