diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b7ce0062ef1f0004ad86815c06c5b1fd5dd198d..039086baf9ea6f9fc08215e6d8f7478aad02c6a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 *Aim: 3 species, 2 crop growth models, farm model, GAEC scenarios, experimental analysis* -## [0.6.0] - unreleased - *Plan: decouple CairoMakie (#81), fix & test ALMaSS, set up first experiments* +## [0.6.0] + +**This point release re-implements the ALMaSS crop model** + + ### Added - `crop.cropdirectory` parameter specifies folder in which all crop data files for the @@ -28,6 +31,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `simulate()` and `initialise()` now take a `params` keyword argument that can be used to override parameters from other input sources +- The ALMaSS crop data config file + `data/crops/almass/crop_data_general.csv` now has extra columns for + `is_c4_plant`, `sowingdensity`, and `phase_before_harvest` + ### Deprecated ### Removed @@ -37,6 +44,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- The implementation of the ALMaSS vegetation model in Persefone has + been completely rewritten, hopefully more faithfully reproducing the + logic in ALMaSS. The resulting plant heights are now more realistic + and do not produce the extreme plant heights seen previously (which + was due to an erroneous interpretation of the ALMaSS growth curves). + + --- ## [0.5.5] - 09-08-2024 diff --git a/Project.toml b/Project.toml index b34a8e3bb74ffe2a863e273a054352ef359724ac..da7164d8dd4153c9eb27a504f91d966a84799468 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Persefone" uuid = "039acd1d-2a07-4b33-b082-83a1ff0fd136" authors = ["Daniel Vedder <daniel.vedder@idiv.de>"] -version = "0.5.5" +version = "0.6.0" [deps] ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" diff --git a/data/crops/almass/crop_data_general.csv b/data/crops/almass/crop_data_general.csv index c779dec5fa35c5777bb5363b88a81dbd8556bddc..1b34e04465790bf212b5622cdcc6d8ff1a651f0a 100644 --- a/data/crops/almass/crop_data_general.csv +++ b/data/crops/almass/crop_data_general.csv @@ -1,28 +1,28 @@ -name,minsowdate,maxsowdate,minharvestdate,maxharvestdate,mingrowthtemp,group -"spring rape",NA,NA,NA,NA,NA,"grain" -"winter rape","20 August","25 August",NA,NA,NA,"grain" -"winter wheat","15 October","31 October",NA,NA,0,"grain" -"spring wheat",NA,NA,NA,NA,NA,"grain" -"winter barley","15 September","30 September",NA,NA,0,"grain" -"spring barley","1 March","10 April",NA,NA,0,"grain" -"undersown spring barley",NA,NA,NA,NA,0,"grain" -"winter rye","23 September","15 October",NA,NA,NA,"grain" -"triticale","25 September","10 October",NA,NA,NA,"grain" -"oats",NA,NA,NA,NA,NA,"grain" -"maize","15 April","30 April",NA,NA,8,"grain" -"potatoes",NA,NA,NA,NA,4,"root" -"carrots",NA,NA,NA,NA,NA,"root" -"beet","15 March","10 May",NA,NA,NA,"root" -"sunflower","25 March","15 April",NA,NA,NA,"other" -"lucerne",NA,NA,NA,NA,NA,"legumes" -"peas/beans","15 February","15 March",NA,NA,5,"legumes" -"silage clover/grass",NA,NA,NA,NA,NA,"legumes" -"fodder/clover",NA,NA,NA,NA,NA,"legumes" -"lawn",NA,NA,NA,NA,NA,"grass" -"permanent grassland (grazed)",NA,NA,NA,NA,NA,"grass" -"permanent grassland (seeded)",NA,NA,NA,NA,NA,"grass" -"permanent grassland (low yield)",NA,NA,NA,NA,NA,"grass" -"permanent set-aside",NA,NA,NA,NA,NA,"semi-natural" -"natural grass",NA,NA,NA,NA,NA,"semi-natural" -"no growth",NA,NA,NA,NA,NA,"semi-natural" -"heath",NA,NA,NA,NA,NA,"semi-natural" +name,minsowdate,maxsowdate,minharvestdate,maxharvestdate,mingrowthtemp,group,biomass_scale,is_c4_plant,sowingdensity,phase_before_harvest +spring rape,NA,NA,NA,NA,NA,grain,1.071,false,75,marchfirst +winter rape,20 August,25 August,NA,NA,NA,grain,1.071,false,75,marchfirst +winter wheat,15 October,31 October,NA,NA,0,grain,1.0,false,400,marchfirst +spring wheat,NA,NA,NA,NA,NA,grain,1.0,false,475,marchfirst +winter barley,15 September,30 September,NA,NA,0,grain,0.857,false,340,marchfirst +spring barley,1 March,10 April,NA,NA,0,grain,0.857,false,360,marchfirst +undersown spring barley,NA,NA,NA,NA,0,grain,0.857,false,NA,marchfirst +winter rye,23 September,15 October,NA,NA,NA,grain,0.857,false,290,marchfirst +triticale,25 September,10 October,NA,NA,NA,grain,1.0,false,320,marchfirst +oats,NA,NA,NA,NA,NA,grain,0.857,false,385,marchfirst +maize,15 April,30 April,NA,NA,8,grain,1.0,true,13,sow +potatoes,NA,NA,NA,NA,4,root,0.857,false,5,marchfirst +carrots,NA,NA,NA,NA,NA,root,0.7857,false,160,marchfirst +beet,15 March,10 May,NA,NA,NA,root,0.857,false,16,marchfirst +sunflower,25 March,15 April,NA,NA,NA,other,1.0,false,8,marchfirst +lucerne,NA,NA,NA,NA,NA,legumes,1.2,false,NA,marchfirst +peas/beans,15 February,15 March,NA,NA,5,legumes,0.857,false,35/80,marchfirst +silage clover/grass,NA,NA,NA,NA,NA,legumes,1.1,false,NA,marchfirst +fodder/clover,NA,NA,NA,NA,NA,legumes,1.2,false,NA,marchfirst +lawn,NA,NA,NA,NA,NA,grass,0.5,false,NA,marchfirst +permanent grassland (grazed),NA,NA,NA,NA,NA,grass,1.1,false,NA,NA +permanent grassland (seeded),NA,NA,NA,NA,NA,grass,1.1,false,NA,NA +permanent grassland (low yield),NA,NA,NA,NA,NA,grass,1.0,false,NA,NA +permanent set-aside,NA,NA,NA,NA,NA,semi-natural,0.7857,false,NA,NA +natural grass,NA,NA,NA,NA,NA,semi-natural,0.567,false,NA,NA +no growth,NA,NA,NA,NA,NA,semi-natural,0.0,false,NA,NA +heath,NA,NA,NA,NA,NA,semi-natural,0.567,false,NA,NA diff --git a/src/crop/almass.jl b/src/crop/almass.jl index a3d47c238c953b703ad3dccd72b6eac9d0efec7d..59b94b9f0cf6ace3c5ebd6869dc10aa56e6b3542 100644 --- a/src/crop/almass.jl +++ b/src/crop/almass.jl @@ -3,6 +3,31 @@ ### This file is responsible for managing the crop growth modules. ### +# Some functions translated from ALMaSS for use in Persefone.jl +# +# ALMaSS is licensed as follows: +# +# Copyright (c) 2011, Christopher John Topping, Aarhus University +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided +# that the following conditions are met: +# +# Redistributions of source code must retain the above copyright notice, this list of conditions and the +# following disclaimer. +# Redistributions in binary form must reproduce the above copyright notice, this list of conditions and +# the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + #FIXME Looking at fields.pdf in the Persefone output, crop maturity is not recognised # properly, so most crops (except maize) are never planted. At the same time, maize never # seems to start growing. @@ -16,14 +41,17 @@ const CROPFILE = "crop_data_general.csv" const GROWTHFILE = "crop_growth_curves.csv" using Persefone: + @rand, + @u_str, AnnualDate, Management, - Length, cm, SimulationModel, fertiliser, maxtemp, - mintemp + mintemp, + year, + dayofyear import Persefone: stepagent!, @@ -39,6 +67,10 @@ import Persefone: using Dates: Date, month, monthday using CSV: CSV +using Unitful: unit + +# We can't use Length{Float64} as that is a Union type +const Tlength = typeof(1.0cm) """ GrowthPhase @@ -58,9 +90,24 @@ 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{Length{Float64}}} + LAItotal::Dict{GrowthPhase, Vector{Float64}} + LAIgreen::Dict{GrowthPhase, Vector{Float64}} + height::Dict{GrowthPhase, Vector{typeof(1.0cm)}} +end + +function Base.show(io::IO, ::MIME"text/plain", curve::CropCurveParams) + println(io, "CropCurveParams") + println(io, " curveID = ", curve.curveID) + println(io, " highnutrients = ", curve.highnutrients) + println(io, "\nGDD:") + show(io, MIME"text/plain"(), curve.GDD) + println(io, "\n\nLAItotal:") + show(io, MIME"text/plain"(), curve.LAItotal) + println(io, "\n\nLAIgreen:") + show(io, MIME"text/plain"(), curve.LAIgreen) + println(io, "\n\nheight:") + show(io, MIME"text/plain"(), curve.height) + return nothing end """ @@ -74,6 +121,8 @@ struct CropType # not here. Also, we need to harmonise crops across the crop growth models. name::String group::String + is_c4_plant::Bool # false means it is a C3 plant + phase_before_harvest::Union{Missing,GrowthPhase} # the phase where the crop can be harvested minsowdate::Union{Missing,AnnualDate} maxsowdate::Union{Missing,AnnualDate} minharvestdate::Union{Missing,AnnualDate} @@ -81,6 +130,21 @@ struct CropType mingrowthtemp::Union{Missing,Float64} highnutrientgrowth::Union{Missing,CropCurveParams} lownutrientgrowth::Union{Missing,CropCurveParams} + biomass_scale::Float64 +end + +function Base.show(io::IO, ::MIME"text/plain", ct::CropType) + println(io, "CropType") + println(io, "=========") + for f in filter(x -> ! (x in (:highnutrientgrowth, :lownutrientgrowth)), fieldnames(typeof(ct))) + println(io, " $f = ", string(getfield(ct, f))) + end + println(io, "\n\nlownutrientgrowth:") + println(io, "--------------------") + show(io, MIME"text/plain"(), ct.lownutrientgrowth) + println(io, "\n\nhighnutrientgrowth:") + println(io, "--------------------") + show(io, MIME"text/plain"(), ct.highnutrientgrowth) end cropname(ct::CropType) = ct.name @@ -91,25 +155,197 @@ cropname(ct::CropType) = ct.name The state data for an ALMaSS vegetation point calculation. Usually part of a `FarmPlot`. """ -mutable struct CropState - #TODO add Unitful +@kwdef mutable struct CropState + # Class in original ALMaSS code: + # `VegElement` from `Landscape/Elements.h`, line 601 croptype::CropType + events::Vector{Management} = Management[] + phase::GrowthPhase - growingdegreedays::Float64 - height::Length{Float64} - LAItotal::Float64 - LAIgreen::Float64 - #biomass::Float64 #XXX I need to figure out how to calculate this - mature::Bool #TODO how do we determine this? - events::Vector{Management} #FIXME does this do anything? + vegddegs::Float64 = 0 # Vegetation growing degree days since sowing + ddegs::Float64 = 0 # Growing degree days in current phase + yddegs::Float64 = 0 # Growing degree days in current phase for previous day + LAgreen::Float64 = 0 + LAtotal::Float64 = 0 + oldLAtotal::Float64 = 0 + veg_height::Tlength = 0.0cm + newgrowth::Float64 = 0 + veg_cover::Float64 = 0 + veg_biomass::Float64 = 0 + green_biomass::Float64 = 0 + dead_biomass::Float64 = 0 + + growth_scaler::Float64 = 1.0 # TODO: where is this set? + forced_phase_shift::Bool = false # TODO: what does this do? + + # TODO: meaning of force_* fields ? + force_growth::Bool = false + force_LAgreen::Float64 = 0 + force_LAtotal::Float64 = 0 + force_veg_height::Tlength = 0.0cm end croptype(cs::CropState) = cs.croptype cropname(cs::CropState) = cropname(croptype(cs)) -cropheight(cs::CropState) = cs.height -cropcover(cs::CropState) = 0.0 # TODO: related to LAItotal, LAIgreen? -cropyield(cs::CropState) = 0.0 # TODO: units? needs biomass? -isharvestable(cs::CropState) = cs.mature +cropheight(cs::CropState) = cs.veg_height +cropcover(cs::CropState) = cs.veg_cover +cropyield(cs::CropState) = cs.veg_biomass # TODO: correct? units? dry or wet? +function isharvestable(cs::CropState) + phase_before_harvest = croptype(cs).phase_before_harvest + if ismissing(phase_before_harvest) || cs.phase != phase_before_harvest + return false + end + curve = growthcurve(cs) + gdd = curve.GDD[phase_before_harvest] + return cs.ddegs >= last(gdd) +end + +# Constant in original ALMaSS code: +# `EL_VEG_START_LAIT` from `Landscape/Elements.cpp`, line 238-239 +const VEG_START_LAIT::Float64 = 1.08 + +# Constant in original ALMaSS code: +# `EL_VEG_HEIGHTSCALE` from `Landscape/Elements.cpp`, line 242-243 +const VEG_HEIGHTSCALE::Tlength = 16.0cm + +# Constant in original ALMaSS code: +# `cfg_beer_law_extinction_coef` from `Landscape/Elements.cpp`, line 139 +# TODO: units +const BEER_LAW_EXTINCTION_COEF::Float64 = 0.6 + +# TODO: units +"Temperature to solar conversion factor for C3 plants." +const temperature_to_solar_conversion_c3 = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # -30°C to -21°C + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # -20°C to -11°C + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # -10°C to -1°C + 0, 0, 0, 0, 0, 0.28, 0.56, 0.84, 1.12, 1.4, # 0°C to 9°C + 1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4, # 10°C to 19°C + 1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.26, 1.12, 0.98, 0.84, # 20°C to 29°C + 0.7, 0.56, 0.42, 0.28, 0.14, 0, 0, 0, 0, 0, # 30°C to 39°C + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 40°C to 49°C + 0 # 50°C +] + +# TODO: units +"Temperature to solar conversion factor for C4 plants." +const temperature_to_solar_conversion_c4 = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # -30°C to -21°C + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # -20°C to -11°C + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # -10°C to -1°C + 0, 0, 0, 0, 0, 0, 0, 0, 0.242857, 0.485714, # 0°C to 9°C + 0.728571, 0.971429, 1.214286, 1.457143, 1.7, 1.7, 1.7, 1.7, 1.7, 1.7, # 10°C to 19°C + 1.7, 1.7, 1.7, 1.7, 1.7, 1.7, 1.7, 1.7, 1.7, 1.7, # 20°C to 29°C + 1.7, 1.7, 1.7, 1.7, 1.7, 1.7, 1.53, 1.36, 1.19, 1.02, # 30°C to 39°C + 0.85, 0.68, 0.51, 0.34, 0.17, 0, 0, 0, 0, 0, # 40°C to 49°C + 0 # 50°C +] + +""" + solar_conversion_c3(temperature) + +Solar conversion factor (no units) for C3 plants. +""" +function solar_conversion_c3(temperature) + if temperature < -30 || temperature > 50 + return zero(eltype(temperature_to_solar_conversion_c3)) + end + idx = Int(floor(0.5 + temperature)) + 30 + 1 + return temperature_to_solar_conversion_c3[idx] +end + +""" + solar_conversion_c3(temperature) + +Solar conversion factor (no units) for C4 plants. +""" +function solar_conversion_c4(temperature) + if temperature < -30 || temperature > 50 + return zero(eltype(temperature_to_solar_conversion_c4)) + end + idx = Int(floor(0.5 + temperature)) + 30 + 1 + return temperature_to_solar_conversion_c4[idx] +end + +# Below is the data for insolation in MJ per m2 for Ødum fitted to 1961-1990 +# by Olesen (1991) +# TODO: unit is u"MJ/m^2" +const global_insolation = [ + 0.91, 0.921585, 0.935138, 0.950669, 0.968188, + 0.987706, 1.009231, 1.032774, 1.058346, 1.085956, + 1.115614, 1.14733, 1.181113, 1.216974, 1.254921, + 1.294964, 1.337112, 1.381373, 1.427757, 1.476271, + 1.526923, 1.57972, 1.634671, 1.691781, 1.751057, + 1.812504, 1.876128, 1.941934, 2.009926, 2.080107, + 2.15248, 2.227048, 2.303812, 2.382774, 2.463933, + 2.547289, 2.63284, 2.720585, 2.81052, 2.902642, + 2.996945, 3.093425, 3.192073, 3.292884, 3.395847, + 3.500954, 3.608194, 3.717555, 3.829024, 3.942587, + 4.05823, 4.175935, 4.295687, 4.417465, 4.541252, + 4.667024, 4.794762, 4.924441, 5.056036, 5.189522, + 5.324873, 5.462059, 5.601051, 5.741819, 5.884329, + 6.02855, 6.174446, 6.321981, 6.471118, 6.621819, + 6.774044, 6.927753, 7.082902, 7.239449, 7.397349, + 7.556555, 7.717022, 7.8787, 8.041541, 8.205493, + 8.370505, 8.536524, 8.703496, 8.871365, 9.040077, + 9.209573, 9.379796, 9.550686, 9.722183, 9.894227, + 10.06675, 10.2397, 10.41301, 10.58661, 10.76044, + 10.93443, 11.10852, 11.28264, 11.45671, 11.63068, + 11.80448, 11.97802, 12.15125, 12.3241, 12.49648, + 12.66834, 12.8396, 13.01019, 13.18004, 13.34907, + 13.51721, 13.6844, 13.85056, 14.01561, 14.17949, + 14.34212, 14.50344, 14.66337, 14.82184, 14.97877, + 15.13411, 15.28778, 15.43971, 15.58983, 15.73807, + 15.88437, 16.02866, 16.17087, 16.31093, 16.44879, + 16.58439, 16.71764, 16.8485, 16.97691, 17.1028, + 17.22612, 17.3468, 17.46479, 17.58005, 17.6925, + 17.80211, 17.90881, 18.01256, 18.11332, 18.21102, + 18.30564, 18.39712, 18.48542, 18.5705, 18.65233, + 18.73086, 18.80605, 18.87788, 18.94632, 19.01132, + 19.07286, 19.13092, 19.18546, 19.23647, 19.28393, + 19.3278, 19.36809, 19.40475, 19.4378, 19.4672, + 19.49296, 19.51506, 19.53349, 19.54825, 19.55934, + 19.56676, 19.5705, 19.57058, 19.56698, 19.55973, + 19.54883, 19.53428, 19.51611, 19.49432, 19.46893, + 19.43996, 19.40743, 19.37136, 19.33177, 19.28868, + 19.24213, 19.19214, 19.13873, 19.08195, 19.02183, + 18.95839, 18.89168, 18.82173, 18.74858, 18.67228, + 18.59286, 18.51036, 18.42484, 18.33634, 18.2449, + 18.15058, 18.05342, 17.95348, 17.8508, 17.74545, + 17.63746, 17.52691, 17.41384, 17.29832, 17.18039, + 17.06012, 16.93757, 16.8128, 16.68586, 16.55683, + 16.42576, 16.29271, 16.15775, 16.02094, 15.88235, + 15.74203, 15.60006, 15.4565, 15.3114, 15.16485, + 15.0169, 14.86762, 14.71707, 14.56532, 14.41243, + 14.25848, 14.10352, 13.94762, 13.79084, 13.63325, + 13.47491, 13.31588, 13.15624, 12.99604, 12.83534, + 12.6742, 12.5127, 12.35088, 12.18881, 12.02654, + 11.86415, 11.70167, 11.53918, 11.37673, 11.21437, + 11.05216, 10.89016, 10.72841, 10.56697, 10.40589, + 10.24522, 10.08502, 9.925323, 9.766186, 9.607653, + 9.449772, 9.292586, 9.13614, 8.980476, 8.825635, + 8.67166, 8.518588, 8.366459, 8.215311, 8.06518, + 7.916101, 7.768108, 7.621236, 7.475515, 7.330978, + 7.187655, 7.045573, 6.904763, 6.765249, 6.62706, + 6.490218, 6.35475, 6.220676, 6.088021, 5.956804, + 5.827045, 5.698765, 5.571981, 5.44671, 5.32297, + 5.200776, 5.080142, 4.961083, 4.843613, 4.727743, + 4.613485, 4.50085, 4.38985, 4.280492, 4.172788, + 4.066743, 3.962368, 3.859668, 3.758651, 3.659322, + 3.561687, 3.465753, 3.371522, 3.279, 3.18819, + 3.099097, 3.011723, 2.926071, 2.842145, 2.759946, + 2.679477, 2.600739, 2.523735, 2.448466, 2.374933, + 2.303139, 2.233084, 2.16477, 2.098198, 2.033369, + 1.970285, 1.908946, 1.849355, 1.791512, 1.735419, + 1.681077, 1.628489, 1.577656, 1.528581, 1.481264, + 1.43571, 1.391919, 1.349896, 1.309643, 1.271164, + 1.234461, 1.199539, 1.166401, 1.135053, 1.105497, + 1.07774, 1.051786, 1.02764, 1.005309, 0.984798, + 0.966113, 0.94926, 0.934248, 0.921081, 0.909769, + 0.900317, 0.892735, 0.88703, 0.88321, 0.881284, + 0.881261, 0.883149, 0.886958, 0.892696, 0.900374, + 0.900374, # duplicated last datapoint in case the year has 366 days +] """ Base.tryparse(type, str) @@ -142,9 +378,9 @@ function buildgrowthcurve(data::Vector{CSV.Row}) LAIgreen = Dict(janfirst=>Vector{Float64}(), sow=>Vector{Float64}(), marchfirst=>Vector{Float64}(), harvest1=>Vector{Float64}(), harvest2=>Vector{Float64}()) - height = Dict(janfirst=>Vector{Length{Float64}}(), sow=>Vector{Length{Float64}}(), - marchfirst=>Vector{Length{Float64}}(), harvest1=>Vector{Length{Float64}}(), - harvest2=>Vector{Length{Float64}}()) + height = Dict(janfirst=>Vector{Tlength}(), sow=>Vector{Tlength}(), + marchfirst=>Vector{Tlength}(), harvest1=>Vector{Tlength}(), + harvest2=>Vector{Tlength}()) for e in data append!(GDD[e.growth_phase], e.GDD) append!(LAItotal[e.growth_phase], e.LAI_total) @@ -163,8 +399,11 @@ Parse a CSV file containing the required parameter values for each crop """ function readcropparameters(cropdirectory::String) @debug "Reading crop parameters" + # TODO: the second-last column (sowingdensity) uses `String` as + # type. This is because the entry for "peas/beans" has a value of + # "35/80" for the sowingdensity. cropdata = CSV.File(joinpath(cropdirectory, CROPFILE), missingstring="NA", - types=[String,AnnualDate,AnnualDate,AnnualDate,AnnualDate,Float64,String]) + types=[String,AnnualDate,AnnualDate,AnnualDate,AnnualDate,Float64,String,Float64,Bool,String,GrowthPhase]) growthdata = CSV.File(joinpath(cropdirectory, GROWTHFILE), missingstring="NA", types=[Int,String,String,GrowthPhase,String, Float64,Float64,Float64,Float64]) @@ -176,41 +415,428 @@ function readcropparameters(cropdirectory::String) filter(x -> x.nutrient_status=="high")) lownuts = buildgrowthcurve(cropgrowthdata |> filter(x -> x.nutrient_status=="low")) - # TODO: set crop group temporarily until there is a column in - # the csv file - crop_group = "CROP_GROUP_NOT_SET" - croptypes[crop.name] = CropType(crop.name, crop_group, crop.minsowdate, crop.maxsowdate, - crop.minharvestdate, crop.maxharvestdate, - crop.mingrowthtemp, highnuts, lownuts) + croptypes[crop.name] = CropType(crop.name, crop.group, crop.is_c4_plant, crop.phase_before_harvest, + crop.minsowdate, crop.maxsowdate, crop.minharvestdate, crop.maxharvestdate, + crop.mingrowthtemp, highnuts, lownuts, crop.biomass_scale) end croptypes end +""" + setphase!(cropstate, phase) + +Set the growth `phase` of an ALMaSS `cropstate`. +""" +function setphase!(cs::CropState, phase::GrowthPhase, model::SimulationModel) + # Function in original ALMaSS code: + # `void VegElement::SetGrowthPhase(int a_phase)` from `Landscape/Elements.cpp`, line 2098 + if phase == sow + cs.vegddegs = 0 + elseif phase == harvest1 + cs.vegddegs = -1 + end + if phase == janfirst + cs.forced_phase_shift = false + # TODO: Original comments from ALMaSS below, but do they make sense? + # + # If it is the first growth phase of the year then we might + # cause some unnecessary hops if e.g. our biomass is 0 and we + # suddenly jump up to 20 cm. To stop this from happening we + # check here and if our settings are lower than the targets we + # do nothing. + if isvalidstart(cs, phase) + if startvalue_veg_height(cs, phase) < cs.veg_height + # we are better off with the numbers we have to start with + cs.LAgreen = startvalue_LAgreen(cs, phase) + cs.LAtotal = startvalue_LAtotal(cs, phase) + cs.veg_height = startvalue_veg_height(cs, phase) + end + end + elseif isvalidstart(cs, phase) + cs.LAgreen = startvalue_LAgreen(cs, phase) + cs.LAtotal = startvalue_LAtotal(cs, phase) + cs.veg_height = startvalue_veg_height(cs, phase) + end + + cs.phase = phase + cs.yddegs = 0 + cs.ddegs = get_dddegs(model) + cs.force_growth = false + + if cs.phase == janfirst + # Original comment from ALMaSS: + # + # For some growth curves there is no growth in the first two + # months of the year. This will more likely than not cause a + # discontinuous jump in the growth curves come March + # first. `force_growth_spring_test()` tries to avoid that by + # checking for positive growth values for the January growth + # phase. If none are found, then it initializes a forced + # growth/transition to the March 1st starting values. + # + # Removal of this causes continuous increase in vegetation + # growth year on year for any curve that does not have a hard + # reset (e.g. harvest). + force_growth_spring_test!(cs, model) + end +end + +# Function in original ALMaSS code: +# `void VegElement::ForceGrowthSpringTest(void)` in `Landscape/Elements.cpp`, line 2162 +function force_growth_spring_test!(cs::CropState, model::SimulationModel) + # Original comments from ALMaSS: + # + # Check if there are any positive growth differentials in the + # curve for the first two months of the year. Do nothing if there + # is. If we have any positive growth then no need to force either + if (get_LAgreen_diff(cs, janfirst, 90000.0, 0.0) > 0.001 || + get_LAtotal_diff(cs, janfirst, 90000.0, 0.0) > 0.001 || + get_height_diff(cs, janfirst, 90000.0, 0.0) > 0.001cm) + return + end + # No growth, force it. + force_growth_initialize!(cs, model) +end + +# Function in original ALMaSS code: +# `void VegElement::ForceGrowthInitialize(void)` in `Landscape/Elements.cpp`, line 2177 +function force_growth_initialize!(cs::CropState, model::SimulationModel) + # Figure out what our target phase is. + if monthday(model.date) < (1, 3) + daysleft = (Date(year(model.date), 3, 1) - model.date).value + next_phase = marchfirst + elseif monthday(model.date) >= (11, 1) + # Adjusted from 365 to prevent occaisional negative values + daysleft = 366 - dayofyear(model.date) + next_phase = janfirst + else + return + end + + if daysleft <= 0 + @warn "Uh! Oh! This really shouldn't happen." + return + end + + if ! isvalidstart(cs, next_phase) + # If no valid starting values for next phase, then + # preinitialize the random starting values! Ie. make the + # choice here and then do not choose another set come next + # phase transition, but use the values we already got at that + # point in time. + LAtotal_target, LAgreen_target, veg_height_target = random_veg_start_values(model) + # TODO: ignoring Weed_target + else + # add +/- 20% variation + vari = (@rand() * 0.4) + 0.8 + # TODO: ignoring Weed_target + LAgreen_target = startvalue_LAgreen(cs, next_phase) * vari + LAtotal_target = startvalue_LAtotal(cs, next_phase) * vari + veg_height_target = startvalue_veg_height(cs, next_phase) * vari + end + + cs.force_growth = true + # TODO ignoring: cs.force_Weed = (Weed_target - cs.weed_biomass) / daysleft + cs.force_LAgreen = (LAgreen_target - cs.LAgreen) / daysleft + cs.force_LAtotal = (LAtotal_target - cs.LAtotal) / daysleft + cs.force_veg_height = (veg_height_target - cs.veg_height) / daysleft +end + +# Function in original ALMaSS code: +# `void VegElement::RandomVegStartValues(...)` in `Landscape/Elements.cpp`, line 2090 +function random_veg_start_values(model) + LAtotal = VEG_START_LAIT * ((@rand(-10:10) / 100.0) + 1.0) + LAgreen = LAtotal / 4 + veg_height = LAgreen * VEG_HEIGHTSCALE + # TODO ignoring `weed_biomass` + return LAtotal, LAgreen, veg_height +end + +function get_LAgreen_diff(cs::CropState, phase::GrowthPhase, ddegs::Float64, yddegs::Float64) + curve = growthcurve(cs) + dds = curve.GDD[phase] + slopes = curve.LAIgreen[phase] + return find_diff(dds, slopes, ddegs, yddegs) +end +get_LAgreen_diff(cs::CropState) = + get_LAgreen_diff(cs, cs.phase, cs.ddegs, cs.yddegs) + +function get_LAtotal_diff(cs::CropState, phase::GrowthPhase, ddegs::Float64, yddegs::Float64) + curve = growthcurve(cs) + dds = curve.GDD[phase] + slopes = curve.LAItotal[phase] + return find_diff(dds, slopes, ddegs, yddegs) +end +get_LAtotal_diff(cs::CropState) = + get_LAtotal_diff(cs, cs.phase, cs.ddegs, cs.yddegs) + +function get_height_diff(cs::CropState, phase::GrowthPhase, ddegs::Float64, yddegs::Float64) + curve = growthcurve(cs) + dds = curve.GDD[phase] + slopes = curve.height[phase] + return find_diff(dds, slopes, ddegs, yddegs) +end +get_height_diff(cs::CropState) = + get_height_diff(cs, cs.phase, cs.ddegs, cs.yddegs) + +# Function in original ALMaSS code: +# `double PlantGrowthData::FindDiff(...)` in `Landscape/Plants.cpp`, line 45 +function find_diff(dds::Vector{Float64}, slopes::Vector{T}, ddegs::Float64, yddegs::Float64) where {T} + # Comment in ALMaSS code: + # + # This is broken for growth curves where one can risk passing + # more than a single inflection point in the growth curve in a + # single day... + + # Note: the code is slightly different to the original ALMaSS + # code, which I think is caused by ALMaSS not storing the -1 entry + # in the dds array. + + if first(dds) == 99999.0 + return 0.0 * oneunit(T) # zero(T) does not work when slopes has units + end + + function get_index(array::Tarr, threshold::eltype(Tarr)) where {Tarr <: DenseArray} + idx = findfirst(x -> x > threshold, array) + if isnothing(idx) + idx = lastindex(array) + 1 + end + idx -= 1 + if idx < firstindex(array) + error("Index too small, idx = $idx") + end + return idx + end + + newindex = get_index(dds, ddegs) + oldindex = get_index(dds, yddegs) + + dds_newindex = dds[newindex] == -1.0 ? zero(eltype(dds)) : dds[newindex] + slopes_oldindex = dds[oldindex] == -1.0 ? zero(eltype(slopes)) : slopes[oldindex] + slopes_newindex = dds[newindex] == -1.0 ? zero(eltype(slopes)) : slopes[newindex] + + if newindex > oldindex + # We have passed an inflection point between today and yesterday. + # First add the increment from yesterdays day degree sum up to + # the inflection point. + dddif = dds_newindex - yddegs + diff = slopes_oldindex * dddif + + # Then from the inflection point up to today. + dddif = ddegs - dds_newindex + diff += slopes_newindex * dddif + else + # No inflection point passed + diff = slopes_newindex * (ddegs - yddegs) + end + return diff +end + +# Function in original ALMaSS code: +# `double Weather::GetDDDegs(long a_date)` in `Landscape/Weather.cpp`, line 205 +# TODO: original ALMaSS code does not use a minimum growth temperature? +get_dddegs(model::SimulationModel) = bounds(supply_temperature(model)) + +# TODO efficiency: the fertiliser check is done in many places +function growthcurve(cs::CropState) + if ismissing(cs.croptype.lownutrientgrowth) && ismissing(cs.croptype.highnutrientgrowth) + error("No growth curves available for cropstate:\n $cs") + end + if fertiliser in cs.events + curve = cs.croptype.highnutrientgrowth + if ismissing(curve) + @warn "fertiliser used, but highnutrientgrowth curve is missing. Using lownutrientgrowth curve." + curve = cs.croptype.lownutrientgrowth + end + else + curve = cs.croptype.lownutrientgrowth + if ismissing(curve) + # Note: This warning is commented out, as this is quite + # common for e.g. "permanent grassland" + # @warn "fertiliser not used, but lownutrientgrowth curve is missing. Using highnutrientgrowth curve." + curve = cs.croptype.highnutrientgrowth + end + end + return curve +end + +# Function in original ALMaSS code: +# `double PlantGrowthData::GetStartValue(int a_veg_type, int a_phase, int a_type)` in `Landscape/Plants.h`, line 142 +startvalue_veg_height(cs::CropState, phase::GrowthPhase) = + first(growthcurve(cs).height[phase]) +startvalue_LAgreen(cs::CropState, phase::GrowthPhase) = + first(growthcurve(cs).LAIgreen[phase]) +startvalue_LAtotal(cs::CropState, phase::GrowthPhase) = + first(growthcurve(cs).LAItotal[phase]) + +# Function in original ALMaSS code: +# `bool PlantGrowthData::StartValid(int a_veg_type, int a_phase)` in `Landscape/Plants.cpp`, line 835 +# The original logic for this is in `Landscape/Plants.cpp`, line 213 +# where `m_start_valid` is set. +isvalidstart(cs::CropState, phase::GrowthPhase) = + growthcurve(cs).height[phase] == -1.0cm + + """ stepagent!(cropstate, model) Update a farm plot by one day. """ function stepagent!(cs::CropState, model::SimulationModel) + # Function in original ALMaSS code: + # `void VegElement::DoDevelopment(void)` from `Landscape/Elements.cpp`, line 2254 + + # This part happens in the ALMaSS function `void Landscape::Tick(void)` + # in `Landscape/Landscape.cpp`, line 2272 + # # update the phase on key dates if monthday(model.date) == (1, 1) - cs.phase = ALMaSS.janfirst - cs.growingdegreedays = 0.0 + setphase!(cs, janfirst, model) elseif monthday(model.date) == (3, 1) - cs.phase = ALMaSS.marchfirst - cs.growingdegreedays = 0.0 + setphase!(cs, marchfirst, model) + end + + # TODO ignoring: cs.new_weed_growth = 0.0 + if ! cs.force_growth + cs.yddegs = cs.ddegs + pos_temp_today = get_dddegs(model) + if cs.vegddegs != -1 + # Sum up the vegetation day degrees since sowing + cs.vegddegs += pos_temp_today + end + # And sum up the phase ddegs + cs.ddegs = pos_temp_today + cs.yddegs + + dLAG = get_LAgreen_diff(cs) * cs.growth_scaler + dLAT = get_LAtotal_diff(cs) * cs.growth_scaler + dHgt = get_height_diff(cs) * cs.growth_scaler + + cs.LAgreen += dLAG + cs.LAtotal += dLAT + cs.veg_height += dHgt + if cs.LAgreen < 0.0 + cs.LAgreen = 0.0 + end + if cs.LAtotal < 0.0 + cs.LAtotal = 0.0 + end + if cs.veg_height < 0.0cm + cs.veg_height = 0.0cm + end + + # TODO ignored: calculation of cs.weedddegs, cs.new_weed_growth, cs.weed_biomass + else + force_growth_development!(cs) + end + + if cs.LAtotal < cs.LAgreen + @warn "Leaf Area Total smaller than Leaf Area Green (Veg Growth Model inconsistent). Performing hack correction." + cs.LAtotal = 1.1 * cs.LAgreen + end + + # Note: calls `VegElement::RecalculateBugsNStuff()` in the original ALMaSS code + recalculate_vegetation!(cs, model) + + # TODO ignored: ResetGeese() + # TODO ignored: Deal with any possible unsprayed margin, transferring info as necessary + # TODO ignored: cs.interested_green_biomass = cs.green_biomass * cs.interested_biomass_fraction +end + +# Function in original ALMaSS code: +# `void VegElement::ForceGrowthDevelopment(void)` from `Landscape/Elements.cpp`, line 2223 +function force_growth_development!(cs::CropState) + # TODO ignored: cs.weed_biomass += cs.force_Weed + # TODO ignored: cs.new_weed_growth = cs.force_Weed + cs.LAgreen += cs.force_LAgreen + cs.LAtotal += cs.force_LAtotal + cs.veg_height += cs.force_veg_height + if cs.LAgreen < 0 + cs.LAgreen = 0 + end + if cs.LAtotal < 0 + cs.LAtotal = 0 + end + if cs.veg_height < 0.0cm + cs.veg_height = 0.0cm end - # 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) - basetemp = cs.croptype.mingrowthtemp - ismissing(basetemp) && (basetemp = 5.0) - gdd = (maxtemp(model)+mintemp(model))/2 - basetemp - gdd > 0 && (cs.growingdegreedays += gdd) - # update crop growth - growcrop!(cs, gdd, model) end +# Function in original ALMaSS code: +# `void VegElement::RecalculateBugsNStuff(void)` from `Landscape/Elements.cpp`, line 1704 +function recalculate_vegetation!(cs::CropState, model::SimulationModel) + # This is the heart of the dynamics of vegetation elements. It + # calculates vegetation cover and uses this to determine + # vegetation biomass. It also calculates spilled grain and goose + # forage, as well a calculating insect biomass, vegetation density + # and dead biomass. + + cs.newgrowth = 0 + + # Beer's Law to give vegetation cover + cs.veg_cover = 1.0 - exp(- BEER_LAW_EXTINCTION_COEF * cs.LAtotal) + + # This is used to calc growth rate + # TODO: hardcoded extinction coefficient of 0.4 + useful_veg_cover = 1.0 - exp(cs.LAgreen * -0.4) + + # Need global radiation today + glrad = supply_global_radiation(model) + + temperature = supply_temperature(model) + if cs.croptype.is_c4_plant + radconv = solar_conversion_c4(temperature) + else + radconv = solar_conversion_c3(temperature) + end + + if cs.LAtotal >= cs.oldLAtotal + # we are in positive growth so grow depending on our equation + + biomass_scale = cs.croptype.biomass_scale + cs.newgrowth = useful_veg_cover * glrad * radconv * biomass_scale + + # TODO: ignoring farm intensity + cs.veg_biomass += cs.newgrowth + else + # Negative growth - so shrink proportional to the loss in LAI Total + if cs.oldLAtotal > 0 + temp_proportion = cs.LAtotal / cs.oldLAtotal + cs.veg_biomass *= temp_proportion + end + end + + # Here we also want to know how much biomass we have on the field + # in total. So we multiply the current biomass by area + # cs.total_biomass = cs.veg_biomass * cs.area + # cs.total_biomass_old = cs.total_biomass + + # NB The cs.weed_biomass is calculated directly from the curve in Curves.pre + # rather than going through the rigmarole of converting leaf-area index + # cs.veg_density = Int(floor(0.5 + (cs.veg_biomass / (1 + cs.veg_height)))) + + # if cs.veg_density > 100 + # cs.veg_density = 100 # to stop array bounds problems + # end + + if cs.LAtotal == 0.0 + cs.green_biomass = 0.0 + else + cs.green_biomass = cs.veg_biomass * (cs.LAgreen / (cs.LAtotal)) + end + cs.dead_biomass = cs.veg_biomass - cs.green_biomass + + # TODO: species-specific calculations + # Here we use our member function pointer to call the correct piece of code for our current species + #(this->*(SpeciesSpecificCalculations))(); +end + +# m_Landscape->SupplyTemp() +supply_temperature(model::SimulationModel) = (mintemp(model) + maxtemp(model) / 2) + +# m_Landscape->SupplyGlobalRadiation() +supply_global_radiation(model::SimulationModel) = + global_insolation[dayofyear(model.date)] + ## CROP MANAGEMENT AND GROWTH FUNCTIONS @@ -223,13 +849,17 @@ function sow!(cs::CropState, model::SimulationModel, cropname::String) !ismissing(cs.croptype.minsowdate) && model.date < cs.croptype.minsowdate && @warn "$(model.date) is earlier than the minimum sowing date for $(cropname)." cs.croptype = model.crops[cropname] - cs.phase = ALMaSS.sow - cs.growingdegreedays = 0.0 - cs.height = 0.0cm - cs.LAItotal = 0.0 - cs.LAIgreen = 0.0 - cs.mature = false - cs.events = Vector{Management}() + setphase!(cs, sow, model) + # TODO: is this correct to remove all events? this would + # e.g. remove fertilising event? + #empty!(cs.events) + + # cs.vegddegs = 0.0 + # cs.ddegs = 0.0 + # cs.yddegs = 0.0 + # cs.veg_height = 0.0cm + # cs.LAItotal = 0.0 + # cs.LAIgreen = 0.0 end """ @@ -238,62 +868,14 @@ end Harvest the crop of this cropstate. """ function harvest!(cs::CropState, model::SimulationModel) - cs.phase in [ALMaSS.harvest1, ALMaSS.harvest2] ? - cs.phase = ALMaSS.harvest2 : - cs.phase = ALMaSS.harvest1 - cs.growingdegreedays = 0.0 - cs.mature = false + phase = (cs.phase in (ALMaSS.harvest1, ALMaSS.harvest2) ? ALMaSS.harvest2 : ALMaSS.harvest1) + setphase!(cs, phase, model) # height & LAI will be automatically adjusted by the growth function - #TODO calculate and return yield + return cs.veg_biomass # TODO: units ok of cs.veg_biomass ? end #TODO fertilise!() #TODO spray!() #TODO till!() -""" - growcrop!(cropstate, gdd, model) - -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. (see `ALMaSS/Landscape/plant.cpp:PlantGrowthData::FindDiff()` and -`ALMaSS/Landscape/elements.cpp:VegElement::DoDevelopment()`). -""" -function growcrop!(cs::CropState, gdd::Float64, model::SimulationModel) - fertiliser in cs.events ? - curve = cs.croptype.lownutrientgrowth : - curve = cs.croptype.highnutrientgrowth - points = curve.GDD[cs.phase] #FIXME what if the curve is empty? - for p in 1:length(points) - if points[p] == 99999 - !(cs.phase in (janfirst, sow)) && (cs.mature = true) #FIXME only in the last phase? - return # the marker that there is no further growth this phase - elseif points[p] == -1 # the marker to set all variables to specified values - cs.height = curve.height[cs.phase][p] - cs.LAItotal = curve.LAItotal[cs.phase][p] - cs.LAIgreen = curve.LAIgreen[cs.phase][p] - return - else - gdd = cs.growingdegreedays - # figure out which is the correct slope value to use for growth - if p == length(points) || gdd < points[p+1] - #FIXME it appears to me from the ALMaSS source code that the curve value - # for the day is multiplied with that day's growingdegreedays to give the - # diff, which is then added to the previous values. However, this results - # in values that are staggeringly too large. I'm not quite sure what the - # issue is here... - cs.height += bounds(curve.height[cs.phase][p]*gdd) - cs.LAItotal += bounds(curve.LAItotal[cs.phase][p]*gdd) - cs.LAIgreen += bounds(curve.LAIgreen[cs.phase][p]*gdd) - return - end - #XXX To be precise, we ought to figure out if one or more inflection - # points have been passed between yesterday and today, and calculate the - # growth exactly up to the inflection point with the old slope, and onward - # with the new slope. Not doing so will introduce a small amount of error, - # although I think this is acceptable. - end - end -end - end # module ALMaSS diff --git a/src/crop/cropmodels.jl b/src/crop/cropmodels.jl index 3d84e98f7d6e87967d76163cae07d3028549dfc5..92722a11be435ccfe5d75a1900331bf45a6142cd 100644 --- a/src/crop/cropmodels.jl +++ b/src/crop/cropmodels.jl @@ -65,9 +65,8 @@ function makecropstate(model::SimulationModel) if @param(crop.cropmodel) == "almass" phase = (month(model.date) < 3 ? ALMaSS.janfirst : ALMaSS.marchfirst) cs = ALMaSS.CropState( - model.crops["natural grass"], - phase, - 0.0, 0.0cm, 0.0, 0.0, false, Vector{Management}() + croptype = model.crops["natural grass"], + phase = phase, ) elseif @param(crop.cropmodel) == "simple" cs = SimpleCrop.CropState( diff --git a/src/crop/simplecrop.jl b/src/crop/simplecrop.jl index 348d9448561f693d1e1f951d33b9b7781ea2187a..bc4f11dc1c9cd4b52d18274ff626af7e73cddabd 100644 --- a/src/crop/simplecrop.jl +++ b/src/crop/simplecrop.jl @@ -43,7 +43,7 @@ cropyield(cs::CropState) = 0.0 # TODO: units? isharvestable(cs::CropState) = true """ - stepagent!(farmplot, model) + stepagent!(cropstate, model) Update a crop state by one day. """ diff --git a/src/farm/farm.jl b/src/farm/farm.jl index 3bc3109e4efc086672a26681f147c3deb78065ce..c60cb93db7b39d423f388131bdd9a3f5ececa103 100644 --- a/src/farm/farm.jl +++ b/src/farm/farm.jl @@ -70,7 +70,7 @@ function stepagent!(farmer::BasicFarmer, model::SimulationModel) @warn "minsowdate and/or maxsowdate is `missing` for crop \"$(ctype.name)\", not assigning sowdates" end end - elseif cropname(field) == "no growth" && model.date == farmer.sowdates[f] + elseif cropname(field) == "no growth" && haskey(farmer.sowdates, f) && model.date == farmer.sowdates[f] # if a field has been harvested, check if the next crop can be sown @sow(farmer.croprotations[f][1]) end diff --git a/test/crop_tests.jl b/test/crop_tests.jl index 106f3854133a6d55e805551e822f1d4b092d2bfc..b412030f5b822b968c616f76524c96174d54d6d7 100644 --- a/test/crop_tests.jl +++ b/test/crop_tests.jl @@ -19,15 +19,15 @@ const TESTPARAM_SIMPLECROP = joinpath(pkgdir(Persefone), "test", "test_parameter end @testset "Submodule ALMaSS" begin - ct = Ps.ALMaSS.CropType("olive tree", "no_crop_group", - missing, missing, missing, missing, missing, missing, missing) + ct = Ps.ALMaSS.CropType("olive tree", "no_crop_group", false, missing, + missing, missing, missing, missing, missing, missing, missing, 1.0) id = 0 pixels = [(0, 0)] farmer = 0 mature = false events = Ps.Management[] - fp = FarmPlot(id, pixels, farmer, - Ps.ALMaSS.CropState(ct, Ps.ALMaSS.janfirst, 0.0, 0.0m, 0.0, 0.0, mature, events)) + force_growth = false + fp = FarmPlot(id, pixels, farmer, Ps.ALMaSS.CropState(croptype=ct, phase=Ps.ALMaSS.janfirst)) @test fp isa FarmPlot @test fp isa FarmPlot{Ps.ALMaSS.CropState} @test croptype(fp) isa Ps.ALMaSS.CropType diff --git a/test/cropparams-almass/crop_data_general.csv b/test/cropparams-almass/crop_data_general.csv deleted file mode 100644 index c779dec5fa35c5777bb5363b88a81dbd8556bddc..0000000000000000000000000000000000000000 --- a/test/cropparams-almass/crop_data_general.csv +++ /dev/null @@ -1,28 +0,0 @@ -name,minsowdate,maxsowdate,minharvestdate,maxharvestdate,mingrowthtemp,group -"spring rape",NA,NA,NA,NA,NA,"grain" -"winter rape","20 August","25 August",NA,NA,NA,"grain" -"winter wheat","15 October","31 October",NA,NA,0,"grain" -"spring wheat",NA,NA,NA,NA,NA,"grain" -"winter barley","15 September","30 September",NA,NA,0,"grain" -"spring barley","1 March","10 April",NA,NA,0,"grain" -"undersown spring barley",NA,NA,NA,NA,0,"grain" -"winter rye","23 September","15 October",NA,NA,NA,"grain" -"triticale","25 September","10 October",NA,NA,NA,"grain" -"oats",NA,NA,NA,NA,NA,"grain" -"maize","15 April","30 April",NA,NA,8,"grain" -"potatoes",NA,NA,NA,NA,4,"root" -"carrots",NA,NA,NA,NA,NA,"root" -"beet","15 March","10 May",NA,NA,NA,"root" -"sunflower","25 March","15 April",NA,NA,NA,"other" -"lucerne",NA,NA,NA,NA,NA,"legumes" -"peas/beans","15 February","15 March",NA,NA,5,"legumes" -"silage clover/grass",NA,NA,NA,NA,NA,"legumes" -"fodder/clover",NA,NA,NA,NA,NA,"legumes" -"lawn",NA,NA,NA,NA,NA,"grass" -"permanent grassland (grazed)",NA,NA,NA,NA,NA,"grass" -"permanent grassland (seeded)",NA,NA,NA,NA,NA,"grass" -"permanent grassland (low yield)",NA,NA,NA,NA,NA,"grass" -"permanent set-aside",NA,NA,NA,NA,NA,"semi-natural" -"natural grass",NA,NA,NA,NA,NA,"semi-natural" -"no growth",NA,NA,NA,NA,NA,"semi-natural" -"heath",NA,NA,NA,NA,NA,"semi-natural" diff --git a/test/nature_tests.jl b/test/nature_tests.jl index d4b723e5716c7128786213340a6e20c84978ff23..0a786cabd2cd81eacd45fd12d9930ee561d57566 100644 --- a/test/nature_tests.jl +++ b/test/nature_tests.jl @@ -51,8 +51,8 @@ end) # end eval fp = Ps.FarmPlot( 1, [(6,6)], 1, Ps.ALMaSS.CropState( - model.crops["winter wheat"], Ps.ALMaSS.janfirst, - 0.0, 0.0m, 0.0, 0.0, false, Vector{Ps.Management}() + croptype = model.crops["winter wheat"], + phase = Ps.ALMaSS.janfirst, ) ) push!(model.farmplots, fp) diff --git a/test/test_parameters.toml b/test/test_parameters.toml index 47c5a08e094537da28b0d519f4776e1228f8c04b..a8a614e780e7ee1ea9368de9b0db168358a88207 100644 --- a/test/test_parameters.toml +++ b/test/test_parameters.toml @@ -41,4 +41,4 @@ insectmodel = ["season", "habitat", "pesticides"] # which factors affect insect [crop] cropmodel = "almass" # crop growth model to use, "almass", "aquacrop", or "simple" -cropdirectory = "test/cropparams-almass/" # the directory storing all data files for the selected crop model +cropdirectory = "data/crops/almass/" # the directory storing all data files for the selected crop model diff --git a/test/test_parameters_almass.toml b/test/test_parameters_almass.toml index fa520c207a6980016d2595ed5d16c78f76137af4..4eaae831355219d74fe90b3fe8ebc1e217c8d900 100644 --- a/test/test_parameters_almass.toml +++ b/test/test_parameters_almass.toml @@ -41,4 +41,4 @@ insectmodel = ["season", "habitat", "pesticides"] # which factors affect insect [crop] cropmodel = "almass" # crop growth model to use, "almass", "aquacrop", or "simple" -cropdirectory = "test/cropparams-almass/" # the directory storing all data files for the selected crop model +cropdirectory = "data/crops/almass/" # the directory storing all data files for the selected crop model diff --git a/test/test_parameters_simplecrop.toml b/test/test_parameters_simplecrop.toml index 43bd55087732774a792cb39d098b7e3abcfce43c..b5cd61e6f69f60f1268db06588e9d86547aec52a 100644 --- a/test/test_parameters_simplecrop.toml +++ b/test/test_parameters_simplecrop.toml @@ -41,4 +41,4 @@ insectmodel = ["season", "habitat", "pesticides"] # which factors affect insect [crop] cropmodel = "simple" # crop growth model to use, "almass", "aquacrop", or "simple" -cropdirectory = "test/cropparams-almass/" # the directory storing all data files for the selected crop model +cropdirectory = "data/crops/almass/" # the directory storing all data files for the selected crop model diff --git a/test/zzz-cropparams-almass/crop_data_general.csv b/test/zzz-cropparams-almass/crop_data_general.csv new file mode 100644 index 0000000000000000000000000000000000000000..9af47222ca7e54057816d197135aa966266a2b1f --- /dev/null +++ b/test/zzz-cropparams-almass/crop_data_general.csv @@ -0,0 +1,28 @@ +name,minsowdate,maxsowdate,minharvestdate,maxharvestdate,mingrowthtemp,group,biomass_scale,is_c4_plant,sowingdensity +spring rape,NA,NA,NA,NA,NA,grain,1.071,false,75 +winter rape,20 August,25 August,NA,NA,NA,grain,1.071,false,75 +winter wheat,15 October,31 October,NA,NA,0,grain,1.0,false,400 +spring wheat,NA,NA,NA,NA,NA,grain,1.0,false,475 +winter barley,15 September,30 September,NA,NA,0,grain,0.857,false,340 +spring barley,1 March,10 April,NA,NA,0,grain,0.857,false,360 +undersown spring barley,NA,NA,NA,NA,0,grain,0.857,false,NA +winter rye,23 September,15 October,NA,NA,NA,grain,0.857,false,290 +triticale,25 September,10 October,NA,NA,NA,grain,1.0,false,320 +oats,NA,NA,NA,NA,NA,grain,0.857,false,385 +maize,15 April,30 April,NA,NA,8,grain,1.0,true,13 +potatoes,NA,NA,NA,NA,4,root,0.857,false,5 +carrots,NA,NA,NA,NA,NA,root,0.7857,false,160 +beet,15 March,10 May,NA,NA,NA,root,0.857,false,16 +sunflower,25 March,15 April,NA,NA,NA,other,1.0,false,8 +lucerne,NA,NA,NA,NA,NA,legumes,1.2,false,NA +peas/beans,15 February,15 March,NA,NA,5,legumes,0.857,false,35/80 +silage clover/grass,NA,NA,NA,NA,NA,legumes,1.1,false,NA +fodder/clover,NA,NA,NA,NA,NA,legumes,1.2,false,NA +lawn,NA,NA,NA,NA,NA,grass,0.5,false,NA +permanent grassland (grazed),NA,NA,NA,NA,NA,grass,1.1,false,NA +permanent grassland (seeded),NA,NA,NA,NA,NA,grass,1.1,false,NA +permanent grassland (low yield),NA,NA,NA,NA,NA,grass,1.0,false,NA +permanent set-aside,NA,NA,NA,NA,NA,semi-natural,0.7857,false,NA +natural grass,NA,NA,NA,NA,NA,semi-natural,0.567,false,NA +no growth,NA,NA,NA,NA,NA,semi-natural,0.0,false,NA +heath,NA,NA,NA,NA,NA,semi-natural,0.567,false,NA diff --git a/test/cropparams-almass/crop_growth_curves.csv b/test/zzz-cropparams-almass/crop_growth_curves.csv similarity index 100% rename from test/cropparams-almass/crop_growth_curves.csv rename to test/zzz-cropparams-almass/crop_growth_curves.csv