From c3fc0ac338a3e1fc2b20614ae100d4a576f53e93 Mon Sep 17 00:00:00 2001 From: Daniel Vedder <daniel.vedder@idiv.de> Date: Tue, 29 Nov 2022 09:40:48 +0100 Subject: [PATCH] Made output frequency configurable, wrote saveindividualdata() --- src/core/landscape.jl | 1 + src/core/output.jl | 93 +++++++++++++++++++++++++++++++++++++++--- src/core/simulation.jl | 2 +- src/parameters.toml | 4 +- 4 files changed, 93 insertions(+), 7 deletions(-) diff --git a/src/core/landscape.jl b/src/core/landscape.jl index c286108..f8f8fd8 100644 --- a/src/core/landscape.jl +++ b/src/core/landscape.jl @@ -17,6 +17,7 @@ in a single object. The model landscape consists of a matrix of pixels. struct Pixel landcover::LandCover fieldid::Union{Missing, UInt32} + #FIXME actually this is stupid - I don't want a field ID, I want a field object end """ diff --git a/src/core/output.jl b/src/core/output.jl index de9047a..eb76714 100644 --- a/src/core/output.jl +++ b/src/core/output.jl @@ -5,6 +5,7 @@ const LOGFILE = "simulation.log" const POPFILE = "populations.csv" +const INDFILE = "individuals.csv" ## Note: `setupdatadir()` was adapted from the GeMM model by Leidinger et al. ## (https://github.com/CCTB-Ecomods/gemm/blob/master/src/output.jl) @@ -55,16 +56,78 @@ function setupdatadir() cp(lcmap, joinpath(param("core.outdir"), basename(lcmap)), force = true) cp(ffmap, joinpath(param("core.outdir"), basename(ffmap)), force = true) # Create the data output file(s) - open(joinpath(param("core.outdir"), POPFILE), "w") do f - println(f, "Date;Species;Abundance") + initnaturedatafiles() +end + + +## NATURE MODEL OUTPUT FUNCTIONS + +#XXX Should these be moved to a separate file (src/nature/natureoutput.jl)? +# At the moment I've kept them here so that all output functions are in the same place. +let nextpopout = nothing, nextindout = nothing + """ + initnaturedatafiles() + + Initialise the files needed for the nature output data and set the + date for the next data output, depending on the model settings. + """ + global function initnaturedatafiles() + startdate = param("core.startdate") + popoutfreq = param("nature.popoutfreq") + indoutfreq = param("nature.indoutfreq") + if popoutfreq != "never" + open(joinpath(param("core.outdir"), POPFILE), "w") do f + println(f, "Date;Species;Abundance") + end + (popoutfreq == "daily") && (nextpopout = startdate) + (popoutfreq == "monthly") && (nextpopout = startdate+Month(1)) + (popoutfreq == "yearly") && (nextpopout = startdate+Year(1)) + (popoutfreq == "end") && (nextpopout = param("core.enddate")) + else nextpopout = startdate - Day(1) end + if indoutfreq != "never" + open(joinpath(param("core.outdir"), INDFILE), "w") do f + println(f, "Date;ID;X;Y;Species;Sex;Age;Energy") + end + (indoutfreq == "daily") && (nextindout = startdate) + (indoutfreq == "monthly") && (nextindout = startdate+Month(1)) + (indoutfreq == "yearly") && (nextindout = startdate+Year(1)) + (indoutfreq == "end") && (nextindout = param("core.enddate")) + else nextindout = startdate - Day(1) end + end + + """ + recordnaturedata(model) + + Record the relevant data output from the nature model, depending on the frequency settings. + """ + global function recordnaturedata(model::AgentBasedModel) + popoutfreq = param("nature.popoutfreq") + indoutfreq = param("nature.indoutfreq") + if model.date == nextpopout + savepopulationdata(model) + (popoutfreq == "daily") && (nextpopout = model.date+Day(1)) + (popoutfreq == "monthly") && (nextpopout = model.date+Month(1)) + (popoutfreq == "yearly") && (nextpopout = model.date+Year(1)) + end + if model.date == nextindout + saveindividualdata(model) + (indoutfreq == "daily") && (nextindout = model.date+Day(1)) + (indoutfreq == "monthly") && (nextindout = model.date+Month(1)) + (indoutfreq == "yearly") && (nextindout = model.date+Year(1)) + end end end +##XXX The current method of controlling the next output date for the different +## output functions is a bit messy. If the farm model introduces similar parameters +## to popoutfreq/indoutfreq, it would be worth revising this to a cleaner hook system. + """ savepopulationdata(model) -Print a comma-separated set of lines to `populations.csv`, giving the -current date and population size for each animal species. +Print a comma-separated set of lines to `populations.csv`, giving the current date +and population size for each animal species. May be called never, daily, monthly, +yearly, or at the end of a simulation, depending on the parameter `nature.popoutfreq`. """ function savepopulationdata(model::AgentBasedModel) pops = Dict{String,Int}(s=>0 for s = param("nature.targetspecies")) @@ -74,7 +137,27 @@ function savepopulationdata(model::AgentBasedModel) end open(joinpath(param("core.outdir"), POPFILE), "a") do f for p in keys(pops) - println(f, "$(model.date);$(p);$(pops[p])") + println(f, join([model.date, p, pops[p]], ";")) end end end + +""" + saveindividualdata(model) + +Print a comma-separated set of lines to `individuals.csv`, listing all properties +of all animal individuals in the model. May be called never, daily, monthly, yearly, or +at the end of a simulation, depending on the parameter `nature.indoutfreq`. +WARNING: Produces very big files! +""" +function saveindividualdata(model::AgentBasedModel) + data = "" + for a in allagents(model) + (typeof(a) != Animal) && continue + entry = join([model.date,a.id,a.pos[1],a.pos[2],a.species.name,a.sex,a.age,a.energy], ";") + data = data*entry*"\n" + end + open(joinpath(param("core.outdir"), INDFILE), "a") do f + print(f, data) + end +end diff --git a/src/core/simulation.jl b/src/core/simulation.jl index 0790523..36511a4 100644 --- a/src/core/simulation.jl +++ b/src/core/simulation.jl @@ -40,7 +40,7 @@ function stepsimulation!(model::AgentBasedModel) #The animal may have been killed, so we need a try/catch try stepagent!(getindex(model, a), model) catch keyerror end end - savepopulationdata(model) + recordnaturedata(model) model.date += Day(1) end diff --git a/src/parameters.toml b/src/parameters.toml index ea7411d..d0032e3 100644 --- a/src/parameters.toml +++ b/src/parameters.toml @@ -22,7 +22,9 @@ enddate = 2020-01-31 [nature] targetspecies = ["Wolpertinger", "Wyvern"] # list of target species to simulate - +popoutfreq = "daily" # output frequency population-level data, daily/monthly/yearly/end/never +indoutfreq = "end" # output frequency individual-level data, daily/monthly/yearly/end/never + [crop] cropmodel = "linear" # crop growth model to use, "linear" or "aquacrop" (not yet implemented) -- GitLab