From f89a240c250a4701dfbc6f32a9f0f2abf3377568 Mon Sep 17 00:00:00 2001
From: Marco Matthies <71844+marcom@users.noreply.github.com>
Date: Tue, 16 Jul 2024 18:51:30 +0200
Subject: [PATCH] Introduce switchable crop model SimpleCrop

---
 src/Persefone.jl          |  1 +
 src/core/simulation.jl    | 25 ++++++++++++++++++++---
 src/crop/almass.jl        | 43 ++++++++++++++-------------------------
 src/crop/farmplot.jl      | 32 +++++++++++++++++++++++++++++
 src/crop/simplecrop.jl    | 22 ++++++++++++++++++--
 src/parameters.toml       |  2 +-
 test/test_parameters.toml |  2 +-
 7 files changed, 92 insertions(+), 35 deletions(-)

diff --git a/src/Persefone.jl b/src/Persefone.jl
index 4e423a0..0517e45 100644
--- a/src/Persefone.jl
+++ b/src/Persefone.jl
@@ -140,6 +140,7 @@ include("world/weather.jl")
 
 include("crop/farmplot.jl")
 include("crop/almass.jl")
+include("crop/simplecrop.jl")
 include("farm/farm.jl")
 
 include("nature/insects.jl")
diff --git a/src/core/simulation.jl b/src/core/simulation.jl
index 70ab962..7c2e8f2 100644
--- a/src/core/simulation.jl
+++ b/src/core/simulation.jl
@@ -118,13 +118,24 @@ function initmodel(settings::Dict{String, Any})
                                        settings["world.weatherfile"]),
                               settings["core.startdate"],
                               settings["core.enddate"])
+
+        # TODO: make this switching on cropmodel simpler
         if settings["crop.cropmodel"] == "almass"
             crops = ALMaSS.readcropparameters(settings["crop.cropfile"],
                                               settings["crop.growthfile"])
-            farmplots = Vector{ALMaSS.FarmPlot}()
+            Tfarmplot = ALMaSS.FarmPlot
+        elseif settings["crop.cropmodel"] == "simple"
+            # TODO: generalise ALMaSS.CropType?
+            # crops = Dict{String, ALMaSS.CropType}()
+            crops = ALMaSS.readcropparameters(settings["crop.cropfile"],
+                                              settings["crop.growthfile"])
+            Tfarmplot = SimpleCrop.FarmPlot
         else
             error("Init for crop model \"$(settings["crop.cropmodel"])\" not implemented")
         end
+
+        farmers = Vector{Farmer}()
+        farmplots = Vector{Tfarmplot}()
         model = AgricultureModel(settings,
                                  StableRNG(settings["core.seed"]),
                                  logger,
@@ -134,13 +145,21 @@ function initmodel(settings::Dict{String, Any})
                                  landscape,
                                  weather,
                                  crops,
-                                 Vector{Farmer}(),
+                                 farmers,
                                  farmplots,
                                  Vector{Union{Animal,Nothing}}(),
                                  Vector{Pair{Animal, Date}}(),
                                  Vector{FarmEvent}())
         saveinputfiles(model)
-        ALMaSS.initfields!(model)
+
+        if settings["crop.cropmodel"] == "almass"
+            ALMaSS.initfields!(model)
+        elseif settings["crop.cropmodel"] == "simple"
+            SimpleCrop.initfields!(model)
+        else
+            error("initfields! for crop model \"$(settings["crop.cropmodel"])\" not implemented")
+        end
+
         initfarms!(model)
         initnature!(model)
         model
diff --git a/src/crop/almass.jl b/src/crop/almass.jl
index 8d26660..4c8f708 100644
--- a/src/crop/almass.jl
+++ b/src/crop/almass.jl
@@ -7,7 +7,14 @@
 
 module ALMaSS
 
-using Persefone: AbstractFarmPlot, EventType, SimulationModel, maxtemp, mintemp, fertiliser
+using Persefone:
+    AbstractFarmPlot,
+    EventType,
+    SimulationModel,
+    fertiliser,
+    initfields_fill_with!,
+    maxtemp,
+    mintemp
 import Persefone: stepagent!
 using Dates: Date, month, monthday
 using CSV: CSV
@@ -156,34 +163,14 @@ end
 Initialise the model with its farm plots.
 """
 function initfields!(model::SimulationModel)
-    n = 0
-    convertid = Dict{Int64,Int64}()
-    width, height = size(model.landscape)
-    for x in 1:width
-        for y in 1:height
-            # for each pixel, we need to extract the field ID given by the map input
-            # file, and convert it into the internal object ID used by Agents.jl,
-            # creating a new agent object if necessary
-            rawid = model.landscape[x,y].fieldid
-            (ismissing(rawid)) && continue
-            if rawid in keys(convertid)
-                objectid = convertid[rawid]
-                model.landscape[x,y].fieldid = objectid
-                push!(model.farmplots[objectid].pixels, (x,y))
-            else
-                #XXX does this phase calculation work out?
-                month(model.date) < 3 ? phase = ALMaSS.janfirst : phase = ALMaSS.marchfirst
-                fp = FarmPlot(length(model.farmplots)+1, [(x,y)],
-                                model.crops["natural grass"], phase,
-                              0.0, 0.0, 0.0, 0.0, Vector{EventType}())
-                push!(model.farmplots, fp)
-                model.landscape[x,y].fieldid = fp.id
-                convertid[rawid] = fp.id
-                n += 1
-            end
-        end
+    initfields_fill_with!(model) do model, x, y
+        month(model.date) < 3 ? phase = ALMaSS.janfirst : phase = ALMaSS.marchfirst
+        FarmPlot(length(model.farmplots) + 1,
+                 [(x,y)],
+                 model.crops["natural grass"],
+                 phase,
+                 0.0, 0.0, 0.0, 0.0, Vector{EventType}())
     end
-    @info "Initialised $n farm plots."
 end
 
 """
diff --git a/src/crop/farmplot.jl b/src/crop/farmplot.jl
index 4bd2721..4272e05 100644
--- a/src/crop/farmplot.jl
+++ b/src/crop/farmplot.jl
@@ -214,3 +214,35 @@ end
 
 
 
+"""
+    initfields_fill_with!(make_farmplot_fn, model)
+
+Initialise the model with its farm plots, using the
+`make_farmplot_fn(model, x, y)` function to create new farmplots.
+"""
+function initfields_fill_with!(make_farmplot_fn::Function, model::SimulationModel)
+    n = 0
+    convertid = Dict{Int64,Int64}()
+    width, height = size(model.landscape)
+    for x in 1:width
+        for y in 1:height
+            # for each pixel, we need to extract the field ID given by the map input
+            # file, and convert it into the internal object ID used by Agents.jl,
+            # creating a new agent object if necessary
+            rawid = model.landscape[x,y].fieldid
+            (ismissing(rawid)) && continue
+            if rawid in keys(convertid)
+                objectid = convertid[rawid]
+                model.landscape[x,y].fieldid = objectid
+                push!(model.farmplots[objectid].pixels, (x,y))
+            else
+                fp = make_farmplot_fn(model, x, y)
+                push!(model.farmplots, fp)
+                model.landscape[x,y].fieldid = fp.id
+                convertid[rawid] = fp.id
+                n += 1
+            end
+        end
+    end
+    @info "Initialised $n farm plots."
+end
diff --git a/src/crop/simplecrop.jl b/src/crop/simplecrop.jl
index b903db5..d96cf30 100644
--- a/src/crop/simplecrop.jl
+++ b/src/crop/simplecrop.jl
@@ -1,13 +1,31 @@
-
 module SimpleCrop
 
+using Persefone: AbstractFarmPlot, SimulationModel, initfields_fill_with!
+import Persefone: stepagent!
+
+mutable struct FarmPlot <: AbstractFarmPlot
+    const id::Int64
+    pixels::Vector{Tuple{Int64, Int64}}
+end
+
 """
     stepagent!(farmplot, model)
 
 Update a farm plot by one day.
 """
 function stepagent!(farmplot::FarmPlot, model::SimulationModel)
+    # TODO: do something simple
 end
 
-end # module SimpleCrop
+"""
+    initfields!(model)
 
+Initialise the model with its farm plots.
+"""
+function initfields!(model::SimulationModel)
+    initfields_fill_with!(model) do model, x, y
+        FarmPlot(length(model.farmplots) + 1, [(x,y)])
+    end
+end
+
+end # module SimpleCrop
diff --git a/src/parameters.toml b/src/parameters.toml
index 2f2215b..659e887 100644
--- a/src/parameters.toml
+++ b/src/parameters.toml
@@ -40,7 +40,7 @@ indoutfreq = "end" # output frequency individual-level data, daily/monthly/yearl
 insectmodel = ["season", "habitat", "pesticides", "weather"] # factors affecting insect growth
 	
 [crop]
-cropmodel = "almass" # crop growth model to use, "almass" or "aquacrop"
+cropmodel = "almass" # crop growth model to use, "almass", "aquacrop", or "simple"
 cropfile = "data/crops/almass/crop_data_general.csv" # file with general crop parameters
 growthfile = "data/crops/almass/almass_crop_growth_curves.csv" # file with crop growth parameters	
 
diff --git a/test/test_parameters.toml b/test/test_parameters.toml
index 4d88eb3..94d6a9a 100644
--- a/test/test_parameters.toml
+++ b/test/test_parameters.toml
@@ -38,7 +38,7 @@ indoutfreq = "daily" # output frequency individual-level data, daily/monthly/yea
 insectmodel = ["season", "habitat", "pesticides"] # which factors affect insect growth ("weather" is not yet implemented)
 	
 [crop]
-cropmodel = "almass" # crop growth model to use, "almass" or "aquacrop"
+cropmodel = "almass" # crop growth model to use, "almass", "aquacrop", or "simple"
 cropfile = "crop_data_general.csv" # file with general crop parameters
 growthfile = "almass_crop_growth_curves.csv" # file with crop growth parameters
 
-- 
GitLab