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
+