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