From 49f84fc04d58ccf4dea843d26990a7754cf0d722 Mon Sep 17 00:00:00 2001
From: Daniel Vedder <daniel.vedder@idiv.de>
Date: Thu, 3 Aug 2023 20:56:00 +0200
Subject: [PATCH] Wrote code to read in crop data files

---
 src/crop/crops.jl   | 158 ++++++++++++++++++++++++++------------------
 src/parameters.toml |   6 +-
 2 files changed, 99 insertions(+), 65 deletions(-)

diff --git a/src/crop/crops.jl b/src/crop/crops.jl
index 036cb1f..7f51beb 100644
--- a/src/crop/crops.jl
+++ b/src/crop/crops.jl
@@ -3,85 +3,117 @@
 ### This file is responsible for managing the crop growth modules.
 ###
 
-@enum GrowthPhase janfirst sow marchfirst harvest1 harvest2 vegphase
+#TODO write tests for input functions
+#TODO write actual growth function
 
 """
-    CropType
+    GrowthPhase
 
-The type struct for all crops. Currently follows the crop
-growth model as implemented in ALMaSS.
+ALMaSS crop growth curves are split into five phases, triggered by
+seasonal dates or agricultural events.
 """
-@kwdef struct CropType
-    name::String
-    sowdate::Date
-    harvestdate::Date
-    mingrowthtemp::Float64
-    inflectionpoints::Dict{GrowthPhase,Vector{Float64}}
-    slopes::Dict{GrowthPhase,Vector{Float64}}
-    #issowable::Union{Function,Bool}=true
+@enum GrowthPhase janfirst sow marchfirst harvest1 harvest2
+
+"""
+    CropCurveParams
+
+The values in this struct define one crop growth curve.
+"""
+struct CropCurveParams
+    curveID::Int
+    highnutrients::Bool
+    GDD::Dict{GrowthPhase,Vector{Float64}}
+    LAItotal::Dict{GrowthPhase,Vector{Float64}}
+    LAIgreen::Dict{GrowthPhase,Vector{Float64}}
+    height::Dict{GrowthPhase,Vector{Float64}}
 end
 
-let croptypes = Dict{String,CropType}()
-    global function registercrop(ct::CropType)
-        croptypes[ct.name] = ct
-    end
+"""
+    CropType
 
-    global function getcrop(name::String)
-        croptypes[name]
-    end
+The type struct for all crops. Currently follows the crop growth model as
+implemented in ALMaSS.
+"""
+struct CropType
+    name::String
+    minsowdate::Union{Missing,Date}
+    maxsowdate::Union{Missing,Date}
+    minharvestdate::Union{Missing,Date}
+    maxharvestdate::Union{Missing,Date}
+    mingrowthtemp::Union{Missing,Float64}
+    highnutrientgrowth::Union{Missing,CropCurveParams}
+    lownutrientgrowth::Union{Missing,CropCurveParams}
+    #issowable::Union{Function,Bool}
 end
 
 """
-    @dday(datestring)
+    Base.tryparse(type, str)
 
-A utility macro to make it quicker to create dates in the
-format "21 August".
+Extend `tryparse` to allow parsing GrowthPhase values.
+(Needed to read in the CSV parameter file.)
 """
-macro dday(date::String)
-    :(Date($(date), dateformat"d U"))
+function Base.tryparse(type::Type{GrowthPhase}, str::String)
+    str == "janfirst" ? janfirst :
+        str == "sow" ? sow :
+        str == "marchfirst" ? marchfirst :
+        str == "harvest1" ? harvest1 :
+        str == "harvest2" ? harvest2 :
+        nothing
 end
 
-let croplist = Dict{String,CropType}()
-    
-    global function newcrop(;kwargs...)
-        crop = CropType(;kwargs...)
-        croplist[crop.name] = crop
-    end
+"""
+    buildgrowthcurve(data)
 
-    global function cropdefinition(name)
-        croplist[name]
+Convert a list of rows from the crop growth data into a CropCurveParams object.
+"""
+function buildgrowthcurve(data::Vector{CSV.Row})
+    isempty(data) && return missing
+    GDD = Dict(janfirst=>Vector{Float64}(), sow=>Vector{Float64}(),
+               marchfirst=>Vector{Float64}(), harvest1=>Vector{Float64}(),
+               harvest2=>Vector{Float64}())
+    LAItotal = Dict(janfirst=>Vector{Float64}(), sow=>Vector{Float64}(),
+                    marchfirst=>Vector{Float64}(), harvest1=>Vector{Float64}(),
+                    harvest2=>Vector{Float64}())
+    LAIgreen = Dict(janfirst=>Vector{Float64}(), sow=>Vector{Float64}(),
+                    marchfirst=>Vector{Float64}(), harvest1=>Vector{Float64}(),
+                    harvest2=>Vector{Float64}())
+    height = Dict(janfirst=>Vector{Float64}(), sow=>Vector{Float64}(),
+                  marchfirst=>Vector{Float64}(), harvest1=>Vector{Float64}(),
+                  harvest2=>Vector{Float64}())
+    for e in data        
+        append!(GDD[e.growth_phase], e.GDD)
+        append!(LAItotal[e.growth_phase], e.LAI_total)
+        append!(LAIgreen[e.growth_phase], e.LAI_green)
+        append!(height[e.growth_phase], e.height)
     end
+    CropCurveParams(data[1].curve_id, data[1].nutrient_status=="high",
+                    GDD, LAItotal, LAIgreen, height)
 end
 
-#
-# XXX It would be nicer to be able to define crops with a macro
-# (like @species), but since macros are so tricky to get right,
-# I'll leave that for later.
-#
-# """
-#     @crop
-#
-# A macro to create and register a new crop type. Pass a list
-# of keyword arguments to instantiate each field of the
-# [`CropType`](@ref) struct.
-# """
-# macro crop(;kwargs...) # macros don't take kwargs
-#     quote
-#         registercrop(CropType($(esc(kwargs))))
-#     end
-# end
-#
-# @crop "winter wheat" begin
-#     minsowdate = @dday("1 November")
-#     maxsowdate = @dday("31 November")
-#     growthparameter = 1.0
-#     issowable = true
-# end
-
-newcrop(name="winter wheat",
-        sowdate = @dday("1 November"),
-        harvestdate = @dday("1 July"),
-        mingrowthtemp = 5.0,
-        growthparameter = 1.0,
-        issowable = true)
+"""
+    readcropparameters(generalcropfile, cropgrowthfile)
 
+Parse a CSV file containing the required parameter values for each crop
+(as produced from the original ALMaSS file by `convert_almass_data.py`).
+"""
+function readcropparameters(generalcropfile::String, cropgrowthfile::String)
+    @debug "Reading crop parameters"
+    cropdata = CSV.File(generalcropfile, missingstring="NA", dateformat="d U",
+                        types=[String,Date,Date,Date,Date,Float64])
+    growthdata = CSV.File(cropgrowthfile, missingstring="NA",
+                          types=[Int,String,String,GrowthPhase,String,
+                                 Float64,Float64,Float64,Float64])
+    croptypes = Vector{CropType}()
+    for crop in cropdata
+        cropgrowthdata = growthdata |> filter(x -> !ismissing(x.crop_name) &&
+                                              x.crop_name == crop.name)
+        highnuts = buildgrowthcurve(cropgrowthdata |>
+                                    filter(x -> x.nutrient_status=="high"))
+        lownuts = buildgrowthcurve(cropgrowthdata |>
+                                   filter(x -> x.nutrient_status=="low"))
+        append!(croptypes, [CropType(crop.name, crop.minsowdate, crop.maxsowdate,
+                                     crop.minharvestdate, crop.maxharvestdate,
+                                     crop.mingrowthtemp, highnuts, lownuts)])
+    end
+    croptypes
+end
diff --git a/src/parameters.toml b/src/parameters.toml
index 172575d..6b26cac 100644
--- a/src/parameters.toml
+++ b/src/parameters.toml
@@ -30,8 +30,10 @@ farmmodel = "FieldManager" # which version of the farm model to use (not yet imp
 targetspecies = ["Wolpertinger", "Wyvern"] # list of target species to simulate
 popoutfreq = "daily" # output frequency population-level data, daily/monthly/yearly/end/never
 indoutfreq = "end" # output frequency individual-level data, daily/monthly/yearly/end/never
-insectmodel = ["season", "habitat", "pesticides"] # which factors affect insect growth ("weather" is not yet implemented)
+insectmodel = ["season", "habitat", "pesticides", "weather"] # factors affecting insect growth
 	
 [crop]
-cropmodel = "linear" # crop growth model to use, "linear" or "aquacrop" (not yet implemented)
+cropmodel = "almass" # crop growth model to use, "almass" or "aquacrop"
+cropfile = "data/crop_data_general.csv" # file with general crop parameters
+cropgrowthfile = "data/almass_crop_growth_curves.csv" # file with crop growth parameters	
 
-- 
GitLab