Select Git revision
simulation.jl
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
simulation.jl 5.73 KiB
### Persefone.jl - a model of agricultural landscapes and ecosystems in Europe.
###
### 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"])
crops = readcropparameters(settings["crop.cropfile"],
settings["crop.growthfile"])
space = GridSpace(size(landscape), periodic=false)
properties = Dict{Symbol,Any}(:settings=>settings,
:logger=>logger,
:date=>settings["core.startdate"],
:landscape=>landscape,
:weather=>weather,
:crops=>crops,
: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