diff --git a/src/nature/species/skylark.jl b/src/nature/species/skylark.jl index 5b0a2e82cab5360c61a1737f6c77c50b38c11322..50268688c19e2deee6d777b459d6cd514f3d697f 100644 --- a/src/nature/species/skylark.jl +++ b/src/nature/species/skylark.jl @@ -11,54 +11,195 @@ 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 + 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 - eggmaturationtime = 11 - eggharvestmortality = 0.9 #??? - eggpredationmortality = 0.1 #??? + eggtime = 11 # 11 days from laying to hatching + eggpredationmortality = 0.03 # per-day egg mortality from predation + nestharvestmortality = 0.9 # egg mortality after a harvest event (XXX guess) - migrationdates = () + nestlingtime = 7:11 # 7-11 days from hatching to leaving nest + nestlingpredationmortality = 0.03 # per-day nestling mortality from predation - habitats = @habitat((@landcover() == grass || + fledglingtime = 25:30 # 25-30 days from hatching to independence + fledglingharvestmortality = 0.5 # fledgling mortality after harvest + fledglingpredationmortality = 0.01 # per-day fledgling mortality from predation + firstyearmortality = 0.38 # total mortality in the first year after independence + + migrationdates = () # is defined by each individual in `initskylark()` + migrationmortality = 0.33 # chance of dying during the winter + + mate = -1 # the agent ID of the mate (-1 if none) + nest = () # coordinates of current nest + nestingbegin = (4, 10) # begin nesting in the middle of April + nestbuildingtime = 4:5 # 4-5 days needed to build a nest (doubled for first nest) + nestcompletion = 0 # days left until the nest is built + eggsperclutch = 2:5 # 2-5 eggs laid per clutch + clutch = [] # IDs of offspring in current clutch + breedingdelay = 18 # wait 18 days after hatching to start a new brood + + habitats = @habitat((@landcover() == grass || + # settle on grass or arable land (but not maize) (@landcover() == agriculture && @cropname() != "maize")) && - @distanceto(forest) > 5) + @distancetoedge() > 5) # at least 50m from other habitats + #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 - @initialise(habitats, pairs=true, initfunction=initskylark) + @initialise(habitats, popdensity=300, pairs=true, initfunction=initskylark) + """ + As an egg, simply check for mortality and hatching. + """ @phase egg begin @kill(@trait(eggpredationmortality), "predation") - @respond harvest @kill(@trait(eggharvestmortality), "harvest") - - #TODO dies if parents die (due to starvation?) + @respond(harvesting, @kill(@trait(nestharvestmortality), "harvest")) - if @trait(age) == 14 + if @trait(age) == @trait(eggtime) @setphase(nestling) end end + """ + As a nestling, simply check for mortality and fledging. + """ @phase nestling begin - #TODO + #TODO add feeding & growth + @kill(@trait(nestlingpredationmortality), "predation") + @respond(harvesting, @kill(@trait(nestharvestmortality), "harvest")) + if @trait(age) == @trait(nestlingtime)+@trait(eggtime) + @setphase(fledgling) + end end - @phase breeding begin - #TODO + """ + As a fledgling, move around a little, but mainly wait for maturity and + check mortality. + """ + @phase fledgling begin + #TODO add feeding & growth + @kill(@trait(fledglingpredationmortality), "predation") + @randomwalk(1) #TODO add movement following the parents + if @trait(age) == @trait(fledglingtime)+@trait(eggtime) + @kill(@trait(firstyearmortality), "first year mortality") #XXX mechanistic? + @setphase(nonbreeding) + end end + """ + As a non-breeding adult, move around with other individuals and check for migration. + """ @phase nonbreeding begin - #TODO + # flocking behaviour - follow a random neighbour or move randomly + #TODO add feeding and mortality, respect habitat when moving + neighbours = map(a->a.id, @neighbours(10)) + #isempty(neighbours) ? @randomwalk(5) : @follow(@rand(neighbours), 2) + if isempty(neighbours) + @randomwalk(5) + else + @follow(model[@rand(neighbours)], 2) + end + # check if the bird migrates + leave, arrive = animal.migrationdates + m, d = monthday(model.date) + migrate = (((m < arrive[1]) || (m == arrive[1] && d < arrive[2])) || + ((m > leave[1]) || (m == leave[1] && d >= leave[2]))) + if migrate + @kill(@trait(migrationmortality), "migration") + returndate = Date(year(model.date), arrive[1], arrive[2]) + model.date != @param(core.startdate) && (returndate += Year(1)) + @setphase(mating) + @migrate(returndate) + end end - @phase migration begin - #TODO + """ + Move around until a mate is found. + """ + @phase mating begin + # if we've found a mate, wait for nesting begin and then go to the next phase + if @trait(mate) != -1 + if !@isalive(@trait(mate)) + @trait(mate) = -1 + return + end + m, d = monthday(model.date) + nest = ((m == @trait(nestingbegin)[1] && d > @trait(nestingbegin)[2] + && @chance(0.05)) || (m > @trait(nestingbegin)[1])) + nest && @setphase(nestbuilding) + return + end + # look for a mate among the neighbouring birds, or move randomly + for n in @neighbours(50) + if n.sex != @trait(sex) && n.mate == -1 + @trait(mate) = n.id + n.mate = @trait(id) + @debug "$(animalid(@trait(id))) and $(animalid(n.id)) have mated." + return + end + end + @randomwalk(10) end - @phase mating begin - #TODO + """ + Females select a location and build a nest. Males do nothing. (Sound familiar?) + """ + @phase nestbuilding begin + if @trait(sex) == female + if isempty(@trait(nest)) + # try to find a nest in the neighbourhood, or move on + nest = @randompixel(10, @trait(habitats)) + if isnothing(nest) + nest = () + @randomwalk(20) + else + # if we've found a location, start the clock on the building time + # (building time doubles for the first nest of the year) + @trait(nestcompletion) = @rand(nestbuildingtime) + month(model.date) == 4 && (@trait(nestcompletion) *= 2) + end + else + # wait while nest is being built, then lay eggs and go to next phase + if @trait(nestcompletion) > 0 + @trait(nestcompletion) -= 1 + else + @trait(clutch) = @reproduce(@trait(mate), @rand(eggsperclutch)) + @animal(@trait(mate)).clutch = @trait(clutch) + @setphase(breeding) + end + end + else + # males stay near the female + @follow(model[@trait(mate)], 5) + @animal(@trait(mate)).phase == "breeding" && @setphase(breeding) + end + end + + """ + Do lots of foraging (not yet implemented). + """ + @phase breeding begin + #TODO forage (move random) + for offspring in @trait(clutch) + # check if offspring are still alive and juvenile, else remove from clutch + if !@isalive(offspring) || @animal(offspring).phase == "nonbreeding" + deleteat!(@trait(clutch), findfirst(x->x==offspring, @trait(clutch))) + end + end + # if all young have fledged, move to nonbreeding (if it's July) or breed again + if isempty(@trait(clutch)) + @trait(nest) = () + month(model.date) >= 7 ? @setphase(nonbreeding) : @setphase(nestbuilding) + end end end @@ -66,13 +207,14 @@ end initskylark(skylark, model) Initialise a skylark individual. Selects migration dates and checks if the -bird should currently be on migration. +bird should currently be on migration. Also sets other individual-specific +variables. Called at model initialisation and when an egg is laid. """ function initskylark(animal::Animal, model::AgentBasedModel) @debug "Added $(animalid(animal)) at $(animal.pos)" + # calculate migration dates for this individual animal.migrationdates = migrationdates(animal, model) - leave = animal.migrationdates[1] - arrive = animal.migrationdates[2] + leave, arrive = animal.migrationdates m, d = monthday(model.date) migrate = (((m < arrive[1]) || (m == arrive[1] && d < arrive[2])) || ((m > leave[1]) || (m == leave[1] && d >= leave[2]))) @@ -81,6 +223,9 @@ function initskylark(animal::Animal, model::AgentBasedModel) model.date != @param(core.startdate) && (returndate += Year(1)) @migrate(returndate) end + # set individual life-history parameters that are defined as ranges for the species + @trait(nestlingtime) = @rand(@trait(nestlingtime)) + @trait(fledglingtime) = @rand(@trait(fledglingtime)) #TODO other stuff? end @@ -91,6 +236,8 @@ Select the dates on which this skylark will leave for / return from its migratio based on observed migration patterns. """ function migrationdates(skylark::Animal, model::AgentBasedModel) + #TODO this ought to be temperature-dependent and dynamic + #XXX magic numbers! minleave = skylark.sex == female ? (9, 15) : (10, 1) minarrive = skylark.sex == male ? (2, 15) : (3, 1) deltaleave = @rand(0:45) #XXX ought to be normally distributed @@ -99,3 +246,4 @@ function migrationdates(skylark::Animal, model::AgentBasedModel) arrive = monthday(Date(2001, minarrive[1], minarrive[2]) + Day(deltaarrive)) (leave, arrive) end +