### Persephone - a socio-economic-ecological model of European agricultural landscapes. ### ### This file includes functions for saving the model output. ### const LOGFILE = "simulation.log" ## Note: `setupdatadir()` was adapted from the GeMM model by Leidinger et al. ## (https://github.com/CCTB-Ecomods/gemm/blob/master/src/output.jl) """ setupdatadir() Creates the output directory and copies relevant files into it. """ function setupdatadir() # Check whether the output directory already exists and handle conflicts if isdir(param("core.outdir")) overwrite = param("core.overwrite") if param("core.overwrite") == "ask" println("The chosen output directory $(param("core.outdir")) already exists.") println("Type 'yes' to overwrite this directory. Otherwise, the simulation will abort.") print("Overwrite? ") answer = readline() (answer == "yes") && (overwrite = true) end if !overwrite Base.error("Output directory exists, will not overwrite. Aborting.") else @warn "Overwriting existing output directory $(param("core.outdir"))." end end mkpath(param("core.outdir")) # Setup the logging system and logfile loglevel = Logging.Info if param("core.loglevel") == "debug" loglevel = Logging.Debug elseif param("core.loglevel") == "quiet" loglevel = Logging.Warn end logfile = open(joinpath(param("core.outdir"), LOGFILE), "w+") simulationlogger = TeeLogger(ConsoleLogger(logfile, loglevel), ConsoleLogger(stdout, loglevel)) global_logger(simulationlogger) @debug "Setting up output directory $(param("core.outdir"))" # Export a copy of the current parameter settings to the output folder. # This can be used to replicate this exact run in future, and also # records the current time and git commit. open(joinpath(param("core.outdir"), basename(param("core.configfile"))), "w") do f println(f, "#\n# --- Persephone configuration parameters ---") 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, "# with git commit $(read(`git rev-parse HEAD`, String))#\n") TOML.print(f, settings) end # Copy the map files to the output folder lcmap = param("core.landcovermap") ffmap = param("core.farmfieldsmap") !(isfile(lcmap)) && Base.error("The map file $(lcmap) 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(ffmap, joinpath(param("core.outdir"), basename(ffmap)), force = true) end """ DataOutput 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()`. Struct fields: - filename: the name of the file to be created in the user-specified output directory - 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 - frequency: how often to call the output function (daily/monthly/yearly/end/never) """ struct DataOutput filename::String header::String outputfunction::Function frequency::String end let outputregistry = Vector{DataOutput}(), nextmonthlyoutput = today(), nextyearlyoutput = today() """ newdataoutput(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(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 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 end (model.date == nextmonthlyoutput) && (nextmonthlyoutput = model.date + Month(1)) (model.date == nextyearlyoutput) && (nextyearlyoutput = model.date + Year(1)) end end