From c9136d3f31db3a5fa1a6d1b94c69ff60a44b822f Mon Sep 17 00:00:00 2001 From: Daniel Vedder <daniel.vedder@idiv.de> Date: Fri, 25 Nov 2022 17:44:43 +0100 Subject: [PATCH] Implemented the functions needed for Agents.jl This is a major commit, probably quite a bit too big... However, all the pieces here depended on each other, so I had to get it all working at the same time. The model now includes: 1. reading in map data 2. creating an Agents.jl model object 3. initialisation and stepping functions for the whole model 4. a basic skeleton of nature.jl 5. an example animal species (the Wolpertinger ;-) ) --- src/Persephone.jl | 14 ++++--- src/core/input.jl | 2 + src/core/simulation.jl | 60 ++++++++++++++++++++++------- src/crop/crops.jl | 29 ++++++++++++++ src/farm/farm.jl | 26 +++++++++++++ src/nature/nature.jl | 78 ++++++++++++++++++++++++++++++++++++++ src/nature/wolpertinger.jl | 34 +++++++++++++++++ src/parameters.toml | 2 +- 8 files changed, 225 insertions(+), 20 deletions(-) create mode 100644 src/nature/wolpertinger.jl diff --git a/src/Persephone.jl b/src/Persephone.jl index 2be55d1..ac48d52 100644 --- a/src/Persephone.jl +++ b/src/Persephone.jl @@ -25,21 +25,23 @@ using ## define exported functions and variables export simulate, - initsim, - stepsim, - finalisesim + initialise, + stepsimulation!, + finalise ## The file that stores all default parameters -const paramfile = "src/parameters.toml" +const PARAMFILE = "src/parameters.toml" ## (DO NOT CHANGE THIS VALUE! Instead, specify simulation-specific configuration files ## by using the "--configfile" commandline argument, or when invoking simulate().) -## include all module files +## include all module files - note that the order matters +## (if file b references something from file a, it must be included later) include("core/input.jl") include("core/output.jl") +include("farm/farm.jl") include("crop/crops.jl") include("nature/nature.jl") -include("farm/farm.jl") +include("nature/wolpertinger.jl") include("core/simulation.jl") end diff --git a/src/core/input.jl b/src/core/input.jl index ee4fc31..193fae5 100644 --- a/src/core/input.jl +++ b/src/core/input.jl @@ -128,5 +128,7 @@ end Read in a TIFF map file and return it as an array. """ function readtiffmapfile(filename) + tiff = GeoArrays.read(filename) + space = GridSpace(size(tiff)[1:2], periodic=false) #TODO this requires GeoArrays end diff --git a/src/core/simulation.jl b/src/core/simulation.jl index 0e647b3..8b0fc72 100644 --- a/src/core/simulation.jl +++ b/src/core/simulation.jl @@ -3,29 +3,63 @@ ### This file includes the core functions for initialising and running simulations. ### -function initsim(config::String) +""" + initialise(config) + +Initialise the model: read in parameters, create the output data directory, +and instantiate the AgentBasedModel object. +""" +function initialise(config::String=PARAMFILE) initsettings(config) - Random.seed!(param("core.seed")) setupdatadir() - #TODO create world + landcover = GeoArrays.read(param("core.mapfile")) + space = GridSpace(size(landcover)[1:2], periodic=false) + properties = Dict{Symbol,Any}(:day=>0, + :landcover=>landcover) + model = AgentBasedModel(Union{Farmer,Animal,CropPlot}, space, properties=properties, + rng=Random.Xoshiro(param("core.seed"))) + initfarms!(model) + initfields!(model) + initnature!(model) @info "Simulation initialised." + model end -function stepsim() - @info "Simulating another day." + +""" + stepsimulation!(model) + +Execute one update of the model. +""" +function stepsimulation!(model::AgentBasedModel) + model.day += 1 + @info "Simulating day $(model.day)." + for a in Schedulers.ByType((Farmer,Animal,CropPlot), true)(model) + stepagent!(getindex(model, a), model) + end #TODO end -function finalisesim() + +""" + finalise(model) + +Wrap up the simulation. Output all remaining data and exit. +""" +function finalise(model::AgentBasedModel) @info "Simulation ran. Nothing happened. But it will!" #TODO + genocide!(model) end -function simulate(config::String=paramfile) - initsim(config) - for day in 1:param("core.runtime") - stepsim() - end - finalisesim() - #TODO + +""" + simulate(config) + +Carry out a complete simulation run. +""" +function simulate(config::String=PARAMFILE) + model = initialise(config) + step!(model, dummystep, stepsimulation!, param("core.runtime")) + finalise(model) end diff --git a/src/crop/crops.jl b/src/crop/crops.jl index e69de29..0db809b 100644 --- a/src/crop/crops.jl +++ b/src/crop/crops.jl @@ -0,0 +1,29 @@ +### Persephone - a socio-economic-ecological model of European agricultural landscapes. +### +### This file is responsible for managing the crop growth modules. +### + +#XXX not sure whether it makes sense to have this as an agent type, +# or perhaps better a grid property? + +@agent CropPlot GridAgent{2} begin + #TODO +end + +""" + stepagent!(cropplot, model) + +Update a crop plot by one day. +""" +function stepagent!(cropplot::CropPlot, model::AgentBasedModel) + #TODO +end + +""" + initfields!(model) + +Initialise the model with its crop plots. +""" +function initfields!(model::AgentBasedModel) + #TODO +end diff --git a/src/farm/farm.jl b/src/farm/farm.jl index e69de29..e982cfb 100644 --- a/src/farm/farm.jl +++ b/src/farm/farm.jl @@ -0,0 +1,26 @@ +### Persephone - a socio-economic-ecological model of European agricultural landscapes. +### +### This file is responsible for managing the farm module(s). +### + +@agent Farmer GridAgent{2} begin + #TODO +end + +""" + stepagent!(farmer, model) + +Update a farmer by one day. +""" +function stepagent!(farmer::Farmer, model::AgentBasedModel) + #TODO +end + +""" + initfarms!(model) + +Initialise the model with a set of farm agents. +""" +function initfarms!(model::AgentBasedModel) + #TODO +end diff --git a/src/nature/nature.jl b/src/nature/nature.jl index e69de29..e2b7cf1 100644 --- a/src/nature/nature.jl +++ b/src/nature/nature.jl @@ -0,0 +1,78 @@ +### Persephone - a socio-economic-ecological model of European agricultural landscapes. +### +### This file is responsible for managing the animal modules. +### + +@enum Sex hermaphrodite male female + +""" + Species + +Species are differentiated by their name, and the functions used to +initialise their population and update each individual. +""" +struct Species + name::String + initpop::Function # takes one argument: model + update::Function # takes two arguments: animal, model +end + +""" + Animal + +This is the generic agent type for all animals. Species are differentiated +by the `species` struct passed by them during initialisation. +""" + +@agent Animal GridAgent{2} begin + species::Species + sex::Sex + age::Int32 + energy::Int32 +end + +let specieslist = Dict{String, Species}() + """ + registerspecies(species) + + This function has to be called by every species definition file + in order for the species to be used. + """ + global function registerspecies(s::Species) + specieslist[s.name] = s + end + + """ + getspecies(name) + + Returns the definition struct for the named species. + """ + global function getspecies(name::String) + specieslist[name] + end +end + +""" + stepagent!(animal, model) + +Update an animal by one day. +""" +function stepagent!(animal::Animal, model::AgentBasedModel) + animal.age += 1 + animal.species.update(animal,model) + if animal.energy <= 0 + kill_agent!(animal, model) + end +end + +""" + initnature!(model) + +Initialise the model with all simulated animal populations. +""" +function initnature!(model::AgentBasedModel) + for s in param("nature.targetspecies") + getspecies(s).initpop(model) + end +end + diff --git a/src/nature/wolpertinger.jl b/src/nature/wolpertinger.jl new file mode 100644 index 0000000..8875ec5 --- /dev/null +++ b/src/nature/wolpertinger.jl @@ -0,0 +1,34 @@ +### Persephone - a socio-economic-ecological model of European agricultural landscapes. +### +### This file holds the code for the Wolpertinger (https://en.wikipedia.org/wiki/Wolpertinger). +### NOT FOR ACTUAL USE! This is of course only a test species ;-) +### Although I dare say the Wolpertinger is rather endangered... +### + +""" + initwolpertinger!(model) + +Initialise a population of Wolpertingers in random locations around the landscape. +""" +function initwolpertinger!(model::AgentBasedModel) + species = getspecies("Wolpertinger") + worldsize = size(model.landcover)[1:2] + popsize = round(worldsize[1]*worldsize[2]/1000) + for i in 1:popsize + add_agent!(Animal, model, species, hermaphrodite, 0, 100) + end + @debug "Hid $(popsize) Wolpertingers for gullible tourists to find." +end + +""" + updatewolpertinger(animal, model) + +Wolpertingers are rather stupid creatures, all they do is move around randomly +and occasionally reproduce by spontaneous parthogenesis... +""" +function updatewolpertinger!(w::Animal, model::AgentBasedModel) + #TODO + @debug "The Wolpertinger is a mysterious animal..." w.id +end + +registerspecies(Species("Wolpertinger", initwolpertinger!, updatewolpertinger!)) diff --git a/src/parameters.toml b/src/parameters.toml index 6a1fc49..52e2521 100644 --- a/src/parameters.toml +++ b/src/parameters.toml @@ -18,7 +18,7 @@ seed = 0 # seed value for the RNG (0 -> random value) [nature] -targetspecies = [] # list of target species to simulate +targetspecies = ["Wolpertinger"] # list of target species to simulate [crop] cropmodel = "linear" # crop growth model to use, "linear" or "aquacrop" (not yet implemented) -- GitLab