From e82aa559f8c3845405f7661a10dfe66792b37dee Mon Sep 17 00:00:00 2001 From: Daniel Vedder <daniel.vedder@idiv.de> Date: Tue, 18 Jul 2023 10:01:34 +0200 Subject: [PATCH] Rewrote insect submodel It is now contained within a single function that can be called at need, rather than being calculated every day for every pixel. --- src/core/simulation.jl | 1 - src/nature/insects.jl | 124 ++++++++++++++++++++++------------------- src/parameters.toml | 2 +- src/world/landscape.jl | 3 +- test/nature_tests.jl | 10 +++- test/runtests.jl | 4 +- 6 files changed, 80 insertions(+), 64 deletions(-) diff --git a/src/core/simulation.jl b/src/core/simulation.jl index 96a9f92..18c0dd0 100644 --- a/src/core/simulation.jl +++ b/src/core/simulation.jl @@ -118,7 +118,6 @@ Execute one update of the model. function stepsimulation!(model::AgentBasedModel) with_logger(model.logger) do @info "Simulating day $(model.date)." - updateinsectbiomass!(model) for a in Schedulers.ByType((Farmer,FarmPlot,Animal), true)(model) try #The animal may have been killed stepagent!(model[a], model) diff --git a/src/nature/insects.jl b/src/nature/insects.jl index 7838492..a513dd8 100644 --- a/src/nature/insects.jl +++ b/src/nature/insects.jl @@ -1,75 +1,85 @@ ### Persefone - a socio-economic-ecological model of European agricultural landscapes. ### -### This file contains the functions for calculating insect biomass. +### This file contains submodel that calculates insect biomass ### -##FIXME Updating every pixel every day is really expensive! I should seriously -## think about switching this to a "calculate-on-demand" design. -## (The problem is that that works for all models except the pesticide one, which -## is a Markov Chain model that cannot be calculated without knowing the previous -## value at the location.) - ##TODO write tests for this """ - updateinsectbiomass!(model) - -Calculate a new insect biomass value for each pixel in the landscape. -""" -function updateinsectbiomass!(model::AgentBasedModel) - @debug "Updating insect biomass" - if @param(nature.insectmodel) == "seasonal" - updatefunction = seasonalmodel - elseif @param(nature.insectmodel) == "seasonalhabitat" - updatefunction = seasonalhabitatmodel - else - Base.error("Unknown insect model $(@param(nature.insectmodel)).") - #TODO replace with exception - end - for p in model.landscape - p.insectbiomass = updatefunction(p, model) - end -end + insectbiomass(pixel, model) +Calculate the insect biomass in this location, using the factors configured +in the `nature.insectmodel` settings (any of: "season", "habitat", "weather", +"pesticides"). Returns a float value in mg/m². -""" - seasonalmodel(pixel) +**Biological note:** this is a very approximate calculation! Insect biomass +varies wildly in time and space and is hard to measure. This calculation is +based on the idea of a parabolic seasonal development of insect abundance, +modified habitat suitability, weather, and pesticide application. Although it +is based on empirical studies, it can only deliver a rough, order-of-magnitude +estimation of likely insect biomass in a given location. -Calculate an insect biomass value based purely on the calendar day, following -a parabolic function as per Paquette et al. (2013). Assumes a maximum insect -density of 300 mg/m² (cf. Odderskaer et al. 1997, Püttmanns et al. 2022). +**Sources:** -*[Note that this is a very approximate calculation!]* +- Odderskær et al. (1997). Skylark Reproduction in Pesticide Treated and +Untreated Fields (32; Pesticides Research). Danish Environmental Protection Agency. +- Grüebler et al. (2008). A predictive model of the density of airborne +insects in agricultural environments. Agriculture, Ecosystems & Environment, +123(1), 75–80. https://doi.org/10.1016/j.agee.2007.05.001 +- Paquette et al. (2013). Seasonal patterns in Tree Swallow prey (Diptera) +abundance are affected by agricultural intensification. Ecological Applications, +23(1), 122–133. https://doi.org/10.1890/12-0068.1 +- Püttmanns et al. (2022). Habitat use and foraging parameters of breeding +Skylarks indicate no seasonal decrease in food availability in heterogeneous +farmland. Ecology and Evolution, 12(1), e8461. https://doi.org/10.1002/ece3.8461 """ -function seasonalmodel(pixel::Pixel, model::AgentBasedModel) - calendarday = dayofyear(model.date) - biomass = -0.085(calendarday-187)^2+300 - biomass > 0 ? biomass : 0 -end +function insectbiomass(pixel::Pixel, model::AgentBasedModel)::Float64 -""" - seasonalhabitatmodel(pixel) + ## if no factors are configure, insect abundance defaults to 300 mg/m², + ## a value in the upper range of insect biomass density in agricultural landscapes + baseline = 300.0 + seasonfactor = 0.0 + habitatfactor = 1.0 + weatherfactor = 1.0 + pesticidefactor = 1.0 -Calculate an insect biomass value based on the calendar day and habitat type, -following a parabolic function. + ## parabolic curve of seasonal development, + ## based on fig. 3a in Paquette et al. (2013) + if "season" in @param(nature.insectmodel) + calendarday = dayofyear(model.date) + seasonfactor = -0.085(calendarday-187)^2 + end -See Odderskaer et al. (1997), Grübler et al. (2008), Paquette et al. (2013), -and Püttmanns et al. (2022). + ## habitat dependence of insect biomass, + ## based on fig. 1 in Grübler et al. (2008) and fig. 5c in Püttmanns et al. (2022) + if "habitat" in @param(nature.insectmodel) + if pixel.landcover == soil || pixel.landcover == forest + habitatfactor = 2.0 + elseif pixel.landcover == grass + habitatfactor = 1.5 + elseif pixel.landcover == agriculture + habitatfactor = 1.0 + else + habitatfactor = 0.0 + end + end -*[Note that this is a very approximate calculation!]* -""" -function seasonalhabitatmodel(pixel::Pixel, model::AgentBasedModel) - calendarday = dayofyear(model.date) - habitatfactor = 0.0 - if pixel.landcover == soil || pixel.landcover == forest - habitatfactor = 2.0 - elseif pixel.landcover == grass - habitatfactor = 1.5 - elseif pixel.landcover == agriculture - habitatfactor = 1.0 + ## temperature dependence of insect biomass, + ## based on fig. 3b in Paquette et al. (2013) + ##XXX (and possibly table 3 in Grübler et al. (2008)) + if "weather" in @param(nature.insectmodel) + #TODO add this once we have a working weather module + end + + ## effect of pesticides on insect abundance, + ## based on figs. 3.6 and 3.7 in Odderskær et al. (1997) + ## Note that this is a simplification: it ignores that insect biomass + ## tends to rise rapidly a few weeks after pesticide application. + if "pesticides" in @param(nature.insectmodel) && pesticide in pixel.events + pesticidefactor = 0.5 end - biomass = -0.085(calendarday-187)^2+300*habitatfactor - biomass > 0 ? biomass : 0 -end -#TODO add models that include weather and pesticide application + ## calculate biomass using a parabolic equation in the vertex form + biomass = seasonfactor+baseline*habitatfactor*pesticidefactor*weatherfactor + biomass > 0 ? biomass : 0.0 +end diff --git a/src/parameters.toml b/src/parameters.toml index 0838a3f..abd543d 100644 --- a/src/parameters.toml +++ b/src/parameters.toml @@ -29,7 +29,7 @@ farmmodel = "FieldManager" # which version of the farm model to use (not yet imp targetspecies = ["Wolpertinger", "Wyvern"] # list of target species to simulate popoutfreq = "daily" # output frequency population-level data, daily/monthly/yearly/end/never indoutfreq = "end" # output frequency individual-level data, daily/monthly/yearly/end/never -insectmodel = "seasonalhabitat" # how to calculate insect biomass, seasonal/seasonalhabitat/full +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) diff --git a/src/world/landscape.jl b/src/world/landscape.jl index e3dcd15..d6b16f6 100644 --- a/src/world/landscape.jl +++ b/src/world/landscape.jl @@ -21,7 +21,6 @@ mutable struct Pixel landcover::LandCover fieldid::Union{Missing,Int64} events::Vector{EventType} - insectbiomass::Float64 # in mg/m² end """ @@ -58,7 +57,7 @@ function initlandscape(landcovermap::String, farmfieldsmap::String) lcv = LandCover(Int(lc/10)) ff = farmfields[x,y][1] !(ismissing(ff)) && (ff = Int64(ff)) - landscape[x,y] = Pixel(lcv, ff, Vector{Symbol}(), 0.0) + landscape[x,y] = Pixel(lcv, ff, Vector{Symbol}()) end end return landscape diff --git a/test/nature_tests.jl b/test/nature_tests.jl index 2f483f9..33260d9 100644 --- a/test/nature_tests.jl +++ b/test/nature_tests.jl @@ -6,7 +6,7 @@ @testset "Habitat macros" begin # set up the testing landscape model = smalltestlandscape() - model.landscape[6,6] = Pixel(Ps.agriculture, 1, [], 0.0) + model.landscape[6,6] = Pixel(Ps.agriculture, 1, []) species::Dict{String,Any} = Dict("name"=>"test_animal") add_agent!((6,6), FarmPlot, model, [(6,6)], Ps.wheat, 1.2, 3.4) add_agent!((3,3), Animal, model, species, Ps.male, 1) @@ -136,3 +136,11 @@ end @test typeof(@rand()) == Float64 @test @rand([true, true]) end + + +@testset "Insect submodel" begin + model = smalltestlandscape() + + #TODO create a set of pixels and check if their calculated biomass + # meets the expected values +end diff --git a/test/runtests.jl b/test/runtests.jl index db3a518..a1de751 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -37,10 +37,10 @@ function smalltestlandscape() for x in 1:6 for y in 1:6 (x in (1:2) || y in (1:4)) ? lc = Ps.forest : lc = Ps.grass - landscape[x,y] = Pixel(lc, missing, [], 0.0) + landscape[x,y] = Pixel(lc, missing, []) end end - landscape[6,4] = Pixel(Ps.water, 0, [], 0.0) + landscape[6,4] = Pixel(Ps.water, 0, []) space = GridSpace(size(landscape), periodic=false) properties = Dict{Symbol,Any}(:date=>TESTSETTINGS["core.startdate"], :landscape=>landscape, -- GitLab