diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d58db8c2fb6affbfcdb442f029ae879525259d8..3bf09dd81c4c1fc5a14b3ca337fccb671eda2748 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 ce1b330c3c984993168a105d400d7ffa98b9e89b..70d6208cc3719da26b5a0ccdeec643898fd8fe02 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 16d48240d58dee5cb0dad12ed932e68bc5d50a93..03fa7d5b0ad32cb24de59dffd81199b2f9253a45 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 2b394d796c91bd20d153c62543cd6e85f5837d91..8281283c3c2658036ab433add1087a29cefb0ba9 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 739544a5bab3a73914fac10ff345b48f901cea91..7b1b90123511192bf6c67d9fdc77f0c7a1002c92 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 9a3eefe5087e3a5b3139e460e8cf25a96b43f276..9842feff3606def29cb43ec229c9596699a73537 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 fbc42358ea062a75d7c3655134dbcfc8c3779864..05158aa6590a6e4bcc38941ca6332d45f734b778 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 848858a767c37c2d8b06e0cad27f0245e1cacea4..c9ecca8cfa7d84f299769d347ef5cd9a424a0433 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 272bb2595d91012f9d0d1be12bacd496397cb16f..d956e37d6bf30102c9e810676a2a8a5a0f37e4f1 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 f310cc611cd80d541c32deff3856f368e83830a7..36a47febe98f6f201b09434e2f6cd62a37bcbf3d 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 db4870c5988f9975de6266b066315f23c82bf33a..8540f05b24689f69b43ef6a28169b1e4a2b68e34 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 3a247041e88c97a9d492085d917612afd8da318d..33413f85989fe7c08935a3c87020d09cb005167b 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