From f89a240c250a4701dfbc6f32a9f0f2abf3377568 Mon Sep 17 00:00:00 2001 From: Marco Matthies <71844+marcom@users.noreply.github.com> Date: Tue, 16 Jul 2024 18:51:30 +0200 Subject: [PATCH] Introduce switchable crop model SimpleCrop --- src/Persefone.jl | 1 + src/core/simulation.jl | 25 ++++++++++++++++++++--- src/crop/almass.jl | 43 ++++++++++++++------------------------- src/crop/farmplot.jl | 32 +++++++++++++++++++++++++++++ src/crop/simplecrop.jl | 22 ++++++++++++++++++-- src/parameters.toml | 2 +- test/test_parameters.toml | 2 +- 7 files changed, 92 insertions(+), 35 deletions(-) diff --git a/src/Persefone.jl b/src/Persefone.jl index 4e423a0..0517e45 100644 --- a/src/Persefone.jl +++ b/src/Persefone.jl @@ -140,6 +140,7 @@ include("world/weather.jl") include("crop/farmplot.jl") include("crop/almass.jl") +include("crop/simplecrop.jl") include("farm/farm.jl") include("nature/insects.jl") diff --git a/src/core/simulation.jl b/src/core/simulation.jl index 70ab962..7c2e8f2 100644 --- a/src/core/simulation.jl +++ b/src/core/simulation.jl @@ -118,13 +118,24 @@ function initmodel(settings::Dict{String, Any}) settings["world.weatherfile"]), settings["core.startdate"], settings["core.enddate"]) + + # TODO: make this switching on cropmodel simpler if settings["crop.cropmodel"] == "almass" crops = ALMaSS.readcropparameters(settings["crop.cropfile"], settings["crop.growthfile"]) - farmplots = Vector{ALMaSS.FarmPlot}() + Tfarmplot = ALMaSS.FarmPlot + elseif settings["crop.cropmodel"] == "simple" + # TODO: generalise ALMaSS.CropType? + # crops = Dict{String, ALMaSS.CropType}() + crops = ALMaSS.readcropparameters(settings["crop.cropfile"], + settings["crop.growthfile"]) + Tfarmplot = SimpleCrop.FarmPlot else error("Init for crop model \"$(settings["crop.cropmodel"])\" not implemented") end + + farmers = Vector{Farmer}() + farmplots = Vector{Tfarmplot}() model = AgricultureModel(settings, StableRNG(settings["core.seed"]), logger, @@ -134,13 +145,21 @@ function initmodel(settings::Dict{String, Any}) landscape, weather, crops, - Vector{Farmer}(), + farmers, farmplots, Vector{Union{Animal,Nothing}}(), Vector{Pair{Animal, Date}}(), Vector{FarmEvent}()) saveinputfiles(model) - ALMaSS.initfields!(model) + + if settings["crop.cropmodel"] == "almass" + ALMaSS.initfields!(model) + elseif settings["crop.cropmodel"] == "simple" + SimpleCrop.initfields!(model) + else + error("initfields! for crop model \"$(settings["crop.cropmodel"])\" not implemented") + end + initfarms!(model) initnature!(model) model diff --git a/src/crop/almass.jl b/src/crop/almass.jl index 8d26660..4c8f708 100644 --- a/src/crop/almass.jl +++ b/src/crop/almass.jl @@ -7,7 +7,14 @@ module ALMaSS -using Persefone: AbstractFarmPlot, EventType, SimulationModel, maxtemp, mintemp, fertiliser +using Persefone: + AbstractFarmPlot, + EventType, + SimulationModel, + fertiliser, + initfields_fill_with!, + maxtemp, + mintemp import Persefone: stepagent! using Dates: Date, month, monthday using CSV: CSV @@ -156,34 +163,14 @@ end Initialise the model with its farm plots. """ function initfields!(model::SimulationModel) - n = 0 - convertid = Dict{Int64,Int64}() - width, height = size(model.landscape) - for x in 1:width - for y in 1:height - # for each pixel, we need to extract the field ID given by the map input - # file, and convert it into the internal object ID used by Agents.jl, - # creating a new agent object if necessary - rawid = model.landscape[x,y].fieldid - (ismissing(rawid)) && continue - if rawid in keys(convertid) - objectid = convertid[rawid] - model.landscape[x,y].fieldid = objectid - push!(model.farmplots[objectid].pixels, (x,y)) - else - #XXX does this phase calculation work out? - month(model.date) < 3 ? phase = ALMaSS.janfirst : phase = ALMaSS.marchfirst - fp = FarmPlot(length(model.farmplots)+1, [(x,y)], - model.crops["natural grass"], phase, - 0.0, 0.0, 0.0, 0.0, Vector{EventType}()) - push!(model.farmplots, fp) - model.landscape[x,y].fieldid = fp.id - convertid[rawid] = fp.id - n += 1 - end - end + initfields_fill_with!(model) do model, x, y + month(model.date) < 3 ? phase = ALMaSS.janfirst : phase = ALMaSS.marchfirst + FarmPlot(length(model.farmplots) + 1, + [(x,y)], + model.crops["natural grass"], + phase, + 0.0, 0.0, 0.0, 0.0, Vector{EventType}()) end - @info "Initialised $n farm plots." end """ diff --git a/src/crop/farmplot.jl b/src/crop/farmplot.jl index 4bd2721..4272e05 100644 --- a/src/crop/farmplot.jl +++ b/src/crop/farmplot.jl @@ -214,3 +214,35 @@ end +""" + initfields_fill_with!(make_farmplot_fn, model) + +Initialise the model with its farm plots, using the +`make_farmplot_fn(model, x, y)` function to create new farmplots. +""" +function initfields_fill_with!(make_farmplot_fn::Function, model::SimulationModel) + n = 0 + convertid = Dict{Int64,Int64}() + width, height = size(model.landscape) + for x in 1:width + for y in 1:height + # for each pixel, we need to extract the field ID given by the map input + # file, and convert it into the internal object ID used by Agents.jl, + # creating a new agent object if necessary + rawid = model.landscape[x,y].fieldid + (ismissing(rawid)) && continue + if rawid in keys(convertid) + objectid = convertid[rawid] + model.landscape[x,y].fieldid = objectid + push!(model.farmplots[objectid].pixels, (x,y)) + else + fp = make_farmplot_fn(model, x, y) + push!(model.farmplots, fp) + model.landscape[x,y].fieldid = fp.id + convertid[rawid] = fp.id + n += 1 + end + end + end + @info "Initialised $n farm plots." +end diff --git a/src/crop/simplecrop.jl b/src/crop/simplecrop.jl index b903db5..d96cf30 100644 --- a/src/crop/simplecrop.jl +++ b/src/crop/simplecrop.jl @@ -1,13 +1,31 @@ - module SimpleCrop +using Persefone: AbstractFarmPlot, SimulationModel, initfields_fill_with! +import Persefone: stepagent! + +mutable struct FarmPlot <: AbstractFarmPlot + const id::Int64 + pixels::Vector{Tuple{Int64, Int64}} +end + """ stepagent!(farmplot, model) Update a farm plot by one day. """ function stepagent!(farmplot::FarmPlot, model::SimulationModel) + # TODO: do something simple end -end # module SimpleCrop +""" + initfields!(model) +Initialise the model with its farm plots. +""" +function initfields!(model::SimulationModel) + initfields_fill_with!(model) do model, x, y + FarmPlot(length(model.farmplots) + 1, [(x,y)]) + end +end + +end # module SimpleCrop diff --git a/src/parameters.toml b/src/parameters.toml index 2f2215b..659e887 100644 --- a/src/parameters.toml +++ b/src/parameters.toml @@ -40,7 +40,7 @@ indoutfreq = "end" # output frequency individual-level data, daily/monthly/yearl insectmodel = ["season", "habitat", "pesticides", "weather"] # factors affecting insect growth [crop] -cropmodel = "almass" # crop growth model to use, "almass" or "aquacrop" +cropmodel = "almass" # crop growth model to use, "almass", "aquacrop", or "simple" cropfile = "data/crops/almass/crop_data_general.csv" # file with general crop parameters growthfile = "data/crops/almass/almass_crop_growth_curves.csv" # file with crop growth parameters diff --git a/test/test_parameters.toml b/test/test_parameters.toml index 4d88eb3..94d6a9a 100644 --- a/test/test_parameters.toml +++ b/test/test_parameters.toml @@ -38,7 +38,7 @@ indoutfreq = "daily" # output frequency individual-level data, daily/monthly/yea insectmodel = ["season", "habitat", "pesticides"] # which factors affect insect growth ("weather" is not yet implemented) [crop] -cropmodel = "almass" # crop growth model to use, "almass" or "aquacrop" +cropmodel = "almass" # crop growth model to use, "almass", "aquacrop", or "simple" cropfile = "crop_data_general.csv" # file with general crop parameters growthfile = "almass_crop_growth_curves.csv" # file with crop growth parameters -- GitLab