### Persefone.jl - a model of agricultural landscapes and ecosystems in Europe. ### ### This file holds the code for the Eurasian Skylark (Alauda arvensis). ### #XXX global variable/function skylarkhabitat = @habitat((@landcover() == grass || (@landcover() == agriculture && @cropname() != "maize")) && @distancetoedge() > 50m) #XXX this ought to check for distance to forest and builtup, # but that's very expensive (see below) # @distanceto(forest) > 5 && # at least 50m from forest edges # @distanceto(builtup) > 5) # and from anthropogenic structures """ 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 - 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 """ @species Skylark begin # species parameters 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 = 0.9 # egg/nestling mortality after a harvest event (XXX guess) 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 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 const nestingend::Int64 = July # last month of nesting const habitats::Function = skylarkhabitat # individual variables daystonextphase::Int64 = 0 # days remaining until fledging or maturity migrationdates::Tuple = () # is defined by each individual in @create(Skylark) territory::Vector = [] # pixels that this skylark claims as its territory 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::Vector{Int64} = Vector{Int64}() # IDs of offspring in current clutch end """ As an egg, simply check for mortality and hatching. """ @phase Skylark egg begin @kill(self.eggpredationmortality, "predation") @respond(harvesting, @kill(self.nestharvestmortality, "harvest")) if self.age == self.eggtime self.daystonextphase = @rand(self.nestlingtime) @setphase(nestling) end end """ As a nestling, simply check for mortality and fledging. """ @phase Skylark nestling begin #TODO add feeding & growth @kill(self.nestlingpredationmortality, "predation") @respond(harvesting, @kill(self.nestharvestmortality, "harvest")) # if self.age == self.nestlingtime+self.eggtime # @setphase(fledgling) # end if self.daystonextphase == 0 self.daystonextphase = @rand(self.fledglingtime) @setphase(fledgling) else self.daystonextphase -= 1 end end """ As a fledgling, move around a little, but mainly wait for maturity and check mortality. """ @phase Skylark fledgling begin #TODO add feeding & growth @kill(self.fledglingpredationmortality, "predation") @walk("random", 10m) #TODO add movement following the parents # if self.age == self.fledglingtime+self.eggtime # @kill(self.firstyearmortality, "first year mortality") #XXX mechanistic? # @setphase(nonbreeding) # end if self.daystonextphase == 0 @kill(self.firstyearmortality, "first year mortality") #XXX mechanistic? @setphase(nonbreeding) else self.daystonextphase -= 1 end end """ As a non-breeding adult, move around with other individuals and check for migration. """ @phase Skylark nonbreeding begin # flocking behaviour - follow a random neighbour or move randomly #TODO add feeding and mortality, respect habitat when moving neighbours = @neighbours(100m) #XXX magic number isempty(neighbours) ? @walk("random", 50m) : @follow(@rand(neighbours), 20m) # 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 #FIXME not all migrate? @kill(self.migrationmortality, "migration") returndate = Date(year(model.date)+1, arrive[1], arrive[2]) @setphase(mating) @migrate(returndate) end end """ Move around until a mate is found. """ @phase Skylark mating begin #TODO mortality and feeding #TODO territoriality # if we've found a mate, wait for nesting begin and then go to the next phase if self.mate != -1 if !@isalive(self.mate) self.mate = -1 return end month, day = monthday(model.date) nest = ((month == self.nestingbegin[1] && day >= self.nestingbegin[2] && @chance(0.05)) || (month > self.nestingbegin[1])) #XXX why the chance? nest && @setphase(nestbuilding) return end # look for a mate among the neighbouring birds, or move randomly for n in @neighbours(500m) #XXX magic number if n.sex != self.sex && n.phase == mating && n.mate == -1 self.mate = n.id n.mate = self.id @debug "$(animalid(self)) and $(animalid(n)) have mated." return end end #@debug("$(animalid(self)) didn't find a mate.") @walk("random", 100m) #XXX magic number end """ Females select a location and build a nest. Males do nothing. (Sound familiar?) """ @phase Skylark nestbuilding begin #TODO mortality and feeding if !@isalive(self.mate) self.mate = -1 @setphase(nonbreeding) return end if self.sex == female if isempty(self.nest) # try to find a nest in the neighbourhood, or move on nestlocation = @randompixel(100m, self.habitats) #XXX magic number if isnothing(nestlocation) @walk("random", 200m) #XXX magic number else # if we've found a location, start the clock on the building time # (building time doubles for the first nest of the year) self.nest = nestlocation self.nestcompletion = @rand(self.nestbuildingtime) month(model.date) == self.nestingbegin[1] && (self.nestcompletion *= 2) @debug "$(animalid(self)) is building a nest." end else # wait while nest is being built, then lay eggs and go to next phase if self.nestcompletion > 0 self.nestcompletion -= 1 else #XXX more accurately, a female lays one egg per day, not all at once @reproduce(@rand(self.eggsperclutch),self.mate) @setphase(breeding) end end else # males stay near the female mate = @animal(self.mate) @follow(mate, 50m) mate.phase == breeding && @setphase(breeding) end end """ Do lots of foraging (not yet implemented). """ @phase Skylark breeding begin #TODO forage (move inside the territory) for offspring in self.clutch # check if offspring are still alive and juvenile, else remove from clutch if !@isalive(offspring) || @animal(offspring).phase == nonbreeding filter!(x -> x != offspring, self.clutch) end end # if all young have fledged, move to nonbreeding (if it's July) or breed again if isempty(self.clutch) self.nest = () if month(model.date) >= self.nestingend self.territory = [] @setphase(nonbreeding) else @setphase(nestbuilding) end end end """ 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 """ 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)" # calculate migration dates for this individual self.migrationdates = migrationdates(self, model) 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 returndate = Date(year(model.date), arrive[1], arrive[2]) model.date != @param(core.startdate) && (returndate += Year(1)) @migrate(returndate) end #TODO other stuff? end @populate Skylark begin habitat = skylarkhabitat initphase = mating birthphase = egg indarea = 3ha pairs = true end