From e43c5ebdc846f50eba5eb4f05fab93cf6f48f3d7 Mon Sep 17 00:00:00 2001 From: Daniel Vedder <daniel.vedder@idiv.de> Date: Fri, 9 Aug 2024 10:38:42 +0200 Subject: [PATCH] First version of farm model (seems to work) --- src/crop/almass.jl | 18 ++++++++-------- src/crop/farmplot.jl | 3 ++- src/farm/farm.jl | 49 ++++++++++++++++++++++++++++++++------------ 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/crop/almass.jl b/src/crop/almass.jl index 570305a..6c020cc 100644 --- a/src/crop/almass.jl +++ b/src/crop/almass.jl @@ -8,6 +8,7 @@ module ALMaSS using Persefone: + AnnualDate, Management, Length, cm, @@ -60,16 +61,17 @@ The type struct for all crops. Currently follows the crop growth model as implemented in ALMaSS. """ struct CropType + #FIXME this needs thinking about. The sowing and harvest dates belong in the farm model, + # not here. Also, we need to harmonise crops across the crop growth models. name::String group::String - minsowdate::Union{Missing,Date} - maxsowdate::Union{Missing,Date} - minharvestdate::Union{Missing,Date} - maxharvestdate::Union{Missing,Date} + minsowdate::Union{Missing,AnnualDate} + maxsowdate::Union{Missing,AnnualDate} + minharvestdate::Union{Missing,AnnualDate} + maxharvestdate::Union{Missing,AnnualDate} mingrowthtemp::Union{Missing,Float64} highnutrientgrowth::Union{Missing,CropCurveParams} lownutrientgrowth::Union{Missing,CropCurveParams} - #issowable::Union{Function,Bool} end cropname(ct::CropType) = ct.name @@ -106,7 +108,7 @@ isharvestable(cs::CropState) = cs.mature Extend `tryparse` to allow parsing GrowthPhase values. (Needed to read in the CSV parameter file.) """ -function Base.tryparse(type::Type{GrowthPhase}, str::String) +function Base.tryparse(::Type{GrowthPhase}, str::String) str == "janfirst" ? janfirst : str == "sow" ? sow : str == "marchfirst" ? marchfirst : @@ -152,8 +154,8 @@ Parse a CSV file containing the required parameter values for each crop """ 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,String]) + cropdata = CSV.File(generalcropfile, missingstring="NA", + types=[String,AnnualDate,AnnualDate,AnnualDate,AnnualDate,Float64,String]) growthdata = CSV.File(growthfile, missingstring="NA", types=[Int,String,String,GrowthPhase,String, Float64,Float64,Float64,Float64]) diff --git a/src/crop/farmplot.jl b/src/crop/farmplot.jl index d4b5787..c100306 100644 --- a/src/crop/farmplot.jl +++ b/src/crop/farmplot.jl @@ -37,9 +37,9 @@ end Sow the specified crop on the farmplot. """ function sow!(farmplot::FarmPlot, model::SimulationModel, cropname::String) - #XXX test if the crop is sowable? createevent!(model, farmplot.pixels, sowing) sow!(farmplot.cropstate, model, cropname) + @debug "Farmer $(farmplot.farmer) sowed $(cropname) on farmplot $(farmplot.id)." end """ @@ -50,6 +50,7 @@ Harvest the crop of this farmplot. function harvest!(farmplot::FarmPlot{T}, model::SimulationModel) where T createevent!(model, farmplot.pixels, harvesting) harvest!(farmplot.cropstate, model) # TODO: multiply with area to return units of `g` + @debug "Farmer $(farmplot.farmer) harvested $(cropname(farmplot)) from farmplot $(farmplot.id)." end """ diff --git a/src/farm/farm.jl b/src/farm/farm.jl index 32a4d7f..a3aec89 100644 --- a/src/farm/farm.jl +++ b/src/farm/farm.jl @@ -4,35 +4,52 @@ ### ##TODO what data do we need to gather from the farm submodel? +## - area covered by each crop over time +## - average height of each crop over time +## - total income per year + +#XXX Initially, we're only working with a single simple crop rotation. +# Later on, we need to figure out how to integrate several. +const CROPROTATION = ["winter rape", "winter wheat", "maize", "winter barley"] + +#FIXME Currently, this is specific to the ALMaSS model. We need to figure out how to generalise it. """ Farmer -This is the agent type for the farm ABM. (Not yet implemented.) +This is the agent type for the farm ABM. """ mutable struct Farmer <: ModelAgent #XXX make this into an abstract type and create subtypes for different farm submodels? (#69) const id::Int64 - fields::Vector{Int64} # IDs of the farmplots this farmer owns - croprotation::Vector{String} #TODO figure this out - totalincome::Float64 + # farmplots owned by this farmer and their associated crop rotations and next sowing date + fields::Vector{Int64} + croprotations::Dict{Int64, Vector{String}} + sowdates::Dict{Int64, AnnualDate} + totalincome::Float64 # accumulated income XXX split up by year? end """ stepagent!(farmer, model) -Update a farmer by one day. +Update a farmer by one day. Cycle through all fields and see what management is needed. """ function stepagent!(farmer::Farmer, model::SimulationModel) for f in farmer.fields field = model.farmplots[f] ctype = croptype(field) if ctype.group != "semi-natural" && isharvestable(field) - harvest!(field, model) + @harvest() #XXX later: calculate income based on yield and annual price - (ctype.group != "grass") && @sow("no growth") - elseif cropname(field) == "no growth" - #TODO if a field has been harvested, check if the next crop can be sown + if ctype.group != "grass" + @sow("no growth") + cycle!(farmer.croprotations[f]) # advance the crop rotation + nextcrop = model.crops[farmer.croprotations[f][1]] + farmer.sowdates[f] = @rand(nextcrop.minsowdate:nextcrop.maxsowdate) + end + elseif cropname(field) == "no growth" && 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 end end @@ -44,16 +61,22 @@ Initialise the model with a set of farm agents. """ function initfarms!(model::SimulationModel) #XXX initially, we only have one farmer controlling all fields in the region - farmer = Farmer(1, collect(1:length(model.farmplots)), [], 0) + farmer = Farmer(1, collect(1:length(model.farmplots)), Dict(), Dict(), 0) model.farmers = [farmer] setasides = findsetasides(farmer, model) for field in model.farmplots + field.farmer = farmer.id if isgrassland(field, model) - @sow("permanent grassland (seeded)") + @sow("permanent grassland (grazed)") + farmer.croprotations[field.id] = ["permanent grassland (grazed)"] elseif field.id in setasides @sow("permanent set-aside") + farmer.croprotations[field.id] = ["permanent set-aside"] else - @sow("no growth") + @sow("no growth") # assign each arable field a crop rotation, cycled randomly + farmer.croprotations[field.id] = cycle!(deepcopy(CROPROTATION), @rand(0:3)) + nextcrop = model.crops[farmer.croprotations[field.id][1]] + farmer.sowdates[field.id] = @rand(nextcrop.minsowdate:nextcrop.maxsowdate) end end end @@ -70,7 +93,7 @@ function findsetasides(farmer::Farmer, model::SimulationModel) model.farmplots[farmer.fields])) setasidearea = 0m² setasides = [] - for f in farmer.fields #XXX should be sorted smallest-largest for highest efficiency + for f in keys(farmer.fields) #XXX should be sorted smallest-largest for highest efficiency field = model.farmplots[f] isgrassland(field, model) && continue push!(setasides, f) -- GitLab