### 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