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

Added skylark data output functions

parent e81536f7
No related branches found
No related tags found
No related merge requests found
...@@ -71,6 +71,30 @@ function populationtrends(model::SimulationModel) ...@@ -71,6 +71,30 @@ function populationtrends(model::SimulationModel)
f f
end end
"""
skylarkpopulation(model)
Plot a line graph of total population size and individual demographics of skylarks over time.
Returns a Makie figure object.
"""
function skylarkpopulation(model::SimulationModel)
pops = model.datatables["skylark_abundance"]
update_theme!(palette=(color=cgrad(:seaborn_bright, 6),), cycle=[:color])
f = Figure()
dates = @param(core.startdate):@param(core.enddate)
axlimits = (1, length(dates), 0, maximum(pops[!,:TotalAbundance]))
ax = Axis(f[1,1], xlabel="Date", ylabel="Population size",
limits=axlimits, xticks=gettickmarks(dates))
lines!(f[1,1], Vector{Float32}(pops.TotalAbundance), linewidth=3, label="Total abundance")
lines!(f[1,1], Vector{Float32}(pops.Mating), linewidth=3, label="Mating")
lines!(f[1,1], Vector{Float32}(pops.Breeding), linewidth=3, label="Breeding")
lines!(f[1,1], Vector{Float32}(pops.Nonbreeding), linewidth=3, label="Non-breeding")
lines!(f[1,1], Vector{Float32}(pops.Juvenile), linewidth=3, label="Juvenile")
lines!(f[1,1], Vector{Float32}(pops.Migrants), linewidth=3, label="Migrants")
axislegend("Demographic"; position=:lt)
f
end
""" """
gettickmarks(dates) gettickmarks(dates)
......
...@@ -158,9 +158,12 @@ struct DataOutput ...@@ -158,9 +158,12 @@ struct DataOutput
header::Vector{String} header::Vector{String}
outputfunction::Function outputfunction::Function
frequency::String frequency::String
plotfunction::Union{Function,Nothing} plotfunction::Union{Function,Nothing} #XXX remove this? (#81)
end end
##TODO what about output types that don't fit neatly into the standard CSV table format?
## (e.g. end-of-run stats, map data)
""" """
newdataoutput!(model, name, header, outputfunction, frequency) newdataoutput!(model, name, header, outputfunction, frequency)
...@@ -169,7 +172,7 @@ that want to have their output functions called regularly. ...@@ -169,7 +172,7 @@ that want to have their output functions called regularly.
""" """
function newdataoutput!(model::SimulationModel, name::String, header::Vector{String}, function newdataoutput!(model::SimulationModel, name::String, header::Vector{String},
outputfunction::Function, frequency::String, outputfunction::Function, frequency::String,
plotfunction::Union{Function,Nothing}=nothing) plotfunction::Union{Function,Nothing}=nothing) #XXX remove this? (#81)
if !(frequency in ("daily", "monthly", "yearly", "end", "never")) if !(frequency in ("daily", "monthly", "yearly", "end", "never"))
Base.error("Invalid frequency '$frequency' for $name.") #TODO replace with exception Base.error("Invalid frequency '$frequency' for $name.") #TODO replace with exception
end end
...@@ -235,7 +238,7 @@ end ...@@ -235,7 +238,7 @@ end
Cycle through all data outputs and call their respective plot functions, Cycle through all data outputs and call their respective plot functions,
saving each figure to file. saving each figure to file.
""" """
function visualiseoutput(model::SimulationModel) function visualiseoutput(model::SimulationModel) #XXX remove this? (#81)
@debug "Visualising output." @debug "Visualising output."
CairoMakie.activate!() # make sure we're using Cairo CairoMakie.activate!() # make sure we're using Cairo
for output in model.dataoutputs for output in model.dataoutputs
......
...@@ -14,6 +14,7 @@ function initecologicaldata(model::SimulationModel) ...@@ -14,6 +14,7 @@ function initecologicaldata(model::SimulationModel)
savepopulationdata, @param(nature.popoutfreq), populationtrends) savepopulationdata, @param(nature.popoutfreq), populationtrends)
newdataoutput!(model, "individuals", ["Date","ID","X","Y","Species","Sex","Age"], newdataoutput!(model, "individuals", ["Date","ID","X","Y","Species","Sex","Age"],
saveindividualdata, @param(nature.indoutfreq), visualisemap) saveindividualdata, @param(nature.indoutfreq), visualisemap)
initskylarkdata(model)
end end
""" """
...@@ -58,3 +59,96 @@ function saveindividualdata(model::SimulationModel) ...@@ -58,3 +59,96 @@ function saveindividualdata(model::SimulationModel)
data data
end end
## SKYLARK DATA OUTPUT
function initskylarkdata(model::SimulationModel)
newdataoutput!(model, "skylark_abundance",
["Date", "TotalAbundance", "Mating", "Breeding",
"Nonbreeding", "Juvenile", "Migrants"],
skylarkabundance, "daily", skylarkpopulation)
# newdataoutput!(model, "skylark_territories", ["Date", "ID", "X", "Y"],
# skylarkterritories, "monthly") #TODO add plotting function
newdataoutput!(model, "skylark_nests", ["Date", "ID", "X", "Y", "Landcover", "Crop"],
skylarknests, "monthly") #TODO add plotting function
# newdataoutput!(model, "skylark_mortality", ["Date", "N", "Cause"],
# skylarkmortality, "daily") #TODO add plotting function
end
"""
skylarkabundance(model)
Save skylark abundance data, including total abundance and demographic data
(abundances of breeding/non-breeding/juvenile/migrated individuals).
"""
function skylarkabundance(model::SimulationModel)
pops = Dict{String,Int}("TotalAbundance" => 0, "Mating" => 0, "Breeding" => 0,
"Nonbreeding" => 0, "Juvenile" => 0, "Migrants" => 0)
for a in model.animals
(isnothing(a) || speciesof(a) != "Skylark") && continue
pops["TotalAbundance"] += 1
if a.phase == nonbreeding
pops["Nonbreeding"] += 1
elseif a.phase in (territorysearch, matesearch) || (a.phase == occupation && a.mate == -1)
pops["Mating"] += 1
else
pops["Breeding"] += 1
end
if a.sex == female && a.clutch > 0
pops["TotalAbundance"] += a.clutch
pops["Juvenile"] += a.clutch
end
end
for m in model.migrants
if speciesof(m.first) == "Skylark"
pops["TotalAbundance"] += 1
pops["Migrants"] += 1
end
end
return [[model.date, pops["TotalAbundance"], pops["Mating"], pops["Breeding"],
pops["Nonbreeding"], pops["Juvenile"], pops["Migrants"]]]
end
"""
skylarkterritories(model)
Return a list of all coordinates occupied by a skylark territory, and the ID of the individual
holding the territory. WARNING: produces very big files.
"""
function skylarkterritories(model::SimulationModel)
#TODO is there a way of outputting this that takes less space? For example as a landscape-size
# matrix where each position is the ID of the individual occupying this pixel?
data = []
for a in model.animals
(isnothing(a) || a.sex != male || speciesof(a) != "Skylark" || a.phase != occupation) && continue
for pos in a.territory
push!(data, [model.date, a.id, pos[1], pos[2]])
end
end
data
end
"""
skylarknests(model)
Return a list of coordinates of active skylark nests, and the habitat type they occupy.
"""
function skylarknests(model::SimulationModel)
data = []
for a in model.animals
(isnothing(a) || a.sex != female || speciesof(a) != "Skylark" || isempty(a.nest)) && continue
push!(data, [model.date, a.id, a.nest[1], a.nest[2],
landcover(a.nest, model), cropname(a.nest, model)])
end
data
end
"""
skylarkmortality(model)
"""
function skylarkmortality(model::SimulationModel)
#TODO
#XXX may require an additional feature in output.jl to account for a different output type
end
...@@ -190,11 +190,13 @@ Females that have found a partner build a nest and lay eggs in a suitable locati ...@@ -190,11 +190,13 @@ Females that have found a partner build a nest and lay eggs in a suitable locati
@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
#XXX can I find a better solution that deepcopying & shuffling to randomise the location?
for pos in @shuffle!(deepcopy(@animal(self.mate).territory)) for pos in @shuffle!(deepcopy(@animal(self.mate).territory))
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) self.clutch = @rand(self.eggsperclutch)
#XXX all skylarks laying on the same day lay the same number of eggs? RNG?!
# time to build + 1 day per egg laid # time to build + 1 day per egg laid
self.timer = @rand(self.nestbuildingtime) + self.clutch self.timer = @rand(self.nestbuildingtime) + self.clutch
if month(model.date) == month(self.nestingbegin) if month(model.date) == month(self.nestingbegin)
...@@ -207,9 +209,9 @@ Females that have found a partner build a nest and lay eggs in a suitable locati ...@@ -207,9 +209,9 @@ Females that have found a partner build a nest and lay eggs in a suitable locati
end end
#FIXME happens quite often (probably because the crop models don't work yet, so #FIXME happens quite often (probably because the crop models don't work yet, so
# 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
@debug("$(animalid(self)) has laid $(self.clutch) eggs.") #FIXME 0 eggs laid? @debug("$(animalid(self)) has laid $(self.clutch) eggs.")
@setphase(breeding) @setphase(breeding)
else else
self.timer -= 1 self.timer -= 1
...@@ -224,6 +226,7 @@ Females that have laid eggs take care of their chicks, restarting the nesting pr ...@@ -224,6 +226,7 @@ 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
...@@ -246,6 +249,7 @@ chicks are independent or in case of brood loss. ...@@ -246,6 +249,7 @@ chicks are independent or in case of brood loss.
@respond(harvesting, destroynest!(self, "harvesting")) @respond(harvesting, destroynest!(self, "harvesting"))
else # restart breeding cycle if there is time else # restart breeding cycle if there is time
self.timer = 0 self.timer = 0
self.nest = ()
if model.date <= self.nestingend - Month(1) if model.date <= self.nestingend - Month(1)
@setphase(nesting) @setphase(nesting)
else else
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment