Skip to content
Snippets Groups Projects
Commit f925cb99 authored by xo30xoqa's avatar xo30xoqa
Browse files

Made skylark dates normally distributed, added first-year mortality

parent 61f0ef71
Branches
Tags
No related merge requests found
...@@ -79,7 +79,6 @@ Returns a Makie figure object. ...@@ -79,7 +79,6 @@ Returns a Makie figure object.
""" """
function skylarkpopulation(model::SimulationModel) function skylarkpopulation(model::SimulationModel)
pops = model.datatables["skylark_abundance"] pops = model.datatables["skylark_abundance"]
update_theme!(palette=(color=cgrad(:seaborn_bright, 6),), cycle=[:color])
f = Figure() f = Figure()
dates = @param(core.startdate):@param(core.enddate) dates = @param(core.startdate):@param(core.enddate)
axlimits = (1, length(dates), 0, maximum(pops[!,:TotalAbundance])) axlimits = (1, length(dates), 0, maximum(pops[!,:TotalAbundance]))
......
...@@ -98,22 +98,22 @@ lastyear(ad::AnnualDate, model::SimulationModel) = Date(year(model.date)-1, ad) ...@@ -98,22 +98,22 @@ lastyear(ad::AnnualDate, model::SimulationModel) = Date(year(model.date)-1, ad)
Return a random element from the given vector, following a (mostly) normal distribution based on Return a random element from the given vector, following a (mostly) normal distribution based on
index values (i.e. elements in the middle of the vector will be returned most frequently). index values (i.e. elements in the middle of the vector will be returned most frequently).
""" """
function randn(v::Vector, rng=default_rng()) function Base.randn(v::AbstractVector, rng=Random.default_rng())
r = randn(rng) + 3 # normal distribution centered around 3, gives values from 0 to 6 r = bounds(randn(rng) + 4, min = 1, max=7) # normal distribution with mean 4, values of [1,7]
step = 6 / length(v) step = 7 / length(v)
i = Int(round(r / step)) i = Int(round(r / step))
v[i] v[i]
end end
""" """
@randn(args...) @randn(vector)
Return a normally-distributed random number or element from the sample, using the model RNG. Return a normally-distributed random number or element from the sample, using the model RNG.
This is a utility wrapper that can only be used a context where the This is a utility wrapper that can only be used a context where the
`model` object is available. `model` object is available.
""" """
macro randn(args...) macro randn(v)
:($(esc(:randn))($(esc(:model)).rng, $(map(esc, args)...))) :($(esc(:randn))($(esc(v)), $(esc(:model)).rng))
end end
""" """
...@@ -148,3 +148,17 @@ where the `model` object is available. ...@@ -148,3 +148,17 @@ where the `model` object is available.
macro chance(odds) macro chance(odds)
:($(esc(:rand))($(esc(:model)).rng) < $(esc(odds))) :($(esc(:rand))($(esc(:model)).rng) < $(esc(odds)))
end end
### MISCELLANEOUS
"""
bounds(x; max=Inf, min=0)
A utility function to make sure that a number is within a given set of bounds.
Returns `max`/`min` if `x` is greater/less than this.
"""
function bounds(x::Number; max::Number=Inf, min::Number=0)
x > max ? max :
x < min ? min :
x
end
...@@ -20,7 +20,6 @@ function reproduce!(animal::Animal, model::SimulationModel, ...@@ -20,7 +20,6 @@ function reproduce!(animal::Animal, model::SimulationModel,
sex = @rand([male, female]) sex = @rand([male, female])
end end
bphase = populationparameters(typeof(animal)).birthphase bphase = populationparameters(typeof(animal)).birthphase
#TODO add DEB?
child = typeof(animal)(length(model.animals)+1, sex, (animal.id, mate), animal.pos, bphase) child = typeof(animal)(length(model.animals)+1, sex, (animal.id, mate), animal.pos, bphase)
push!(model.animals, child) push!(model.animals, child)
push!(animal.offspring, child.id) push!(animal.offspring, child.id)
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
### ###
##TODO ##TODO
## - first-year mortality
## - habitat-dependent juvenile predation mortality ## - habitat-dependent juvenile predation mortality
## - habitat-dependent dispersal movement ## - habitat-dependent dispersal movement
## - mid-season territory adjustment ## - mid-season territory adjustment
...@@ -51,6 +50,8 @@ ...@@ -51,6 +50,8 @@
const fledglingpredationmortality::Float64 = 0.01 # per-day fledgling mortality from predation 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 firstyearmortality::Float64 = 0.38 # total mortality in the first year after independence
# skylarks migrate from autumn to early spring, with the females leaving earlier
# and arriving later than the males
const migrationdeparture::Tuple{AnnualDate,AnnualDate} = ((September, 15), (November, 1)) const migrationdeparture::Tuple{AnnualDate,AnnualDate} = ((September, 15), (November, 1))
const migrationarrival::Tuple{AnnualDate,AnnualDate} = ((February, 15), (March, 1)) const migrationarrival::Tuple{AnnualDate,AnnualDate} = ((February, 15), (March, 1))
const migrationdelayfemales::Day = Day(15) const migrationdelayfemales::Day = Day(15)
...@@ -64,13 +65,14 @@ ...@@ -64,13 +65,14 @@
const nestingcover = (0.2, 0.5) # min and max preferred vegetation cover for nesting const nestingcover = (0.2, 0.5) # min and max preferred vegetation cover for nesting
const matefaithfulness = 0.5 # chance of a female retaining her previous partner const matefaithfulness = 0.5 # chance of a female retaining her previous partner
const nestingbegin::AnnualDate = (April, 10) # begin nesting in the middle of April const nestingbegin::Tuple{AnnualDate,AnnualDate} = ((April, 10), (April, 20)) # 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 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 eggsperclutch::UnitRange{Int64} = 2:5 # eggs laid per clutch
const nestingend::AnnualDate = (August, 15) # end of the nesting period const nestingend::AnnualDate = (August, 15) # end of the nesting period
# individual variables # individual variables
timer::Int64 = 0 # a counter that can be used for different purposes timer::Int64 = 0 # a counter that can be used for different purposes
firstnest::AnnualDate = (April, 15) # is redefined by each individual in @create(Skylark)
migrationdates::Tuple = () # is defined by each individual in @create(Skylark) migrationdates::Tuple = () # is defined by each individual in @create(Skylark)
mate::Int64 = -1 # the agent ID of the mate (-1 if none) mate::Int64 = -1 # the agent ID of the mate (-1 if none)
nest::Tuple = () # coordinates of current nest nest::Tuple = () # coordinates of current nest
...@@ -86,6 +88,7 @@ end ...@@ -86,6 +88,7 @@ end
Non-breeding adults move around with other individuals and check for migration. Non-breeding adults move around with other individuals and check for migration.
""" """
@phase Skylark nonbreeding begin @phase Skylark nonbreeding begin
#XXX is flocking behaviour important? It may be quite computationally expensive...
# flocking behaviour - follow a random neighbour or move randomly # flocking behaviour - follow a random neighbour or move randomly
neighbours = @neighbours(self.visionrange) #XXX check for the closest neighbour(s)? neighbours = @neighbours(self.visionrange) #XXX check for the closest neighbour(s)?
isempty(neighbours) ? isempty(neighbours) ?
...@@ -183,10 +186,10 @@ end ...@@ -183,10 +186,10 @@ end
Females that have found a partner build a nest and lay eggs in a suitable location. Females that have found a partner build a nest and lay eggs in a suitable location.
""" """
@phase Skylark nesting begin @phase Skylark nesting begin
if model.date < self.nestingbegin if model.date < self.firstnest #XXX dependent on weather?
# wait for nesting to begin, moving around in the territory # wait for nesting to begin, moving around in the territory
@move(@rand(@animal(self.mate).territory)) @move(@rand(@animal(self.mate).territory))
elseif model.date > self.nestingend - Month(1) elseif self.clutch == 0 && model.date > self.nestingend - Month(1)
@setphase(nonbreeding) # stop trying to nest if it's too late (should be rare) @setphase(nonbreeding) # stop trying to nest if it's too late (should be rare)
elseif isempty(self.nest) elseif isempty(self.nest)
# choose site, build nest & lay eggs # choose site, build nest & lay eggs
...@@ -195,15 +198,9 @@ Females that have found a partner build a nest and lay eggs in a suitable locati ...@@ -195,15 +198,9 @@ Females that have found a partner build a nest and lay eggs in a suitable locati
if allowsnesting(self, model, pos) if allowsnesting(self, model, pos)
@move(pos) @move(pos)
self.nest = pos self.nest = pos
self.clutch = @rand(self.eggsperclutch)
#XXX all skylarks laying on the same day lay the same number of eggs? RNG?! #XXX all skylarks laying on the same day lay the same number of eggs? RNG?!
# time to build + 1 day per egg laid self.timer = @rand(self.nestbuildingtime)
self.timer = @rand(self.nestbuildingtime) + self.clutch (model.date == self.firstnest) && (self.timer *= 2) # the first nest takes twice as long to build
if month(model.date) == month(self.nestingbegin)
# the first nest takes twice as long to build
#XXX this may affect the first two nests
self.timer += self.timer*2-self.clutch
end
break break
end end
end end
...@@ -211,8 +208,13 @@ Females that have found a partner build a nest and lay eggs in a suitable locati ...@@ -211,8 +208,13 @@ Females that have found a partner build a nest and lay eggs in a suitable locati
# all agricultural areas have height == cover == 0) # all agricultural areas have height == cover == 0)
#isempty(self.nest) && @warn("$(animalid(self)) didn't find a nesting location.") #isempty(self.nest) && @warn("$(animalid(self)) didn't find a nesting location.")
elseif self.timer == 0 elseif self.timer == 0
if self.clutch == 0 # one egg is laid per day
self.clutch = @randn(self.eggsperclutch)
self.timer = self.clutch
else
@debug("$(animalid(self)) has laid $(self.clutch) eggs.") @debug("$(animalid(self)) has laid $(self.clutch) eggs.")
@setphase(breeding) @setphase(breeding)
end
else else
self.timer -= 1 self.timer -= 1
end end
...@@ -226,7 +228,6 @@ Females that have laid eggs take care of their chicks, restarting the nesting pr ...@@ -226,7 +228,6 @@ Females that have laid eggs take care of their chicks, restarting the nesting pr
chicks are independent or in case of brood loss. chicks are independent or in case of brood loss.
""" """
@phase Skylark breeding begin @phase Skylark breeding begin
#FIXME juveniles remain much too long
#XXX Schachtelbruten - sometimes skylarks start a new nest before the previous young are gone #XXX Schachtelbruten - sometimes skylarks start a new nest before the previous young are gone
# wait for eggs to hatch & chicks to mature, checking for predation and disturbance mortality # wait for eggs to hatch & chicks to mature, checking for predation and disturbance mortality
self.timer += 1 self.timer += 1
...@@ -239,8 +240,11 @@ chicks are independent or in case of brood loss. ...@@ -239,8 +240,11 @@ chicks are independent or in case of brood loss.
@chance(self.fledglingpredationmortality) && destroynest!(self, "predation") @chance(self.fledglingpredationmortality) && destroynest!(self, "predation")
else else
# create new young, reset timer and clutch counter # create new young, reset timer and clutch counter
#FIXME first year mortality for o in 1:self.clutch #XXX is this the best way of doing first-year mortality?
@reproduce(self.clutch, self.mate) @chance(self.firstyearmortality) ?
@debug("A skylark has died from first year mortality") :
@reproduce(1, self.mate)
end
self.clutch = 0 self.clutch = 0
end end
if self.clutch > 0 if self.clutch > 0
...@@ -364,8 +368,8 @@ should currently be on migration. Also sets other individual-specific variables. ...@@ -364,8 +368,8 @@ should currently be on migration. Also sets other individual-specific variables.
@create Skylark begin @create Skylark begin
@debug "Added $(animalid(self)) at $(self.pos)" @debug "Added $(animalid(self)) at $(self.pos)"
#XXX migration dates should be temperature-dependent and dynamic #XXX migration dates should be temperature-dependent and dynamic
arrive = @rand(self.migrationarrival[1]:self.migrationarrival[2]) arrive = @randn(self.migrationarrival[1]:self.migrationarrival[2])
depart = @rand(self.migrationdeparture[1]:self.migrationdeparture[2]) depart = @randn(self.migrationdeparture[1]:self.migrationdeparture[2])
if self.sex == female if self.sex == female
arrive += self.migrationdelayfemales arrive += self.migrationdelayfemales
depart += self.migrationdelayfemales depart += self.migrationdelayfemales
...@@ -377,6 +381,7 @@ should currently be on migration. Also sets other individual-specific variables. ...@@ -377,6 +381,7 @@ should currently be on migration. Also sets other individual-specific variables.
@setphase(matesearch) @setphase(matesearch)
@migrate(arrive) @migrate(arrive)
end end
self.firstnest = @randn(self.nestingbegin[1]:self.nestingbegin[2])
end end
""" """
......
...@@ -240,18 +240,6 @@ function randomdirection(model::SimulationModel, distance::Length) ...@@ -240,18 +240,6 @@ function randomdirection(model::SimulationModel, distance::Length)
Tuple(@rand(-range:range, 2)) Tuple(@rand(-range:range, 2))
end end
"""
bounds(x; max=Inf, min=0)
A utility function to make sure that a number is within a given set of bounds.
Returns `max`/`min` if `x` is greater/less than this.
"""
function bounds(x::Number; max::Number=Inf, min::Number=0)
x > max ? max :
x < min ? min :
x
end
""" """
inbounds(pos, model) inbounds(pos, model)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment