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

Restructured data output functionality to be more modular

parent 6a86a434
No related branches found
No related tags found
No related merge requests found
TODO.md TODO.md
\ No newline at end of file docs/planning/very_first_results.zip
*results*/
\ No newline at end of file
...@@ -42,6 +42,7 @@ include("core/landscape.jl") ...@@ -42,6 +42,7 @@ include("core/landscape.jl")
include("farm/farm.jl") include("farm/farm.jl")
include("crop/crops.jl") include("crop/crops.jl")
include("nature/nature.jl") include("nature/nature.jl")
include("nature/ecologicaldata.jl")
include("nature/wolpertinger.jl") include("nature/wolpertinger.jl")
include("nature/wyvern.jl") include("nature/wyvern.jl")
include("core/simulation.jl") include("core/simulation.jl")
......
...@@ -31,11 +31,11 @@ let settings::Dict{String, Dict{String, Any}} ...@@ -31,11 +31,11 @@ let settings::Dict{String, Dict{String, Any}}
end end
""" """
astoml(io) printparameters(io)
Print all settings in TOML format to the given output stream. Print all settings in TOML format to the given output stream.
""" """
global function astoml(stream::IO=stdout) global function printparameters(stream::IO=stdout)
TOML.print(stream, settings) TOML.print(stream, settings)
end end
end end
......
...@@ -4,8 +4,6 @@ ...@@ -4,8 +4,6 @@
### ###
const LOGFILE = "simulation.log" const LOGFILE = "simulation.log"
const POPFILE = "populations.csv"
const INDFILE = "individuals.csv"
## Note: `setupdatadir()` was adapted from the GeMM model by Leidinger et al. ## Note: `setupdatadir()` was adapted from the GeMM model by Leidinger et al.
## (https://github.com/CCTB-Ecomods/gemm/blob/master/src/output.jl) ## (https://github.com/CCTB-Ecomods/gemm/blob/master/src/output.jl)
...@@ -46,7 +44,7 @@ function setupdatadir() ...@@ -46,7 +44,7 @@ function setupdatadir()
println(f, "# This file was generated automatically.") println(f, "# This file was generated automatically.")
println(f, "# Simulation run on $(string(Dates.format(Dates.now(), "d u Y HH:MM:SS"))),") println(f, "# Simulation run on $(string(Dates.format(Dates.now(), "d u Y HH:MM:SS"))),")
println(f, "# with git commit $(read(`git rev-parse HEAD`, String))#\n") println(f, "# with git commit $(read(`git rev-parse HEAD`, String))#\n")
astoml(f) printparameters(f)
end end
# Copy the map files to the output folder # Copy the map files to the output folder
lcmap = param("core.landcovermap") lcmap = param("core.landcovermap")
...@@ -55,109 +53,80 @@ function setupdatadir() ...@@ -55,109 +53,80 @@ function setupdatadir()
!(isfile(ffmap)) && Base.error("The map file $(ffmap) doesn't exist.") !(isfile(ffmap)) && Base.error("The map file $(ffmap) doesn't exist.")
cp(lcmap, joinpath(param("core.outdir"), basename(lcmap)), force = true) cp(lcmap, joinpath(param("core.outdir"), basename(lcmap)), force = true)
cp(ffmap, joinpath(param("core.outdir"), basename(ffmap)), force = true) cp(ffmap, joinpath(param("core.outdir"), basename(ffmap)), force = true)
# Create the data output file(s)
initnaturedatafiles()
end end
"""
DataOutput
## NATURE MODEL OUTPUT FUNCTIONS A struct for organising model output. This is designed for text-based data output
that is updated more or less regularly (e.g. population data in csv files).
Submodels can register their own output functions using `newdataoutput()`.
#XXX Should these be moved to a separate file (src/nature/natureoutput.jl)? Struct fields:
# At the moment I've kept them here so that all output functions are in the same place. - filename: the name of the file to be created in the user-specified output directory
let nextpopout = nothing, nextindout = nothing - header: a string to be written to the start of the file as it is initialised
""" - outputfunction: a function that takes a model object and returns a string to write to file
initnaturedatafiles() - frequency: how often to call the output function (daily/monthly/yearly/end/never)
"""
struct DataOutput
filename::String
header::String
outputfunction::Function
frequency::String
end
Initialise the files needed for the nature output data and set the let outputregistry = Vector{DataOutput}(),
date for the next data output, depending on the model settings. nextmonthlyoutput = today(),
""" nextyearlyoutput = today()
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) newdataoutput(filename, header, outputfunction, frequency)
Record the relevant data output from the nature model, depending on the frequency settings. Create and register a new data output. This function must be called by all submodels
that want to have their output functions called regularly.
""" """
global function recordnaturedata(model::AgentBasedModel) global function newdataoutput(filename::String, header::String,
popoutfreq = param("nature.popoutfreq") outputfunction::Function, frequency::String)
indoutfreq = param("nature.indoutfreq") if !(frequency in ("daily", "monthly", "yearly", "end", "never"))
if model.date == nextpopout Base.error("Invalid frequency '$frequency' for $filename.")
savepopulationdata(model)
(popoutfreq == "daily") && (nextpopout = model.date+Day(1))
(popoutfreq == "monthly") && (nextpopout = model.date+Month(1))
(popoutfreq == "yearly") && (nextpopout = model.date+Year(1))
end end
if model.date == nextindout ndo = DataOutput(filename, header, outputfunction, frequency)
saveindividualdata(model) append!(outputregistry, [ndo])
(indoutfreq == "daily") && (nextindout = model.date+Day(1)) if frequency != "never"
(indoutfreq == "monthly") && (nextindout = model.date+Month(1)) open(joinpath(param("core.outdir"), filename), "w") do f
(indoutfreq == "yearly") && (nextindout = model.date+Year(1)) println(f, header)
end
end end
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. May be called never, daily, monthly, outputdata(model)
yearly, or at the end of a simulation, depending on the parameter `nature.popoutfreq`.
""" Cycle through all registered data outputs and activate them according to their
function savepopulationdata(model::AgentBasedModel) configured frequency.
pops = Dict{String,Int}(s=>0 for s = param("nature.targetspecies")) """
for a in allagents(model) global function outputdata(model::AgentBasedModel)
(typeof(a) != Animal) && continue #XXX all output functions are run on the first update (regardless of frequency)
pops[a.species.name] += 1 # -> should they all be run on the last update, too?
end if model.date == param("core.startdate")
open(joinpath(param("core.outdir"), POPFILE), "a") do f nextmonthlyoutput = model.date
for p in keys(pops) nextyearlyoutput = model.date
println(f, join([model.date, p, pops[p]], ";"))
end end
end for output in outputregistry
end (output.frequency == "never") && continue
# check if this output should be activated today
""" if (output.frequency == "daily") ||
saveindividualdata(model) (output.frequency == "monthly" && model.date == nextmonthlyoutput) ||
(output.frequency == "yearly" && model.date == nextyearlyoutput) ||
Print a comma-separated set of lines to `individuals.csv`, listing all properties (output.frequency == "end" && model.date == param("core.enddate"))
of all animal individuals in the model. May be called never, daily, monthly, yearly, or open(joinpath(param("core.outdir"), output.filename), "a") do f
at the end of a simulation, depending on the parameter `nature.indoutfreq`. outstring = output.outputfunction(model)
WARNING: Produces very big files! (outstring[end] != '\n') && (outstring *= '\n')
""" print(f, outstring)
function saveindividualdata(model::AgentBasedModel) end
data = "" end
for a in allagents(model) end
(typeof(a) != Animal) && continue (model.date == nextmonthlyoutput) && (nextmonthlyoutput = model.date + Month(1))
entry = join([model.date,a.id,a.pos[1],a.pos[2],a.species.name,a.sex,a.age,a.energy], ";") (model.date == nextyearlyoutput) && (nextyearlyoutput = model.date + Year(1))
data = data*entry*"\n"
end
open(joinpath(param("core.outdir"), INDFILE), "a") do f
print(f, data)
end end
end end
...@@ -40,7 +40,7 @@ function stepsimulation!(model::AgentBasedModel) ...@@ -40,7 +40,7 @@ function stepsimulation!(model::AgentBasedModel)
#The animal may have been killed, so we need a try/catch #The animal may have been killed, so we need a try/catch
try stepagent!(getindex(model, a), model) catch keyerror end try stepagent!(getindex(model, a), model) catch keyerror end
end end
recordnaturedata(model) outputdata(model)
model.date += Day(1) model.date += Day(1)
end end
......
### Persephone - a socio-economic-ecological model of European agricultural landscapes.
###
### This file includes the functions for collecting and saving ecological output data.
###
const POPFILE = "populations.csv"
const INDFILE = "individuals.csv"
"""
savepopulationdata(model)
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"))
for a in allagents(model)
(typeof(a) != Animal) && continue
pops[a.species.name] += 1
end
data = ""
for p in keys(pops)
data *= join([model.date, p, pops[p]], ";")*"\n"
end
data
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 *= entry*"\n"
end
data
end
...@@ -97,8 +97,13 @@ end ...@@ -97,8 +97,13 @@ end
Initialise the model with all simulated animal populations. Initialise the model with all simulated animal populations.
""" """
function initnature!(model::AgentBasedModel) function initnature!(model::AgentBasedModel)
#The config file determines which species are simulated in this run # The config file determines which species are simulated in this run
for s in param("nature.targetspecies") for s in param("nature.targetspecies")
getspecies(s).initpop!(model) getspecies(s).initpop!(model)
end end
# Initialise the data output
newdataoutput(POPFILE, "Date;Species;Abundance", savepopulationdata,
param("nature.popoutfreq"))
newdataoutput(INDFILE, "Date;ID;X;Y;Species;Sex;Age;Energy",
saveindividualdata, param("nature.indoutfreq"))
end end
...@@ -6,6 +6,6 @@ then ...@@ -6,6 +6,6 @@ then
rm -r test_results rm -r test_results
fi fi
./run.jl -o test_results -s 1 ./run.jl -o test_results -s 2
src/analysis/analyse_nature.R src/analysis/analyse_nature.R
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment