diff --git a/data/crop_data_general.csv b/data/crop_data_general.csv index c8f2fdbcf3a091d1892de9de5740e6c5a9e5f0e7..4fd822472b0c862113bf47269bda7d9e875ad3cd 100644 --- a/data/crop_data_general.csv +++ b/data/crop_data_general.csv @@ -1,19 +1,19 @@ name,minsowdate,maxsowdate,minharvestdate,maxharvestdate,mingrowthtemp -"winter barley","15 September","30 September",NA,NA,NA -"spring barley","1 March","10 April",NA,NA,NA -"peas/beans",NA,NA,NA,NA,NA +"winter barley","15 September","30 September",NA,NA,0 +"spring barley","1 March","10 April",NA,NA,0 +"peas/beans",NA,NA,NA,NA,5 "spring rape",NA,NA,NA,NA,NA "winter rape",NA,NA,NA,NA,NA "winter rye",NA,NA,NA,NA,NA -"winter wheat","15 October","31 October",NA,NA,NA +"winter wheat","15 October","31 October",NA,NA,0 "beet",NA,NA,NA,NA,NA -"maize",NA,NA,NA,NA,NA +"maize",NA,NA,NA,NA,8 "permanent grassland (grazed)",NA,NA,NA,NA,NA "permanent grassland (seeded)",NA,NA,NA,NA,NA "fodder/clover",NA,NA,NA,NA,NA "natural grass",NA,NA,NA,NA,NA -"potatoes",NA,NA,NA,NA,NA -"undersown spring barley",NA,NA,NA,NA,NA +"potatoes",NA,NA,NA,NA,4 +"undersown spring barley",NA,NA,NA,NA,0 "carrots",NA,NA,NA,NA,NA "oats",NA,NA,NA,NA,NA "permanent set-aside",NA,NA,NA,NA,NA diff --git a/docs/base_temperatures_Ramirez-Villegas_etal_20213.png b/docs/base_temperatures_Ramirez-Villegas_etal_20213.png new file mode 100644 index 0000000000000000000000000000000000000000..fc8e2a9cd2dd33f2c7861aa55257363b7b3c03a0 Binary files /dev/null and b/docs/base_temperatures_Ramirez-Villegas_etal_20213.png differ diff --git a/src/core/simulation.jl b/src/core/simulation.jl index ae42934920bced52220b6612a2b5a58b40ee4d50..c21578eba35d31e9cede5e3f6324d8b0c29f4799 100644 --- a/src/core/simulation.jl +++ b/src/core/simulation.jl @@ -69,12 +69,15 @@ function initmodel(settings::Dict{String, Any}) weather = initweather(settings["world.weatherfile"], settings["core.startdate"], settings["core.enddate"]) + crops = readcropparameters(settings["crop.cropfile"], + settings["crop.growthfile"]) space = GridSpace(size(landscape), periodic=false) properties = Dict{Symbol,Any}(:settings=>settings, :logger=>logger, :date=>settings["core.startdate"], :landscape=>landscape, :weather=>weather, + :crops=>crops, :dataoutputs=>dataoutputs, :events=>events) model = AgentBasedModel(Union{Farmer,Animal,FarmPlot}, space, properties=properties, diff --git a/src/crop/crops.jl b/src/crop/crops.jl index 7f51beba10857a2f14ebe1563018d6f87fee3aff..f716c9e3b32c08e53e1083b973cfd2f9fde77c1e 100644 --- a/src/crop/crops.jl +++ b/src/crop/crops.jl @@ -4,7 +4,6 @@ ### #TODO write tests for input functions -#TODO write actual growth function """ GrowthPhase @@ -96,14 +95,14 @@ end 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) +function readcropparameters(generalcropfile::String, growthfile::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", + growthdata = CSV.File(growthfile, missingstring="NA", types=[Int,String,String,GrowthPhase,String, Float64,Float64,Float64,Float64]) - croptypes = Vector{CropType}() + croptypes = Dict{String,CropType}() for crop in cropdata cropgrowthdata = growthdata |> filter(x -> !ismissing(x.crop_name) && x.crop_name == crop.name) @@ -111,9 +110,9 @@ function readcropparameters(generalcropfile::String, cropgrowthfile::String) 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)]) + croptypes[crop.name] = CropType(crop.name, crop.minsowdate, crop.maxsowdate, + crop.minharvestdate, crop.maxharvestdate, + crop.mingrowthtemp, highnuts, lownuts) end croptypes end diff --git a/src/crop/farmplot.jl b/src/crop/farmplot.jl index a77d0f8390039c49357bbf2bbcb3f2b73f2ea86b..8b7b24b96887a4b26d69b9d7b96014f223912988 100644 --- a/src/crop/farmplot.jl +++ b/src/crop/farmplot.jl @@ -14,25 +14,13 @@ This is the spatial unit with which the crop growth model and the farm model wor @agent FarmPlot GridAgent{2} begin pixels::Vector{Tuple{Int64, Int64}} croptype::CropType + phase::GrowthPhase growingdegreedays::Float64 height::Float64 - biomass::Float64 + LAItotal::Float64 + LAIgreen::Float64 + #biomass::Float64 #XXX I need to figure out how to calculate this events::Vector{EventType} - #TODO -end - -""" - stepagent!(farmplot, model) - -Update a farm plot by one day. -""" -function stepagent!(farmplot::FarmPlot, model::AgentBasedModel) - # update growing degree days - gdd = meantemp(model) - farmplot.croptype.mingrowthtemp - gdd > 0 && (farmplot.growingdegreedays += gdd) - # update crop growth - growcrop!(farmplot, model) - #TODO expand? end """ @@ -56,7 +44,11 @@ function initfields!(model::AgentBasedModel) model.landscape[x,y].fieldid = objectid push!(model[objectid].pixels, (x,y)) else - fp = add_agent!((x,y), FarmPlot, model, [(x,y)], fallow, 0.0, 0.0) + #XXX does this phase calculation work out? + month(model.date) < 3 ? phase = janfirst : phase = marchfirst + fp = add_agent!((x,y), FarmPlot, model, [(x,y)], + model.crops["natural grass"], phase, false, + 0.0, 0.0, 0.0, 0.0, Vector{EventType}()) model.landscape[x,y].fieldid = fp.id convertid[rawid] = fp.id n += 1 @@ -66,6 +58,99 @@ function initfields!(model::AgentBasedModel) @info "Initialised $n farm plots." end +""" + stepagent!(farmplot, model) + +Update a farm plot by one day. +""" +function stepagent!(farmplot::FarmPlot, model::AgentBasedModel) + # 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 = farmplot.croptype.mingrowthtemp + ismissing(basetemp) && (basetemp = 5.0) + gdd = (maxtemp(model)+mintemp(model))/2 - basetemp + gdd > 0 && (farmplot.growingdegreedays += gdd) + # update the phase on key dates + monthday(model.date) == (1,1) && (farmplot.phase = janfirst) + monthday(model.date) == (3,1) && (farmplot.phase = marchfirst) + # update crop growth + growcrop!(farmplot, model) +end + + +## CROP MANAGEMENT AND GROWTH FUNCTIONS + +""" + sow!(cropname, farmplot, model) + +Sow the specified crop on this farmplot. +""" +function sow!(cropname::String, farmplot::FarmPlot, model::AgentBasedModel) + createevent!(model, farmplot.pixels, sowing) + farmplot.croptype = model.crops[cropname] + farmplot.phase = sow + #XXX test if the crop is sowable? +end + +""" + harvest!(farmplot, model) + +Harvest the crop on this farmplot. +""" +function harvest!(farmplot::FarmPlot, model::AgentBasedModel) + createevent!(model, farmplot.pixels, harvesting) + farmplot.phase in [harvest1, harvest2] ? + farmplot.phase = harvest2 : + farmplot.phase = harvest1 + # height & LAI will be automatically adjusted by the growth function + #TODO calculate and return yield +end + +#TODO fertilise!() +#TODO spray!() +#TODO till!() + +""" + growcrop!(farmplot, 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. +""" +function growcrop!(farmplot::FarmPlot, model::AgentBasedModel) + fertiliser in farmplot.events ? + curve = farmplot.croptype.lownutrientgrowth : + curve = farmplot.croptype.highnutrientgrowth + points = curve.GDD[farmplot.phase] + for p in 1:length(points) + if points[p] == 99999 + return # the marker that there is no further growth this phase + elseif points[p] == -1 # the marker to set all variables to specified values + farmplot.height = curve.height[farmplot.phase][p] + farmplot.LAItotal = curve.LAItotal[farmplot.phase][p] + farmplot.LAIgreen = curve.LAIgreen[farmplot.phase][p] + return + else + gdd = farmplot.growingdegreedays + # figure out which is the correct slope value to use for growth + if p == length(points) || gdd < points[p+1] + farmplot.height += curve.height[farmplot.phase][p] + farmplot.LAItotal += curve.LAItotal[farmplot.phase][p] + farmplot.LAIgreen += curve.LAIgreen[farmplot.phase][p] + 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 + + +## UTILITY FUNCTIONS + """ averagefieldsize(model) @@ -101,14 +186,3 @@ function cropheight(pos::Tuple{Int64,Int64}, model::AgentBasedModel) ismissing(model.landscape[pos...].fieldid) ? nothing : model[model.landscape[pos...].fieldid].height end - - -""" - growcrop!(farmplot, model) - -Apply the relevant crop growth model to update the plant height on this farm plot. -""" -function growcrop!(farmplot::FarmPlot, model::AgentBasedModel) - crop = farmplot.croptype - #TODO -end diff --git a/src/parameters.toml b/src/parameters.toml index 6b26cac2ca94642e9cb07506732aa367e4ebb4f9..46d1902be2934ccae68a731a4c4f1c3f0a15daf4 100644 --- a/src/parameters.toml +++ b/src/parameters.toml @@ -35,5 +35,5 @@ insectmodel = ["season", "habitat", "pesticides", "weather"] # factors affecting [crop] 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 +growthfile = "data/almass_crop_growth_curves.csv" # file with crop growth parameters diff --git a/src/world/landscape.jl b/src/world/landscape.jl index d6b16f608a5693dcd48027065b7d9658736f45f7..d8127d4589de3387508c861c4679fdba886f30b0 100644 --- a/src/world/landscape.jl +++ b/src/world/landscape.jl @@ -8,7 +8,7 @@ ## Do not change the order of this enum, or initlandscape() will break! "The types of landscape event that can be simulated" -@enum EventType tillage sowing fertiliser pesticide harvest +@enum EventType tillage sowing fertiliser pesticide harvesting """ Pixel diff --git a/test/test_parameters.toml b/test/test_parameters.toml index 9e68c6f3bfdb99a380293b820eef6984bf0aa624..eac86aaf4a99d094c6f46148bc7fe21913b83da6 100644 --- a/test/test_parameters.toml +++ b/test/test_parameters.toml @@ -31,5 +31,7 @@ indoutfreq = "end" # output frequency individual-level data, daily/monthly/yearl insectmodel = ["season", "habitat", "pesticides"] # which factors affect insect growth ("weather" is not yet implemented) [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 +growthfile = "data/almass_crop_growth_curves.csv" # file with crop growth parameters