From 1e52e2591dea158ad441d94e9b9e3252f6da0633 Mon Sep 17 00:00:00 2001
From: Marco Matthies <71844+marcom@users.noreply.github.com>
Date: Mon, 29 Jul 2024 21:50:49 +0200
Subject: [PATCH] Rework functions for FarmPlot and crop models

- `stepagent!` for FarmPlot that delegates to crop state stepagent!
  function

- `harvest!`, `sow!` functions work on a `FarmPlot`, and delegate to
  the respective methods acting on a `CropState`
---
 src/crop/almass.jl     | 36 ++++++++++++++++--------------------
 src/crop/farmplot.jl   | 30 ++++++++++++++++++++++++++++++
 src/crop/simplecrop.jl | 34 ++++++++++++++++++++++++++++++----
 src/farm/farm.jl       |  3 ++-
 test/crop_tests.jl     |  2 +-
 5 files changed, 79 insertions(+), 26 deletions(-)

diff --git a/src/crop/almass.jl b/src/crop/almass.jl
index a306348..9203323 100644
--- a/src/crop/almass.jl
+++ b/src/crop/almass.jl
@@ -9,7 +9,6 @@ module ALMaSS
 
 using Persefone:
     Management,
-    FarmPlot,
     Length,
     cm,
     SimulationModel,
@@ -74,8 +73,8 @@ cropname(ct::CropType) = ct.name
 """
     CropState
 
-The state data for an ALMaSS vegetation point calculation as used in
-FarmPlot.
+The state data for an ALMaSS vegetation point calculation.  Usually
+part of a `FarmPlot`.
 """
 mutable struct CropState
     #TODO add Unitful
@@ -168,15 +167,14 @@ function readcropparameters(generalcropfile::String, growthfile::String)
 end
 
 """
-    stepagent!(farmplot, model)
+    stepagent!(cropstate, model)
 
 Update a farm plot by one day.
 """
-function stepagent!(farmplot::FarmPlot{CropState}, model::SimulationModel)
+function stepagent!(cs::CropState, model::SimulationModel)
     # update growing degree days
     # if no crop-specific base temperature is given, default to 5°C
     # (https://www.eea.europa.eu/publications/europes-changing-climate-hazards-1/heat-and-cold/heat-and-cold-2014-mean)
-    cs = farmplot.crop_state
     basetemp = cs.croptype.mingrowthtemp
     ismissing(basetemp) && (basetemp = 5.0)
     gdd = (maxtemp(model)+mintemp(model))/2 - basetemp
@@ -185,19 +183,18 @@ function stepagent!(farmplot::FarmPlot{CropState}, model::SimulationModel)
     monthday(model.date) == (1,1) && (cs.phase = ALMaSS.janfirst)
     monthday(model.date) == (3,1) && (cs.phase = ALMaSS.marchfirst)
     # update crop growth
-    growcrop!(farmplot, model)
+    growcrop!(cs, model)
 end
 
 
 ## CROP MANAGEMENT AND GROWTH FUNCTIONS
 
 """
-    sow!(cropname, farmplot, model)
+    sow!(cropstate, model, cropname)
 
-Sow the specified crop on this farmplot.
+Change the cropstate to sow the specified crop.
 """
-function sow!(cropname::String, farmplot::FarmPlot{CropState}, model::SimulationModel)
-    cs = farmplot.crop_state
+function sow!(cs::CropState, model::SimulationModel, cropname::String)
     createevent!(model, farmplot.pixels, sowing)
     cs.croptype = model.crops[cropname]
     cs.phase = ALMaSS.sow
@@ -205,12 +202,11 @@ function sow!(cropname::String, farmplot::FarmPlot{CropState}, model::Simulation
 end
 
 """
-    harvest!(farmplot, model)
+    harvest!(cropstate, model)
 
-Harvest the crop on this farmplot.
+Harvest the crop of this cropstate.
 """
-function harvest!(farmplot::FarmPlot{CropState}, model::SimulationModel)
-    cs = farmplot.crop_state
+function harvest!(cs::CropState, model::SimulationModel)
     createevent!(model, farmplot.pixels, harvesting)
     cs.phase in [ALMaSS.harvest1, ALMaSS.harvest2] ?
         cs.phase = ALMaSS.harvest2 :
@@ -224,13 +220,13 @@ end
 #TODO till!()
 
 """
-    growcrop!(farmplot, model)
+    growcrop!(cropstate, model)
 
-Apply the relevant crop growth model to update the plants on this farm plot.
-Currently only supports the ALMaSS crop growth model by Topping et al.
+Apply the relevant crop growth model to update the plants crop state
+on this farm plot.  Implements the ALMaSS crop growth model by Topping
+et al.
 """
-function growcrop!(farmplot::FarmPlot{CropState}, model::SimulationModel)
-    cs = farmplot.crop_state
+function growcrop!(cs::CropState, model::SimulationModel)
     fertiliser in cs.events ?
         curve = cs.croptype.lownutrientgrowth :
         curve = cs.croptype.highnutrientgrowth
diff --git a/src/crop/farmplot.jl b/src/crop/farmplot.jl
index 018ec54..b1c479a 100644
--- a/src/crop/farmplot.jl
+++ b/src/crop/farmplot.jl
@@ -15,6 +15,36 @@ cropheight(f::FarmPlot{T}) where {T} = cropheight(f.crop_state)
 cropcover(f::FarmPlot{T}) where {T} = cropcover(f.crop_state)
 cropyield(f::FarmPlot{T}) where {T} = cropyield(f.crop_state)
 
+"""
+    stepagent!(farmplot, model)
+
+Update a farm plot by one day.
+"""
+function stepagent!(farmplot::FarmPlot{T}, model::SimulationModel) where T
+    stepagent!(farmplot.crop_state, model)
+end
+
+"""
+    sow!(farmplot, model, cropname)
+
+Sow the specified crop on the farmplot.
+"""
+function sow!(farmplot::FarmPlot, model::SimulationModel, cropname::String)
+    createevent!(model, farmplot.pixels, sowing)
+    sow!(farmplot.crop_state, model, cropname)
+    #XXX test if the crop is sowable?
+end
+
+"""
+    harvest!(farmplot, model)
+
+Harvest the crop of this farmplot.
+"""
+function harvest!(farmplot::FarmPlot{T}, model::SimulationModel) where T
+    createevent!(model, farmplot.pixels, harvesting)
+    harvest!(farmplot.crop_state, model)  # TODO: multiply with area to return units of `g`
+end
+
 ## UTILITY FUNCTIONS
 
 """
diff --git a/src/crop/simplecrop.jl b/src/crop/simplecrop.jl
index dead41f..6bfe858 100644
--- a/src/crop/simplecrop.jl
+++ b/src/crop/simplecrop.jl
@@ -3,7 +3,7 @@ module SimpleCrop
 using Persefone:
     FarmPlot,
     Length,
-    m,
+    cm,
     SimulationModel
 
 import Persefone:
@@ -35,10 +35,36 @@ cropyield(cs::CropState) = 0.0  # TODO: units?
 """
     stepagent!(farmplot, model)
 
-Update a farm plot by one day.
+Update a crop state by one day.
 """
-function stepagent!(farmplot::FarmPlot{CropState}, model::SimulationModel)
-    # TODO: do something simple
+function stepagent!(cs::CropState, model::SimulationModel)
+    # height undergoes random diffusion, bounded by 0
+    delta_height = (2 * rand() - 1) * cm
+    cs.height = max(0.0cm, cs.height + delta_height)
+end
+
+"""
+    sow!(cropstate, model, cropname)
+
+Change the cropstate to sow the specified crop.
+"""
+function sow!(cs::CropState, model::SimulationModel, cropname::String)
+    cs.croptype = model.crops[cropname]
+    cs.height = 0cm
+end
+
+"""
+    harvest!(cropstate, model)
+
+Harvest the crop of this cropstate.
+"""
+function harvest!(cs::CropState, model::SimulationModel)
+    # TODO: set cs.croptype ?
+    # TODO: this 1.0g/cm/m^2 height_to_yield factor should be a param
+    #       for every crop type
+    yield = cs.height * 1.0g/cm/m^2
+    cs.height = 0cm
+    return yield
 end
 
 end # module SimpleCrop
diff --git a/src/farm/farm.jl b/src/farm/farm.jl
index e9e70c3..6d7b158 100644
--- a/src/farm/farm.jl
+++ b/src/farm/farm.jl
@@ -12,7 +12,8 @@ mutable struct Farmer <: ModelAgent
     #XXX make this into an abstract type and create subtypes for different
     # farm submodels? (#69)
     const id::Int64
-    fields::Vector{ALMaSS.FarmPlot}
+    # TODO: hardcoded ALMaSS crop model
+    fields::Vector{FarmPlot{ALMaSS.CropState}}
     croprotation::Vector{ALMaSS.CropType}
     #TODO add AES
 end
diff --git a/test/crop_tests.jl b/test/crop_tests.jl
index 1cdbe4f..9146925 100644
--- a/test/crop_tests.jl
+++ b/test/crop_tests.jl
@@ -34,7 +34,7 @@ end
 
 @testset "Submodule SimpleCrop" begin
     ct = Ps.SimpleCrop.CropType("olive tree")
-    fp = FarmPlot(0, [(0,0)], Ps.SimpleCrop.CropState(ct, 0.0m))
+    fp = FarmPlot(0, [(0,0)], Ps.SimpleCrop.CropState(ct, 0.0cm))
     @test fp isa FarmPlot
     @test fp isa FarmPlot{Ps.SimpleCrop.CropState}
     @test croptype(fp) isa Ps.SimpleCrop.CropType
-- 
GitLab