From 02fa32d94f590c820b43c708a774dd0e3bbdb2c6 Mon Sep 17 00:00:00 2001 From: Daniel Vedder <daniel.vedder@idiv.de> Date: Tue, 10 Jan 2023 12:14:00 +0100 Subject: [PATCH] Made data outputs a model property --- src/Persephone.jl | 1 + src/core/output.jl | 88 ++++++++++++++++-------------------- src/core/simulation.jl | 2 + src/nature/ecologicaldata.jl | 4 +- test/io_tests.jl | 18 ++++---- test/landscape_tests.jl | 1 + 6 files changed, 56 insertions(+), 58 deletions(-) diff --git a/src/Persephone.jl b/src/Persephone.jl index 43ef2f1..7795c48 100644 --- a/src/Persephone.jl +++ b/src/Persephone.jl @@ -30,6 +30,7 @@ export FarmPlot, Animal, Farmer, + DataOutput, #macros @param, @species, diff --git a/src/core/output.jl b/src/core/output.jl index a75c347..b7ca007 100644 --- a/src/core/output.jl +++ b/src/core/output.jl @@ -82,58 +82,50 @@ struct DataOutput frequency::String end -let outputregistry = Vector{DataOutput}(), - nextmonthlyoutput = today(), - nextyearlyoutput = today() - - """ - newdataoutput(model, filename, header, outputfunction, frequency) +""" + newdataoutput(model, filename, header, outputfunction, frequency) - 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 newdataoutput(model::AgentBasedModel, filename::String, header::String, - outputfunction::Function, frequency::String) - if !(frequency in ("daily", "monthly", "yearly", "end", "never")) - Base.error("Invalid frequency '$frequency' for $filename.") - end - ndo = DataOutput(filename, header, outputfunction, frequency) - append!(outputregistry, [ndo]) - if frequency != "never" - open(joinpath(@param(core.outdir), filename), "w") do f - println(f, header) - end +Create and register a new data output. This function must be called by all submodels +that want to have their output functions called regularly. +""" +function newdataoutput(model::AgentBasedModel, filename::String, header::String, + outputfunction::Function, frequency::String) + if !(frequency in ("daily", "monthly", "yearly", "end", "never")) + Base.error("Invalid frequency '$frequency' for $filename.") + end + ndo = DataOutput(filename, header, outputfunction, frequency) + append!(model.dataoutputs, [ndo]) + if frequency != "never" + open(joinpath(@param(core.outdir), filename), "w") do f + println(f, header) end end +end - """ - outputdata(model) - - Cycle through all registered data outputs and activate them according to their - configured frequency. - """ - global function outputdata(model::AgentBasedModel) - #XXX all output functions are run on the first update (regardless of frequency) - # -> should they all be run on the last update, too? - if model.date == @param(core.startdate) - nextmonthlyoutput = model.date - nextyearlyoutput = model.date - end - for output in outputregistry - (output.frequency == "never") && continue - # check if this output should be activated today - if (output.frequency == "daily") || - (output.frequency == "monthly" && model.date == nextmonthlyoutput) || - (output.frequency == "yearly" && model.date == nextyearlyoutput) || - (output.frequency == "end" && model.date == @param(core.enddate)) - open(joinpath(@param(core.outdir), output.filename), "a") do f - outstring = output.outputfunction(model) - (outstring[end] != '\n') && (outstring *= '\n') - print(f, outstring) - end - end +""" + outputdata(model) + +Cycle through all registered data outputs and activate them according to their +configured frequency. +""" +function outputdata(model::AgentBasedModel) + #XXX all output functions are run on the first update (regardless of frequency) + # -> should they all be run on the last update, too? + startdate = @param(core.startdate) + isnextmonth = d -> (day(d) == day(startdate)) + isnextyear = d -> (month(d) == month(startdate) && day(d) == day(startdate)) + for output in model.dataoutputs + (output.frequency == "never") && continue + # check if this output should be activated today + if (output.frequency == "daily") || + (output.frequency == "monthly" && isnextmonth(model.date)) || + (output.frequency == "yearly" && isnextyear(model.date)) || + (output.frequency == "end" && model.date == @param(core.enddate)) + open(joinpath(@param(core.outdir), output.filename), "a") do f + outstring = output.outputfunction(model) + (outstring[end] != '\n') && (outstring *= '\n') + print(f, outstring) + end end - (model.date == nextmonthlyoutput) && (nextmonthlyoutput = model.date + Month(1)) - (model.date == nextyearlyoutput) && (nextyearlyoutput = model.date + Year(1)) end end diff --git a/src/core/simulation.jl b/src/core/simulation.jl index 11244c8..0c98df3 100644 --- a/src/core/simulation.jl +++ b/src/core/simulation.jl @@ -22,11 +22,13 @@ function initialise(config::String=PARAMFILE, seed::Union{Int64,Nothing}=nothing settings = getsettings(config, seed) Random.seed!(settings["core"]["seed"]) events = Vector{FarmEvent}() + dataoutputs = Vector{DataOutput}() landscape = initlandscape(settings["core"]["landcovermap"], settings["core"]["farmfieldsmap"]) space = GridSpace(size(landscape), periodic=false) properties = Dict{Symbol,Any}(:settings=>settings, :date=>settings["core"]["startdate"], :landscape=>landscape, + :dataoutputs=>dataoutputs, :events=>events) @debug "Setting up model." model = AgentBasedModel(Union{Farmer,Animal,FarmPlot}, space, properties=properties, diff --git a/src/nature/ecologicaldata.jl b/src/nature/ecologicaldata.jl index 507ad17..564097a 100644 --- a/src/nature/ecologicaldata.jl +++ b/src/nature/ecologicaldata.jl @@ -12,8 +12,8 @@ const INDFILE = "individuals.csv" Create output files for each data group collected by the nature model. """ function initecologicaldata(model::AgentBasedModel) - newdataoutput(model, POPFILE, "Date;Species;Abundance", savepopulationdata, - @param(nature.popoutfreq)) + newdataoutput(model, POPFILE, "Date;Species;Abundance", + savepopulationdata, @param(nature.popoutfreq)) newdataoutput(model, INDFILE, "Date;ID;X;Y;Species;Sex;Age", saveindividualdata, @param(nature.indoutfreq)) end diff --git a/test/io_tests.jl b/test/io_tests.jl index 8534fbe..3554457 100644 --- a/test/io_tests.jl +++ b/test/io_tests.jl @@ -14,21 +14,23 @@ end @testset "Output functions" begin - properties = Dict{Symbol,Any}(:settings=>TESTSETTINGS) + properties = Dict{Symbol,Any}(:settings=>TESTSETTINGS, + :dataoutputs=>Vector{DataOutput}()) space = GridSpace((10,10), periodic=false) model = AgentBasedModel(Animal, space, properties=properties, warn=false) # test that the output directory is created with all files + outdir = @param(core.outdir) logstring = "Setting up output directory results_testsuite_$(Dates.today())_s1" @test_logs (:debug, logstring) min_level=Logging.Debug Ps.setupdatadir(model) - @test isdir(@param(core.outdir)) - @test isfile(joinpath(@param(core.outdir), @param(core.landcovermap))) - @test isfile(joinpath(@param(core.outdir), @param(core.farmfieldsmap))) - @test isfile(joinpath(@param(core.outdir), @param(core.configfile))) - @test isfile(joinpath(@param(core.outdir), Ps.LOGFILE)) + @test isdir(outdir) + @test isfile(joinpath(outdir, @param(core.landcovermap))) + @test isfile(joinpath(outdir, @param(core.farmfieldsmap))) + @test isfile(joinpath(outdir, @param(core.configfile))) + @test isfile(joinpath(outdir, Ps.LOGFILE)) # check whether the overwrite warning/protection works - logstring = "Overwriting existing output directory $(@param(core.outdir))." + logstring = "Overwriting existing output directory $(outdir)." @test_logs (:warn, logstring) match_mode=:any Ps.setupdatadir(model) #TODO test overwrite protection (requires parameter mutability) - rm(@param(core.outdir), force=true, recursive=true) + rm(outdir, force=true, recursive=true) #TODO test that creating a DataOutput works, and outputs data with the required frequency end diff --git a/test/landscape_tests.jl b/test/landscape_tests.jl index 78ea08e..958e046 100644 --- a/test/landscape_tests.jl +++ b/test/landscape_tests.jl @@ -59,6 +59,7 @@ end properties = Dict{Symbol,Any}(:date=>Date(2022, 1, 1), :landscape=>landscape, :events=>Vector{FarmEvent}(), + :dataoutputs=>Vector{DataOutput}(), :settings=>TESTSETTINGS) model = AgentBasedModel(Union{Farmer,Animal,FarmPlot}, space, properties=properties, warn=false) -- GitLab