From a1e8f923bf0c52812939864288b1f8cddfc5221c Mon Sep 17 00:00:00 2001 From: Daniel Vedder <daniel.vedder@idiv.de> Date: Wed, 12 Jun 2024 10:31:31 +0200 Subject: [PATCH] Added world.mapdirectory parameter Also updated CONTRIBUTORS.md, added a "none" option for `core.logoutput`, and added type annotations to the Skylark struct --- CHANGELOG.md | 22 ++++++++++++++++++---- CONTRIBUTORS.md | 22 +++++++++------------- LICENSE | 2 +- README.md | 2 +- src/analysis/makieplots.jl | 7 +++++-- src/core/output.jl | 8 +++++--- src/core/simulation.jl | 6 ++++-- src/nature/species/skylark.jl | 34 +++++++++++++++------------------- src/parameters.toml | 13 +++++++------ src/world/landscape.jl | 6 ++++-- test/runtests.jl | 6 ++++-- test/test_parameters.toml | 1 + 12 files changed, 74 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d58db8..3bf09dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v0.5.1] - unreleased + +### Added + +- `core.logoutput` parameter to define whether logs are printed to screen, file, none, or both + +- `world.mapdirectory` parameter specifies the path to the directory in which + `landcovermap`, `farmfieldsmap`, and `weatherfile` are located + +### Changed + +### Deprecated + +### Removed + +### Fixed + + ## [v0.5.0] - 07-06-2024 This release doesn't add much new functionality, but represents a major restructuring @@ -45,14 +63,10 @@ way the species definition macros work and are used. - requires Julia 1.10 -### Deprecated - ### Removed - Agents.jl dependency (including `AgentBasedModel` and functions for adding/moving/removing agents) -### Fixed - ## [v0.4.1] - 2023-11-14 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ce1b330..70d6208 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -2,28 +2,24 @@ ### Lead developer -- Daniel Vedder +- Daniel Vedder (daniel.vedder@idiv.de) +### Supervisor -### Supporting developer(s) - -- Lea Kolb +- Guy Pe'er (guy.peer@idiv.de) +### Supporting developers -### Code reviewer +- Marco Matthies +- Gabriel DÃaz Iturry -- Ludmilla Figueiredo +### Code reviewers -### Supervisor - -- Guy Pe'er - +- Ludmilla Figueiredo +- Marco Matthies ### Advisors - Aletta Bonn - - Kerstin Wiegand - -- Birgit Müller diff --git a/LICENSE b/LICENSE index 16d4824..03fa7d5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT LICENSE -Copyright (c) 2022-2023 Daniel Vedder, Lea Kolb +Copyright (c) 2022-2024 Persefone.jl developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 2b394d7..8281283 100644 --- a/README.md +++ b/README.md @@ -86,4 +86,4 @@ You can then access all Persefone functions, such as `simulate()`. (See --- -© 2022-2023 the contributors (MIT license) +© 2022-2024 the contributors (MIT license) diff --git a/src/analysis/makieplots.jl b/src/analysis/makieplots.jl index 739544a..7b1b901 100644 --- a/src/analysis/makieplots.jl +++ b/src/analysis/makieplots.jl @@ -4,7 +4,7 @@ ### """ - visualisemap(model, date, landcovermap) + visualisemap(model, date, landcover) Draw the model's land cover map and plot all individuals as points on it at the specified date. If no date is passed, use the last date for which data @@ -15,7 +15,10 @@ Returns a Makie figure object. function visualisemap(model::SimulationModel,date=nothing,landcover=nothing) # load and plot the map # Note: if the landcover map is supplied, it needs to be rotr90'ed - isnothing(landcover) && (landcover = rotr90(load(@param(world.landcovermap)))) + if isnothing(landcover) + lcm = joinpath(@param(world.mapdirectory), @param(world.landcovermap)) + landcover = rotr90(load(lcm)) + end f = Figure() ax = Axis(f[1,1]) hidedecorations!(ax) diff --git a/src/core/output.jl b/src/core/output.jl index 9a3eefe..9842fef 100644 --- a/src/core/output.jl +++ b/src/core/output.jl @@ -32,7 +32,7 @@ end """ modellogger(loglevel, outdir, output="both") -Create a logger object that writes output both to screen and to a logfile. +Create a logger object that writes output to screen and/or a logfile. This object is stored as `model.logger` and can then be used with `with_logger()`. Note: requires [`createdatadir`](@ref) to be run first. """ @@ -51,6 +51,8 @@ function modellogger(loglevel::String, outdir::String, output::String="both") return ConsoleLogger(logfile, loglevel) elseif output == "screen" return ConsoleLogger(stdout, loglevel) + elseif output == "none" + return NullLogger() else Base.error("Invalid log output target $output, should be file/screen/both.") end @@ -95,8 +97,8 @@ function saveinputfiles(model::SimulationModel) TOML.print(f, prepareTOML(model.settings)) end # Copy the map files to the output folder - lcmap = @param(world.landcovermap) - ffmap = @param(world.farmfieldsmap) + lcmap = joinpath(@param(world.mapdirectory), @param(world.landcovermap)) + ffmap = joinpath(@param(world.mapdirectory), @param(world.farmfieldsmap)) #TODO replace errors with exceptions !(isfile(lcmap)) && Base.error("The map file $(lcmap) doesn't exist.") !(isfile(ffmap)) && Base.error("The map file $(ffmap) doesn't exist.") diff --git a/src/core/simulation.jl b/src/core/simulation.jl index fbc4235..05158aa 100644 --- a/src/core/simulation.jl +++ b/src/core/simulation.jl @@ -110,9 +110,11 @@ function initmodel(settings::Dict{String, Any}) settings["core.outdir"], settings["core.logoutput"]) with_logger(logger) do - landscape = initlandscape(settings["world.landcovermap"], + landscape = initlandscape(settings["world.mapdirectory"], + settings["world.landcovermap"], settings["world.farmfieldsmap"]) - weather = initweather(settings["world.weatherfile"], + weather = initweather(joinpath(settings["world.mapdirectory"], + settings["world.weatherfile"]), settings["core.startdate"], settings["core.enddate"]) crops = readcropparameters(settings["crop.cropfile"], diff --git a/src/nature/species/skylark.jl b/src/nature/species/skylark.jl index 848858a..c9ecca8 100644 --- a/src/nature/species/skylark.jl +++ b/src/nature/species/skylark.jl @@ -4,13 +4,13 @@ ### skylarkhabitat = @habitat((@landcover() == grass || - # settle on grass or arable land (but not maize) - (@landcover() == agriculture && @cropname() != "maize")) && - @distancetoedge() >= 5) # at least 50m from other habitats - #XXX this ought to check for distance to forest and builtup, - # but that's very expensive (see below) - # @distanceto(forest) > 5 && # at least 50m from forest edges - # @distanceto(builtup) > 5) # and from anthropogenic structures + # settle on grass or arable land (but not maize) + (@landcover() == agriculture && @cropname() != "maize")) && + @distancetoedge() >= 5) # at least 50m from other habitats +#XXX this ought to check for distance to forest and builtup, +# but that's very expensive (see below) +# @distanceto(forest) > 5 && # at least 50m from forest edges +# @distanceto(builtup) > 5) # and from anthropogenic structures """ Skylark @@ -30,32 +30,29 @@ At the moment, this implementation is still in development. ISBN 3-89104-019-9 """ @species Skylark begin - #XXX use Unitful.jl - #TODO add type annotations - eggtime::Int64 = 11 # 11 days from laying to hatching eggpredationmortality::Float64 = 0.03 # per-day egg mortality from predation nestharvestmortality::Float64 = 0.9 # egg/nestling mortality after a harvest event (XXX guess) - nestlingtime = 7:11 # 7-11 days from hatching to leaving nest + nestlingtime::Union{Int64,UnitRange{Int64}} = 7:11 # 7-11 days from hatching to leaving nest nestlingpredationmortality::Float64 = 0.03 # per-day nestling mortality from predation - fledglingtime = 25:30 # 25-30 days from hatching to independence + fledglingtime::Union{Int64,UnitRange{Int64}} = 25:30 # 25-30 days from hatching to independence fledglingharvestmortality::Float64 = 0.5 # fledgling mortality after harvest fledglingpredationmortality::Float64 = 0.01 # per-day fledgling mortality from predation firstyearmortality::Float64 = 0.38 # total mortality in the first year after independence - migrationdates = () # is defined by each individual in @create(Skylark) + migrationdates::Tuple = () # is defined by each individual in @create(Skylark) migrationmortality::Float64 = 0.33 # chance of dying during the winter mate::Int64 = -1 # the agent ID of the mate (-1 if none) - nest = () # coordinates of current nest - nestingbegin = (April, 10) # begin nesting in the middle of April - nestbuildingtime = 4:5 # 4-5 days needed to build a nest (doubled for first nest) + nest::Tuple = () # coordinates of current nest + nestingbegin::Tuple{Int64,Int64} = (April, 10) # begin nesting in the middle of April + nestbuildingtime::Union{Int64,UnitRange{Int64}} = 4:5 # 4-5 days needed to build a nest (doubled for first nest) nestcompletion::Int64 = 0 # days left until the nest is built - eggsperclutch = 2:5 # 2-5 eggs laid per clutch - clutch = [] # IDs of offspring in current clutch + eggsperclutch::Union{Int64,UnitRange{Int64}} = 2:5 # 2-5 eggs laid per clutch + clutch::Vector{Int64} = Vector{Int64}() # IDs of offspring in current clutch breedingdelay::Int64 = 18 # wait 18 days after hatching to start a new brood nestingend::Int64 = July # last month of nesting @@ -68,7 +65,6 @@ As an egg, simply check for mortality and hatching. @phase Skylark egg begin @kill(self.eggpredationmortality, "predation") @respond(harvesting, @kill(self.nestharvestmortality, "harvest")) - if self.age == self.eggtime @setphase(nestling) end diff --git a/src/parameters.toml b/src/parameters.toml index 272bb25..d956e37 100644 --- a/src/parameters.toml +++ b/src/parameters.toml @@ -10,7 +10,7 @@ configfile = "src/parameters.toml" # location of the configuration file outdir = "results" # location and name of the output folder overwrite = "ask" # overwrite the output directory? (true/false/"ask") -logoutput = "both" # log output to screen/file/both +logoutput = "both" # log output to screen/file/none/both csvoutput = true # save collected data in CSV files visualise = true # generate result graphs storedata = true # keep collected data in memory @@ -19,13 +19,14 @@ loglevel = "debug" # verbosity level: "debug", "info", "warn" processors = 2 # number of processors to use on parallel runs seed = 2 # seed value for the RNG (0 -> random value) startdate = 2022-01-01 # first day of the simulation -#enddate = 2022-12-31 # last day of the simulation -enddate = 2022-03-31 # last day of the simulation (test value) +enddate = 2022-12-31 # last day of the simulation +#enddate = 2022-03-31 # last day of the simulation (test value) [world] -landcovermap = "data/regions/jena-small/landcover.tif" # location of the landcover map -farmfieldsmap = "data/regions/jena-small/fields.tif" # location of the field geometry map -weatherfile = "data/regions/jena-small/weather.csv" # location of the weather data file +mapdirectory = "data/regions/jena-small" # the directory in which all geographic data are stored +landcovermap = "landcover.tif" # name of the landcover map in the map directory +farmfieldsmap = "fields.tif" # name of the field geometry map in the map directory +weatherfile = "weather.csv" # name of the weather data file in the map directory [farm] farmmodel = "FieldManager" # which version of the farm model to use (not yet implemented) diff --git a/src/world/landscape.jl b/src/world/landscape.jl index f310cc6..36a47fe 100644 --- a/src/world/landscape.jl +++ b/src/world/landscape.jl @@ -38,13 +38,15 @@ mutable struct FarmEvent end """ - initlandscape(landcovermap, farmfieldsmap) + initlandscape(directory, landcovermap, farmfieldsmap) Initialise the model landscape based on the map files specified in the configuration. Returns a matrix of pixels. """ -function initlandscape(landcovermap::String, farmfieldsmap::String) +function initlandscape(directory::String, landcovermap::String, farmfieldsmap::String) @debug "Initialising landscape" + landcovermap = joinpath(directory, landcovermap) + farmfieldsmap = joinpath(directory, farmfieldsmap) #TODO replace errors with exception !(isfile(landcovermap)) && Base.error("Landcover map $(landcovermap) doesn't exist.") !(isfile(farmfieldsmap)) && Base.error("Field map $(farmfieldsmap) doesn't exist.") diff --git a/test/runtests.jl b/test/runtests.jl index db4870c..8540f05 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -31,10 +31,12 @@ function inittestmodel(smallmap=true) if smallmap landscape = smalltestlandscape() else - landscape = Ps.initlandscape(TESTSETTINGS["world.landcovermap"], + landscape = Ps.initlandscape(TESTSETTINGS["world.mapdirectory"], + TESTSETTINGS["world.landcovermap"], TESTSETTINGS["world.farmfieldsmap"]) end - weather = Ps.initweather(TESTSETTINGS["world.weatherfile"], + weather = Ps.initweather(joinpath(TESTSETTINGS["world.mapdirectory"], + TESTSETTINGS["world.weatherfile"]), TESTSETTINGS["core.startdate"], TESTSETTINGS["core.enddate"]) crops = Ps.readcropparameters(TESTSETTINGS["crop.cropfile"], diff --git a/test/test_parameters.toml b/test/test_parameters.toml index 3a24704..33413f8 100644 --- a/test/test_parameters.toml +++ b/test/test_parameters.toml @@ -21,6 +21,7 @@ startdate = 2022-02-01 enddate = 2022-03-31 [world] +mapdirectory = "." landcovermap = "landcover_jena.tif" # location of the landcover map farmfieldsmap = "fields_jena.tif" # location of the field geometry map weatherfile = "weather_jena.csv" # location of the weather data file -- GitLab