Skip to content
Snippets Groups Projects
Select Git revision
  • e668f4ee38a0824350a3539bc8c0475b743d689a
  • master default protected
  • development
  • 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
20 results

skylark.jl

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    skylark.jl 11.73 KiB
    ### Persefone.jl - a model of agricultural landscapes and ecosystems in Europe.
    ###
    ### This file holds the code for the Eurasian Skylark (Alauda arvensis).
    ###
    
    """
        Skylark
    
    *Alauda arvensis* is a common and charismatic species of agricultural landscapes.
    At the moment, this implementation is still in development.
    
    **Sources:**
        - Bauer, H.-G., Bezzel, E., & Fiedler, W. (Eds.). (2012). Das Kompendium
          der Vögel Mitteleuropas: Ein umfassendes Handbuch zu Biologie, Gefährdung
          und Schutz (Einbändige Sonderausg. der 2., vollständig überarb. und erw.
          Aufl. 2005). AULA-Verlag
        - Delius, J. D. (1965). A Population Study of Skylarks Alauda Arvensis.
          Ibis, 107(4), 466–492. https://doi.org/10.1111/j.1474-919X.1965.tb07332.x
        - Donald et al. (2002). Survival rates, causes of failure and productivity
          of Skylark Alauda arvensis nests on lowland farmland. Ibis, 144(4), 652–664.
          https://doi.org/10.1046/j.1474-919X.2002.00101.x
        - Glutz von Blotzheim, Urs N. (Ed.). (1985). Handbuch der Vögel Mitteleuropas.
          Bd. 10. Passeriformes (Teil 1) 1. Alaudidae - Hirundidae. AULA-Verlag, Wiesbaden.
          ISBN 3-89104-019-9
        - Jenny, M. (1990). Territorialität und Brutbiologie der Feldlerche Alauda
          arvensis in einer intensiv genutzten Agrarlandschaft. Journal für Ornithologie,
          131(3), 241–265. https://doi.org/10.1007/BF01640998
        - Püttmanns et al. (2022). Habitat use and foraging parameters of breeding Skylarks
          indicate no seasonal decrease in food availability in heterogeneous farmland.
          Ecology and Evolution, 12(1), e8461. https://doi.org/10.1002/ece3.8461
    """
    @species Skylark begin
        # species parameters
        const movementrange::Length = 500m #XXX arbitrary
        const visionrange::Length = 200m #XXX arbitrary
        
        const eggtime::Int64 = 11 # days from laying to hatching
        const nestlingtime::UnitRange{Int64} = 7:11 # days from hatching to leaving nest
        const fledglingtime::UnitRange{Int64} = 25:30 # days from hatching to independence
    
        const eggpredationmortality::Float64 = 0.03 # per-day egg mortality from predation
        const nestharvestmortality::Float64 = 1.0 # egg/nestling mortality after a harvest event
        const nestlingpredationmortality::Float64 = 0.03 # per-day nestling mortality from predation
        const fledglingharvestmortality::Float64 = 0.5 # fledgling mortality after harvest
        const fledglingpredationmortality::Float64 = 0.01 # per-day fledgling mortality from predation
        const firstyearmortality::Float64 = 0.38 # total mortality in the first year after independence
        const migrationmortality::Float64 = 0.33 # chance of dying during the winter
    
        const minimumterritory = 5000m² # size of territory under ideal conditions
        const maxforageheight = 50cm # maximum preferred vegetation height for foraging
        const maxforagecover = 0.7 # maximum preferred vegetation cover for foraging
        const nestingheight = (15cm, 25cm) # min and max preferred vegetation height for nesting
        const nestingcover = (0.2, 0.5) # min and max preferred vegetation cover for nesting
        
        const nestingbegin::Tuple{Int64,Int64} = (April, 10) # begin nesting in the middle of April
        const nestbuildingtime::UnitRange{Int64} = 4:5 # 4-5 days needed to build a nest (doubled for first nest)
        const eggsperclutch::UnitRange{Int64} = 2:5 # eggs laid per clutch
        const breedingdelay::Int64 = 18 # days after hatching before starting a new brood #XXX ??
        const nestingend::Int64 = July # last month of nesting
    
        # individual variables
        #FIXME check what needs to be rewritten
        timer::Int64 = 0 # a count-down timer that can be used for different purposes
        migrationdates::Tuple = () # is defined by each individual in @create(Skylark)
        mate::Int64 = -1 # the agent ID of the mate (-1 if none)
        nest::Tuple = () # coordinates of current nest
        nestcompletion::Int64 = 0 # days left until the nest is built
        clutch::Int64 = 0 # number of offspring in current clutch
    end
    
    
    ## LIFE PHASES
    
    #TODO respect habitat when moving
    
    """
    Non-breeding adults move around with other individuals and check for migration.
    """
    @phase Skylark nonbreeding begin
        # flocking behaviour - follow a random neighbour or move randomly
        neighbours = @neighbours(self.visionrange) #XXX check for the closest neighbour(s)?
        isempty(neighbours) ?
            @walk("random", self.movementrange) :
            @follow(@rand(neighbours), 30m) #XXX magic number
        # check if the bird migrates
        leave, arrive = self.migrationdates
        month, day = monthday(model.date)
        migrate = (((month < arrive[1]) || (month == arrive[1] && day < arrive[2])) ||
                   ((month > leave[1]) || (month == leave[1] && day >= leave[2])))
        if migrate
            @kill(self.migrationmortality, "migration")
            returndate = Date(year(model.date)+1, arrive[1], arrive[2])
            self.sex == male ?
                @setphase(territorysearch) :
                @setphase(matesearch)
            @migrate(returndate)
        end
    end
    
    """
    Males returning from migration move around to look for suitable habitats to establish a territory.
    """
    @phase Skylark territorysearch begin
        #TODO
        #TODO Standorttreue
        # If we've found a territory, or the breeding season is over, move to the next phase
        if !isempty(self.territory)
            @setphase(occupation)
        elseif month(model.date) > self.nestingend
            @setphase(nonbreeding)
        else
            @walk("random", self.movementrange)
            #FIXME MethodError: no method matching walk!(::Persefone.Skylark, ::AgricultureModel, ::Tuple{UnitRange{Int64}, UnitRange{Int64}})
        end
    end
    
    """
    Once a male has found a territory, he remains in it until the breeding season is over,
    adjusting it to new conditions when and as necessary.
    """
    @phase Skylark occupation begin
        #move to a random location in the territory
        @move(@rand(self.territory))
        #TODO adjust territory as needed
    end
    
    """
    Females returning from migration move around to look for a suitable partner with a territory.
    """
    @phase Skylark matesearch begin
        #TODO move around, looking for a male with an established territory
        #TODO teilweise Partnertreue
    
        # look for a mate among the neighbouring birds, or move randomly
        for n in @neighbours(self.visionrange)
            if n.sex == male && n.mate == -1 && n.phase == occupation
                self.mate = n.id
                n.mate = self.id
                @debug "$(animalid(self)) and $(animalid(n)) have mated."
                break
            end
        end
        #@debug("$(animalid(self)) didn't find a mate.")
        if self.mate != -1
            @setphase(nesting)
        elseif month(model.date) > self.nestingend # stop trying to find a mate if it's too late
            @setphase(nonbreeding)
        else
            @walk("random", self.movementrange)
        end
    end
    
    """
    Females that have found a partner build a nest and lay eggs in a suitable location.
    """
    @phase Skylark nesting begin
        if model.date < Date(year(model.date), self.nestingbegin...)
            # wait for nesting to begin, moving around in the territory
            @move(@rand(@animal(self.mate).territory))
        elseif isempty(nest)
            # choose site, build nest & lay eggs
            for pos in @shuffle!(deepcopy(@animal(self.mate).territory))
                #TODO is this condition correct? -> needs validation!
                if (@landcover() == grass || @landcover() == soil ||
                    (@landcover() == agriculture &&
                     (self.nestingheight[1] <= @cropheight() <= self.nestingheight[2]) &&
                     (self.nestingcover[1] <= @cropcover() <= self.nestingcover[2])))
                    @move(pos)
                    self.nest = pos
                    self.clutch = @rand(self.eggsperclutch)
                    timer = self.nestbuildingtime + self.clutch # time to build + 1 day per egg laid
                    if month(model.date) == self.nestingbegin[1] 
                        # the first nest takes twice as long to build
                        #XXX this may affect the first two nests
                        timer += self.nestbuildingtime
                    end
                    break
                end
            end
            isempty(nest) && @warn("$(animalid(self)) didn't find a nesting location.")
        elseif timer == 0
            @debug("$(animalid(self)) has laid $(self.clutch) eggs.")
            @setphase(breeding)
        else
            self.timer -= 1
        end
        # tillage and harvest destroys the nest
        @respond(tillage, nest = ())
        @respond(harvesting, nest = ())
    end
    
    """
    Females that have laid eggs take care of their chicks, restarting the nesting process once the
    chicks are independent or in case of brood loss.
    """
    @phase Skylark breeding begin
        #TODO wait for eggs to hatch & chicks to mature, checking for mortality
        # restart breeding cycle if there is time
        if clutch == 0 && month(model.date) <= self.nestingend
            @setphase(nesting) #TODO breeding delay?
        elseif month(model.date) > self.nestingend
            @setphase(nonbreeding)
        end
    end
    
    
    ## SUPPORTING FUNCTIONS
    
    """
        migrationdates(skylark, model)
    
    Select the dates on which this skylark will leave for / return from its migration,
    based on observed migration patterns.
    """
    function migrationdates(skylark::Animal, model::SimulationModel)
        #TODO this ought to be temperature-dependent and dynamic
        minleave = skylark.sex == female ? (September, 15) : (October, 1)
        minarrive = skylark.sex == male ? (February, 15) : (March, 1)
        deltaleave = @rand(0:45) #XXX ought to be normally distributed
        deltaarrive = @rand(0:15) #XXX ought to be normally distributed
        leave = monthday(Date(2001, minleave[1], minleave[2]) + Day(deltaleave))
        arrive = monthday(Date(2001, minarrive[1], minarrive[2]) + Day(deltaarrive))
        (leave, arrive)
    end
    
    """
        foragequality(maxcover, maxheight, position, model)
    
    Calculate the relative quality of the habitat at this position for foraging.
    (Approximated from Püttmanns et al., 2021; Jeromin, 2002; Jenny, 1990b.)
    """
    function foragequality(maxcover::Float64, maxheight::Float64, position::Tuple{Int64,Int64},
                           model::SimulationModel)
        #TODO this is a key function that needs to be validated thoroughly
        px = model.landscape[position...]
        !(px.landcover in (grass, soil, agriculture)) && return 0.0
        quality = 1.0
        if !is.missing(px.fieldid)
            f = model.farmplots[px.fieldid]
            groundcoverfactor = x -> bounds(-1/maxcover + 1.0, max=1.0)
            plantheightfactor = x -> bounds(-1/maxheight + 1.0, max=1.0)
            #FIXME need percentage cover, not LAI
            quality = bounds(groundcoverfactor(f.LAItotal) + plantheightfactor(f.height), max=1.0)
        else
            @warn "No field assigned to location $position" px
        end
        return quality
    end
    
    
    ## INITIALISATION
    
    """
    Initialise a skylark individual. Selects migration dates and checks if the bird
    should currently be on migration. Also sets other individual-specific variables.
    """
    @create Skylark begin
        @debug "Added $(animalid(self)) at $(self.pos)"
        self.migrationdates = migrationdates(self, model) #XXX should be dynamic
        leave, arrive = self.migrationdates
        mon, day = monthday(model.date)
        migrate = (((mon < arrive[1]) || (mon == arrive[1] && day < arrive[2])) ||
                   ((mon > leave[1]) || (mon == leave[1] && day >= leave[2])))
        if migrate
            returndate = Date(year(model.date), arrive[1], arrive[2])
            model.date != @param(core.startdate) && (returndate += Year(1))
            self.sex == male ?
                @setphase(territorysearch) :
                @setphase(matesearch)
            @migrate(returndate)
        end
    end
    
    @populate Skylark begin
        # initialise on open land, at least 60m from vertical structures
        habitat = @habitat((@landcover() == grass || @landcover() == agriculture) &&
                           @distanceto(forest) >= 60m && @distanceto(builtup) >=60m)
        initphase = nonbreeding
        birthphase = nonbreeding
        indarea = 3ha
        pairs = true
    end