### Persefone - a socio-economic-ecological model of European agricultural landscapes. ### ### This file includes the core functions for initialising and running simulations. ### #XXX How can I make the model output during a parallel run clearer? """ simulate(config=PARAMFILE, seed=nothing) Initialise one or more model objects and carry out a full simulation experiment, optionally specifying a configuration file and a seed for the RNG. This is the default way to run a Persefone simulation. """ function simulate(config::String=PARAMFILE, seed::Union{Int64,Nothing}=nothing) models = initialise(config, seed) isa(models, Vector) ? map(simulate!, models) : #TODO parallelise simulate!(models) end """ simulate!(model) Carry out a complete simulation run using a pre-initialised model object. """ function simulate!(model::AgentBasedModel) runtime = Dates.value(@param(core.enddate)-@param(core.startdate))+1 step!(model, dummystep, stepsimulation!, runtime) finalise!(model) end """ initialise(config=PARAMFILE, seed=nothing) Initialise the model: read in parameters, create the output data directory, and instantiate the AgentBasedModel object(s). Optionally allows specifying the configuration file and overriding the `seed` parameter. This returns a single model object, unless the config file contains multiple values for one or more parameters, in which case it creates a full-factorial simulation experiment and returns a vector of model objects. """ function initialise(config::String=PARAMFILE, seed::Union{Int64,Nothing}=nothing) @info "Simulation run started at $(Dates.now())." settings = getsettings(config, seed) scanparams = settings["internal.scanparams"] delete!(settings, "internal.scanparams") isempty(scanparams) ? initmodel(settings) : map(initmodel, paramscan(settings, scanparams)) #TODO parallelise end """ initmodel(settings) Initialise a model object using a ready-made settings dict. This is a helper function for `initialise()`. """ function initmodel(settings::Dict{String, Any}) @debug "Initialising model object." createdatadir(settings["core.outdir"], settings["core.overwrite"]) logger = modellogger(settings["core.loglevel"], settings["core.outdir"]) with_logger(logger) do events = Vector{FarmEvent}() dataoutputs = Vector{DataOutput}() landscape = initlandscape(settings["world.landcovermap"], settings["world.farmfieldsmap"]) weather = initweather(settings["world.weatherfile"], settings["core.startdate"], settings["core.enddate"]) space = GridSpace(size(landscape), periodic=false) properties = Dict{Symbol,Any}(:settings=>settings, :logger=>logger, :date=>settings["core.startdate"], :landscape=>landscape, :weather=>weather, :dataoutputs=>dataoutputs, :events=>events) model = AgentBasedModel(Union{Farmer,Animal,FarmPlot}, space, properties=properties, rng=StableRNG(settings["core.seed"]), warn=false) saveinputfiles(model) initfields!(model) initfarms!(model) initnature!(model) model end end """ paramscan(settings) Create a list of settings dicts, covering all possible parameter combinations given by the input settings (i.e. a full-factorial experiment). This is a helper function for `initialise()`. """ function paramscan(settings::Dict{String,Any}, scanparams::Vector{String}) isempty(scanparams) && return [settings] param = pop!(scanparams) combinations = Vector{Dict{String,Any}}() # recursively generate a set of settings dicts covering all combinations for comb in paramscan(settings, scanparams) for value in settings[param] newcombination = deepcopy(comb) newcombination[param] = value if comb["core.outdir"] == settings["core.outdir"] outdir = joinpath(comb["core.outdir"], "$(split(param, ".")[2])_$(value)") else outdir = "$(comb["core.outdir"])_$(split(param, ".")[2])_$(value)" end newcombination["core.outdir"] = outdir push!(combinations, newcombination) end end combinations end """ stepsimulation!(model) Execute one update of the model. """ function stepsimulation!(model::AgentBasedModel) with_logger(model.logger) do @info "Simulating day $(model.date)." for a in Schedulers.ByType((Farmer,FarmPlot,Animal), true)(model) try #The animal may have been killed stepagent!(model[a], model) catch exc # check if the KeyError comes from the `model[a]` or the function call isa(exc, KeyError) && isa(exc.key, Int) ? continue : throw(exc) end end updateevents!(model) outputdata(model) model.date += Day(1) model end end """ finalise!(model) Wrap up the simulation. Currently doesn't do anything except print some information. """ function finalise!(model::AgentBasedModel) with_logger(model.logger) do @info "Simulated $(model.date-@param(core.startdate))." @info "Simulation run completed at $(Dates.now()),\nwrote output to $(@param(core.outdir))." #XXX is there anything to do here? model end end