Persefone is divided into four components, three of which are semi-independent submodels:
core: This is the foundation of the model software, which sets up and executes simulation runs. It also reads in the configuration file and landscape maps, and provides data output functionality. (Eventually, it will also provide weather data.)
nature: This is an individual-based model of species in agricultural landscapes. It defines the
Animalagent type, and a set of macros that can be used to rapidly create new species. It also includes ecological process functions that are useful for all species.
farm: This is an agent-based model of farmer decision making. It is not yet implemented, but will provide the
crop: This is a mathematical growth model for various crops. It is not yet implemented, but already provides the agent type
FarmPlot, representing one field and its associated extent and crop type.
core provides functionality that is needed by all of the submodels. Decisions made by
Farmers affect the
FarmPlots they own, and (directly or indirectly) the
Animals in the model landscape.
A cursory reading of the source code will quickly show that most functions take an
AgentBasedModel object as one of their arguments. This is the key data structure of Agents.jl, and holds all state that is in any way relevant to a simulation run. (Persefone has a strict "no global state" policy to avoid state-dependent bugs and allow parallelisation.) The model object gives access to all agent instances (via
id is the unique identifier of this agent). It also stores the configuration (
model.settings), the landscape (
model.landscape, a matrix of
Pixel objects that store the local land cover, amongst other things), and the current simulation date (
Persefone.initmodel for details.)
For more information about working with agent objects, see the Agents.jl API.
The model is configured via a TOML file, the default version of which is at
src/parameters.toml. An individual run can be configured using a user-defined configuration file, commandline arguments, or function calls (when Persefone is used as a package rather than an application). During a model run, the
@param macro can be used to access parameter values. Note that parameter names are prepended with the name of the component they are associated with. For example, the
outdir parameter belongs to the
[core] section of the TOML file, and must therefore be referenced as
src/core/input.jl for details.)
@param(parameter) expands to
model.settings["parameter"], it can obviously only be used in a context where the
model object is actually available. (This is the case for most functions in Persefone, but not for all.) Similarly, many of the
nature macros depend on specific variables being available where they are called, and can therefore only be used in specific contexts (this is indicated in their documentation).
Persefone can output model data into text files with a specified frequency (daily, monthly, yearly, or at the simulation end). Submodels can use
Persefone.newdataoutput! to plug into this system. For an example of how to use this, see
src/core/output.jl for details.)
FarmEvent struct is used to communicate farming-related events between submodels. An event can be triggered with
createevent! and affects all pixels within a
src/core/landscape.jl for details.)
By default in Julia, the random number generator (RNG) and the system logger are two globally accessible variables. As Persefone needs to avoid all global data (since this would interfere with reproducibility in parallel runs), the
model object stores a local logger and a local RNG. The local logger generally does not change the way the model uses log statements, it is only relevant for some functions in