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