diff --git a/src/Persefone.jl b/src/Persefone.jl index 6a824d15d92e0434d56af3e148c9a2a48f665a28..d6b7ccb599aa811b9c4e96aaa237e834b06e3bfd 100644 --- a/src/Persefone.jl +++ b/src/Persefone.jl @@ -132,6 +132,22 @@ The supertype of all agents in the model (animal species, farmer types, farmplot """ abstract type ModelAgent end +""" + AbstractCropType + +The abstract supertype of all crop types in the model. Each crop +model has to define a type `CropType <: AbstractCropType`. +""" +abstract type AbstractCropType end + +""" + AbstractCropState + +The abstract supertype of all crop states in the model. Each crop +model has to define a type `CropState <: AbstractCropState`. +""" +abstract type AbstractCropState end + function stepagent! end ## include all module files (note that the order matters - if file diff --git a/src/core/simulation.jl b/src/core/simulation.jl index 5c3628eb62e5e7b6970094e1df3bc77bba149448..667f766a5b2c7a76640c2af1d23d3be2ddb56798 100644 --- a/src/core/simulation.jl +++ b/src/core/simulation.jl @@ -12,7 +12,7 @@ This is the heart of the model - a struct that holds all data and state for one simulation run. It is created by [`initialise`](@ref) and passed as input to most model functions. """ -@kwdef mutable struct AgricultureModel{Tcroptype, Tcropstate} <: SimulationModel +@kwdef mutable struct AgricultureModel <: SimulationModel settings = Dict{String, Any}() rng::AbstractRNG logger::AbstractLogger @@ -20,9 +20,9 @@ as input to most model functions. date::Date landscape = Matrix{Pixel}() weather::Weather - crops = Dict{String, Tcroptype}() + crops = Dict{String, AbstractCropType}() farmers = Vector{Farmer}() - farmplots = Vector{FarmPlot{Tcropstate}}() + farmplots = Vector{FarmPlot}() animals = Vector{Union{Animal, Nothing}}() migrants = Vector{Pair{Animal, AnnualDate}}() events = Vector{FarmEvent}() @@ -140,9 +140,9 @@ function initmodel(settings::Dict{String, Any}) settings["world.weatherfile"]), settings["core.startdate"], settings["core.enddate"]) - crops, Tcroptype, Tcropstate = initcropmodel(settings["crop.cropmodel"], - settings["crop.cropdirectory"]) - model = AgricultureModel{Tcroptype, Tcropstate}(; + crops = initcropmodel(settings["crop.cropmodel"], + settings["crop.cropdirectory"]) + model = AgricultureModel(; settings, rng = StableRNG(settings["core.seed"]), logger, diff --git a/src/crop/almass.jl b/src/crop/almass.jl index 8edc31833e9e816caf558899948cee8d1309711f..a2dada4fc2d1d5e58552246a3fa6cd38ae34785e 100644 --- a/src/crop/almass.jl +++ b/src/crop/almass.jl @@ -43,6 +43,8 @@ const GROWTHFILE = "crop_growth_curves.csv" using Persefone: @rand, @u_str, + AbstractCropState, + AbstractCropType, AnnualDate, Management, cm, @@ -116,7 +118,7 @@ end The type struct for all crops. Currently follows the crop growth model as implemented in ALMaSS. """ -struct CropType +struct CropType <: AbstractCropType #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 @@ -155,7 +157,7 @@ cropname(ct::CropType) = ct.name The state data for an ALMaSS vegetation point calculation. Usually part of a `FarmPlot`. """ -@kwdef mutable struct CropState +@kwdef mutable struct CropState <: AbstractCropState # Class in original ALMaSS code: # `VegElement` from `Landscape/Elements.h`, line 601 croptype::CropType diff --git a/src/crop/aquacrop.jl b/src/crop/aquacrop.jl index 205078dcce265f39f2a4fa1e434c51872ed85ae8..975dd5a3523a8e7f57f8921478bdfb1e2fc9e3e8 100644 --- a/src/crop/aquacrop.jl +++ b/src/crop/aquacrop.jl @@ -10,6 +10,8 @@ using Dates: Date using DataFrames: DataFrame using Persefone: + AbstractCropState, + AbstractCropType, AnnualDate, cm, daynumber, @@ -68,7 +70,7 @@ const AQUACROP_CROPNAMES = [ "wheatGDD", ] -@kwdef struct CropType +@kwdef struct CropType <: AbstractCropType name::String aquacrop_name::String group::String @@ -101,7 +103,7 @@ function readcropparameters(cropdirectory::String) return croptypes end -mutable struct CropState +mutable struct CropState <: AbstractCropState croptype::CropType height::Tlength # TODO: remove height field, supply from cropstate soiltype::SoilType diff --git a/src/crop/cropmodels.jl b/src/crop/cropmodels.jl index 715e74bf58ac2a84a37b738784c88b21eb1edacb..5571dbdc0dc27d5a4964bb4c85a2e30f753ac61a 100644 --- a/src/crop/cropmodels.jl +++ b/src/crop/cropmodels.jl @@ -13,23 +13,17 @@ simulation, as well as the types `Tcroptype` and `Tcropstate`. """ function initcropmodel(cropmodel::AbstractString, cropdirectory::AbstractString) if cropmodel == "almass" - Tcroptype = ALMaSS.CropType - Tcropstate = ALMaSS.CropState crops = ALMaSS.readcropparameters(cropdirectory) elseif cropmodel == "simple" - Tcroptype = SimpleCrop.CropType - Tcropstate = SimpleCrop.CropState crops_almass = ALMaSS.readcropparameters(cropdirectory) crops = Dict(name => SimpleCrop.CropType(ct.name, ct.group, ct.minsowdate, ct.maxsowdate) for (name, ct) in crops_almass) elseif cropmodel == "aquacrop" - Tcroptype = AquaCropWrapper.CropType - Tcropstate = AquaCropWrapper.CropState crops = AquaCropWrapper.readcropparameters(cropdirectory) else error("initcropmodel: no implementation for crop model '$cropmodel'") end - return crops, Tcroptype, Tcropstate + return crops end """ diff --git a/src/crop/farmplot.jl b/src/crop/farmplot.jl index f676276569d827c4b986129b176db8ea59a8865e..810bbdfcee53a14fd5ac2186165d7c3604937dc7 100644 --- a/src/crop/farmplot.jl +++ b/src/crop/farmplot.jl @@ -8,27 +8,27 @@ A struct representing a single field, on which a crop can be grown. """ -mutable struct FarmPlot{T} <: ModelAgent +mutable struct FarmPlot <: ModelAgent const id::Int64 pixels::Vector{Tuple{Int64, Int64}} farmer::Int64 soiltype::SoilType - cropstate::T + cropstate::AbstractCropState end -croptype(f::FarmPlot{T}) where {T} = croptype(f.cropstate) -cropname(f::FarmPlot{T}) where {T} = cropname(croptype(f)) -cropheight(f::FarmPlot{T}) where {T} = cropheight(f.cropstate) -cropcover(f::FarmPlot{T}) where {T} = cropcover(f.cropstate) -cropyield(f::FarmPlot{T}) where {T} = cropyield(f.cropstate) -isharvestable(f::FarmPlot{T}) where {T} = isharvestable(f.cropstate) +croptype(f::FarmPlot) = croptype(f.cropstate) +cropname(f::FarmPlot) = cropname(croptype(f)) +cropheight(f::FarmPlot) = cropheight(f.cropstate) +cropcover(f::FarmPlot) = cropcover(f.cropstate) +cropyield(f::FarmPlot) = cropyield(f.cropstate) +isharvestable(f::FarmPlot) = isharvestable(f.cropstate) """ stepagent!(farmplot, model) Update a farm plot by one day. """ -function stepagent!(farmplot::FarmPlot{T}, model::SimulationModel) where T +function stepagent!(farmplot::FarmPlot, model::SimulationModel) stepagent!(farmplot.cropstate, model) end @@ -48,7 +48,7 @@ end Harvest the crop of this farmplot. """ -function harvest!(farmplot::FarmPlot{T}, model::SimulationModel) where T +function harvest!(farmplot::FarmPlot, model::SimulationModel) 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)." diff --git a/src/crop/simplecrop.jl b/src/crop/simplecrop.jl index bc4f11dc1c9cd4b52d18274ff626af7e73cddabd..32dbad83d13db43fa8af1ac64e8d330f6540cea1 100644 --- a/src/crop/simplecrop.jl +++ b/src/crop/simplecrop.jl @@ -1,6 +1,8 @@ module SimpleCrop using Persefone: + AbstractCropState, + AbstractCropType, AnnualDate, FarmPlot, Length, @@ -21,7 +23,7 @@ import Persefone: using Unitful: @u_str # TODO: alternatively just use ALMaSS.CropType ? -struct CropType +struct CropType <: AbstractCropType name::String group::String minsowdate::Union{Missing,AnnualDate} @@ -30,7 +32,7 @@ end cropname(ct::CropType) = ct.name -mutable struct CropState +mutable struct CropState <: AbstractCropState croptype::CropType height::Length{Float64} end diff --git a/test/crop_tests.jl b/test/crop_tests.jl index e80ed5946dc3be503de7b7a031eb332da3bcf158..8b3a0d3816d81c11fdfc52ae032c158991779098 100644 --- a/test/crop_tests.jl +++ b/test/crop_tests.jl @@ -33,7 +33,7 @@ end force_growth = false fp = FarmPlot(id, pixels, farmer, Ps.nosoildata, Ps.ALMaSS.CropState(croptype=ct, phase=Ps.ALMaSS.janfirst)) @test fp isa FarmPlot - @test fp isa FarmPlot{Ps.ALMaSS.CropState} + @test fp.cropstate isa Ps.ALMaSS.CropState @test croptype(fp) isa Ps.ALMaSS.CropType @test cropname(fp) isa String @test cropheight(fp) isa Length{Float64} @@ -48,7 +48,7 @@ end farmer = 0 fp = FarmPlot(id, pixels, farmer, Ps.nosoildata, Ps.SimpleCrop.CropState(ct, 0.0cm)) @test fp isa FarmPlot - @test fp isa FarmPlot{Ps.SimpleCrop.CropState} + @test fp.cropstate isa Ps.SimpleCrop.CropState @test croptype(fp) isa Ps.SimpleCrop.CropType @test cropname(fp) isa String @test cropheight(fp) isa Length{Float64} @@ -67,7 +67,7 @@ end soiltype = Ps.sand fp = FarmPlot(id, pixels, farmer, soiltype, PsAC.CropState(ct, soiltype, model)) @test fp isa FarmPlot - @test fp isa FarmPlot{PsAC.CropState} + @test fp.cropstate isa PsAC.CropState @test croptype(fp) isa PsAC.CropType @test cropname(fp) isa String @test cropheight(fp) isa Length{Float64} diff --git a/test/runtests.jl b/test/runtests.jl index 906be637f6631b120b328d326a8b7fc2cc122f18..3ca43643d9b4ba553548c48a840e6d7136ca35c1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -46,7 +46,7 @@ function inittestmodel(smallmap=true) TESTSETTINGS["core.enddate"]) # TODO: support other crop models besides ALMaSS crops = Ps.ALMaSS.readcropparameters(TESTSETTINGS["crop.cropdirectory"]) - model = AgricultureModel{Ps.ALMaSS.CropType,Ps.ALMaSS.CropState}(; + model = AgricultureModel(; settings = copy(TESTSETTINGS), rng = StableRNG(TESTSETTINGS["core.seed"]), logger = global_logger(),