From 92fcb60fea05fe7d2e6599a2910afec608ae3e62 Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Mon, 19 Aug 2024 16:00:18 +0200 Subject: [PATCH 01/27] Bump version and update Manifest.toml files (#449) --- Manifest.toml | 196 +++++++++----- Project.toml | 2 +- build/create_binaries/Manifest.toml | 248 ++++++++++-------- build/wflow_cli/Manifest.toml | 220 ++++++++++------ .../Setup/Installer/Application.Setup.wixproj | 2 +- docs/src/changelog.md | 2 +- server/Manifest.toml | 232 +++++++++------- 7 files changed, 546 insertions(+), 356 deletions(-) diff --git a/Manifest.toml b/Manifest.toml index e66d73e35..7f8e34277 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -2,7 +2,7 @@ julia_version = "1.10.0" manifest_format = "2.0" -project_hash = "d07ae13655ba6d661946c37e1c2a11d4acab567d" +project_hash = "d28d785486f7576a44ae0401e6a18f49d6f3007c" [[deps.AbstractTrees]] git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177" @@ -30,18 +30,20 @@ uuid = "ec485272-7323-5ecc-a04f-4719b315124d" version = "0.4.0" [[deps.ArrayInterface]] -deps = ["Adapt", "LinearAlgebra", "SparseArrays", "SuiteSparse"] -git-tree-sha1 = "44691067188f6bd1b2289552a23e4b7572f4528d" +deps = ["Adapt", "LinearAlgebra"] +git-tree-sha1 = "f54c23a5d304fb87110de62bace7777d59088c34" uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" -version = "7.9.0" +version = "7.15.0" [deps.ArrayInterface.extensions] ArrayInterfaceBandedMatricesExt = "BandedMatrices" ArrayInterfaceBlockBandedMatricesExt = "BlockBandedMatrices" ArrayInterfaceCUDAExt = "CUDA" + ArrayInterfaceCUDSSExt = "CUDSS" ArrayInterfaceChainRulesExt = "ChainRules" ArrayInterfaceGPUArraysCoreExt = "GPUArraysCore" ArrayInterfaceReverseDiffExt = "ReverseDiff" + ArrayInterfaceSparseArraysExt = "SparseArrays" ArrayInterfaceStaticArraysCoreExt = "StaticArraysCore" ArrayInterfaceTrackerExt = "Tracker" @@ -49,9 +51,11 @@ version = "7.9.0" BandedMatrices = "aae01518-5342-5314-be14-df237901396f" BlockBandedMatrices = "ffab5731-97b5-5995-9138-79e8c1846df0" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" + CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" ChainRules = "082447d4-558c-5d27-93f4-14fc19e9eca2" GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527" ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" + SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" @@ -68,9 +72,15 @@ version = "0.1.0" [[deps.BitTwiddlingConvenienceFunctions]] deps = ["Static"] -git-tree-sha1 = "0c5f81f47bbbcf4aea7b2959135713459170798b" +git-tree-sha1 = "f21cfd4950cb9f0587d5067e69405ad2acd27b87" uuid = "62783981-4cbd-42fc-bca8-16325de8dc4b" -version = "0.1.5" +version = "0.1.6" + +[[deps.Blosc_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Lz4_jll", "Zlib_jll", "Zstd_jll"] +git-tree-sha1 = "19b98ee7e3db3b4eff74c5c9c72bf32144e24f10" +uuid = "0b7ba130-8d10-5ba8-a3d6-c5182647fed9" +version = "1.21.5+0" [[deps.Bzip2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] @@ -86,27 +96,32 @@ version = "0.1.3" [[deps.CPUSummary]] deps = ["CpuId", "IfElse", "PrecompileTools", "Static"] -git-tree-sha1 = "601f7e7b3d36f18790e2caf83a882d88e9b71ff1" +git-tree-sha1 = "5a97e67919535d6841172016c9530fd69494e5ec" uuid = "2a0fbf3d-bb9c-48f3-b0a9-814d99fd7ab9" -version = "0.2.4" +version = "0.2.6" [[deps.CloseOpenIntervals]] deps = ["Static", "StaticArrayInterface"] -git-tree-sha1 = "70232f82ffaab9dc52585e0dd043b5e0c6b714f1" +git-tree-sha1 = "05ba0d07cd4fd8b7a39541e31a7b0254704ea581" uuid = "fb6a15b2-703c-40df-9091-08a04967cfa9" -version = "0.1.12" +version = "0.1.13" [[deps.CommonDataModel]] -deps = ["CFTime", "DataStructures", "Dates", "Preferences", "Printf"] -git-tree-sha1 = "7f5717cbb2c1ce650cfd454451f282df33103596" +deps = ["CFTime", "DataStructures", "Dates", "Preferences", "Printf", "Statistics"] +git-tree-sha1 = "d6fb5bf939a2753c74984b11434ea25d6c397a58" uuid = "1fbeeb36-5f17-413c-809b-666fb144f157" -version = "0.2.5" +version = "0.3.6" + +[[deps.CommonWorldInvalidations]] +git-tree-sha1 = "ae52d1c52048455e85a387fbee9be553ec2b68d0" +uuid = "f70d9fcc-98c5-4d4a-abd7-e4cdeebd8ca8" +version = "1.0.0" [[deps.Compat]] deps = ["TOML", "UUIDs"] -git-tree-sha1 = "c955881e3c981181362ae4088b35995446298b80" +git-tree-sha1 = "8ae8d32e09f0dcf42a36b90d4e17f5dd2e4c4215" uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "4.14.0" +version = "4.16.0" weakdeps = ["Dates", "LinearAlgebra"] [deps.Compat.extensions] @@ -125,9 +140,9 @@ version = "0.3.1" [[deps.DataStructures]] deps = ["Compat", "InteractiveUtils", "OrderedCollections"] -git-tree-sha1 = "97d79461925cdb635ee32116978fc735b9463a39" +git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82" uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.18.19" +version = "0.18.20" [[deps.Dates]] deps = ["Printf"] @@ -168,16 +183,27 @@ version = "0.3.1" [[deps.FileWatching]] uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +[[deps.GMP_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "781609d7-10c4-51f6-84f2-b8444358ff6d" +version = "6.2.1+6" + [[deps.Glob]] git-tree-sha1 = "97285bbd5230dd766e9ef6749b80fc617126d496" uuid = "c27321d9-0574-5035-807b-f59d2c89b15c" version = "1.3.1" +[[deps.GnuTLS_jll]] +deps = ["Artifacts", "GMP_jll", "JLLWrappers", "Libdl", "Nettle_jll", "P11Kit_jll", "Zlib_jll"] +git-tree-sha1 = "383db7d3f900f4c1f47a8a04115b053c095e48d3" +uuid = "0951126a-58fd-58f1-b5b3-b08c7c4a876d" +version = "3.8.4+0" + [[deps.Graphs]] deps = ["ArnoldiMethod", "Compat", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] -git-tree-sha1 = "3863330da5466410782f2bffc64f3d505a6a8334" +git-tree-sha1 = "ebd18c326fa6cee1efb7da9a3b45cf69da2ed4d9" uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" -version = "1.10.0" +version = "1.11.2" [[deps.HDF5_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "libaec_jll"] @@ -187,15 +213,15 @@ version = "1.14.3+3" [[deps.HostCPUFeatures]] deps = ["BitTwiddlingConvenienceFunctions", "IfElse", "Libdl", "Static"] -git-tree-sha1 = "eb8fed28f4994600e29beef49744639d985a04b2" +git-tree-sha1 = "8e070b599339d622e9a081d17230d74a5c473293" uuid = "3e5b6fbb-0976-4d2c-9146-d79de83f2fb0" -version = "0.1.16" +version = "0.1.17" [[deps.Hwloc_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "ca0f6bf568b4bfc807e7537f081c81e35ceca114" +git-tree-sha1 = "5e19e1e4fa3e71b774ce746274364aef0234634e" uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8" -version = "2.10.0+0" +version = "2.11.1+0" [[deps.IfElse]] git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1" @@ -203,9 +229,9 @@ uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173" version = "0.1.1" [[deps.Inflate]] -git-tree-sha1 = "ea8031dea4aff6bd41f1df8f2fdfb25b33626381" +git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d" uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" -version = "0.1.4" +version = "0.1.5" [[deps.InteractiveUtils]] deps = ["Markdown"] @@ -228,9 +254,9 @@ weakdeps = ["Serialization"] [[deps.LayoutPointers]] deps = ["ArrayInterface", "LinearAlgebra", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface"] -git-tree-sha1 = "62edfee3211981241b57ff1cedf4d74d79519277" +git-tree-sha1 = "a9eaadb366f5493a5654e843864c13d8b107548c" uuid = "10f19ff3-798f-405d-979b-55457f8fc047" -version = "0.1.15" +version = "0.1.17" [[deps.LazyArtifacts]] deps = ["Artifacts", "Pkg"] @@ -290,9 +316,9 @@ version = "1.0.3" [[deps.LoopVectorization]] deps = ["ArrayInterface", "CPUSummary", "CloseOpenIntervals", "DocStringExtensions", "HostCPUFeatures", "IfElse", "LayoutPointers", "LinearAlgebra", "OffsetArrays", "PolyesterWeave", "PrecompileTools", "SIMDTypes", "SLEEFPirates", "Static", "StaticArrayInterface", "ThreadingUtilities", "UnPack", "VectorizationBase"] -git-tree-sha1 = "a13f3be5d84b9c95465d743c82af0b094ef9c2e2" +git-tree-sha1 = "8084c25a250e00ae427a379a5b607e7aed96a2dd" uuid = "bdcacae8-1622-11e9-2a5c-532679323890" -version = "0.12.169" +version = "0.12.171" [deps.LoopVectorization.extensions] ForwardDiffExt = ["ChainRulesCore", "ForwardDiff"] @@ -303,23 +329,29 @@ version = "0.12.169" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" +[[deps.Lz4_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "7f26c8fc5229e68484e0b3447312c98e16207d11" +uuid = "5ced341a-0733-55b8-9ab6-a4889d929147" +version = "1.10.0+0" + [[deps.MPICH_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] -git-tree-sha1 = "656036b9ed6f942d35e536e249600bc31d0f9df8" +git-tree-sha1 = "19d4bd098928a3263693991500d05d74dbdc2004" uuid = "7cb0a576-ebde-5e09-9194-50597f1243b4" -version = "4.2.0+0" +version = "4.2.2+0" [[deps.MPIPreferences]] deps = ["Libdl", "Preferences"] -git-tree-sha1 = "8f6af051b9e8ec597fa09d8885ed79fd582f33c9" +git-tree-sha1 = "c105fe467859e7f6e9a852cb15cb4301126fac07" uuid = "3da0fdf6-3ccc-4f1b-acd9-58baa6c99267" -version = "0.1.10" +version = "0.1.11" [[deps.MPItrampoline_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] -git-tree-sha1 = "77c3bd69fdb024d75af38713e883d0f249ce19c2" +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] +git-tree-sha1 = "8c35d5420193841b2f367e658540e8d9e0601ed0" uuid = "f1f71cc9-e9ae-5b93-9b94-4fe0e1ad3748" -version = "5.3.2+0" +version = "5.4.0+0" [[deps.MacroTools]] deps = ["Markdown", "Random"] @@ -356,24 +388,30 @@ version = "2023.1.10" [[deps.NCDatasets]] deps = ["CFTime", "CommonDataModel", "DataStructures", "Dates", "DiskArrays", "NetCDF_jll", "NetworkOptions", "Printf"] -git-tree-sha1 = "173a378f357e9bb24b22019efb5e4778223ce8cf" +git-tree-sha1 = "a640912695952b074672edb5f9aaee2f7f9fd59a" uuid = "85f8d34a-cbdd-5861-8df4-14fed0d494ab" -version = "0.13.2" +version = "0.14.4" [[deps.NetCDF_jll]] -deps = ["Artifacts", "Bzip2_jll", "HDF5_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "XML2_jll", "Zlib_jll", "Zstd_jll"] -git-tree-sha1 = "10c612c81eaffdd6b7c28a45a554cdd9d2f40ff1" +deps = ["Artifacts", "Blosc_jll", "Bzip2_jll", "HDF5_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "TOML", "XML2_jll", "Zlib_jll", "Zstd_jll", "libzip_jll"] +git-tree-sha1 = "4686378c4ae1d1948cfbe46c002a11a4265dcb07" uuid = "7243133f-43d8-5620-bbf4-c2c921802cf3" -version = "400.902.208+0" +version = "400.902.211+1" + +[[deps.Nettle_jll]] +deps = ["Artifacts", "GMP_jll", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "eca63e3847dad608cfa6a3329b95ef674c7160b4" +uuid = "4c82536e-c426-54e4-b420-14f461c4ed8b" +version = "3.7.2+0" [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" version = "1.2.0" [[deps.OffsetArrays]] -git-tree-sha1 = "e64b4f5ea6b7389f6f046d13d4896a8f9c1ba71e" +git-tree-sha1 = "1a27764e945a152f7ca7efa04de513d473e9542e" uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" -version = "1.14.0" +version = "1.14.1" weakdeps = ["Adapt"] [deps.OffsetArrays.extensions] @@ -392,15 +430,21 @@ version = "4.1.6+0" [[deps.OpenSSL_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "3da7367955dcc5c54c1ba4d402ccdc09a1a3e046" +git-tree-sha1 = "a028ee3cb5641cccc4c24e90c36b0a4f7707bdf5" uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" -version = "3.0.13+1" +version = "3.0.14+0" [[deps.OrderedCollections]] git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" version = "1.6.3" +[[deps.P11Kit_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "2cd396108e178f3ae8dedbd8e938a18726ab2fbf" +uuid = "c2071276-7c44-58a7-b746-946036e04d0a" +version = "0.24.1+0" + [[deps.Parameters]] deps = ["OrderedCollections", "UnPack"] git-tree-sha1 = "34c0e9ad262e5f7fc75b10a9952ca7692cfc5fbe" @@ -413,16 +457,16 @@ uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" version = "1.10.0" [[deps.Polyester]] -deps = ["ArrayInterface", "BitTwiddlingConvenienceFunctions", "CPUSummary", "IfElse", "ManualMemory", "PolyesterWeave", "Requires", "Static", "StaticArrayInterface", "StrideArraysCore", "ThreadingUtilities"] -git-tree-sha1 = "09f59c6dda37c7f73efddc5bdf6f92bc940eb484" +deps = ["ArrayInterface", "BitTwiddlingConvenienceFunctions", "CPUSummary", "IfElse", "ManualMemory", "PolyesterWeave", "Static", "StaticArrayInterface", "StrideArraysCore", "ThreadingUtilities"] +git-tree-sha1 = "6d38fea02d983051776a856b7df75b30cf9a3c1f" uuid = "f517fe37-dbe3-4b94-8317-1923a5111588" -version = "0.7.12" +version = "0.7.16" [[deps.PolyesterWeave]] deps = ["BitTwiddlingConvenienceFunctions", "CPUSummary", "IfElse", "Static", "ThreadingUtilities"] -git-tree-sha1 = "240d7170f5ffdb285f9427b92333c3463bf65bf6" +git-tree-sha1 = "645bed98cd47f72f67316fd42fc47dee771aefcd" uuid = "1d0040c9-8b98-4ee7-8388-3f51789ca0ad" -version = "0.2.1" +version = "0.2.2" [[deps.PrecompileTools]] deps = ["Preferences"] @@ -471,9 +515,9 @@ version = "0.1.0" [[deps.SLEEFPirates]] deps = ["IfElse", "Static", "VectorizationBase"] -git-tree-sha1 = "3aac6d68c5e57449f5b9b865c9ba50ac2970c4cf" +git-tree-sha1 = "456f610ca2fbd1c14f5fcf31c6bfadc55e7d66e0" uuid = "476501e8-09a2-5ece-8869-fb82de89a1fa" -version = "0.6.42" +version = "0.6.43" [[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" @@ -497,16 +541,16 @@ uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" version = "1.10.0" [[deps.Static]] -deps = ["IfElse"] -git-tree-sha1 = "d2fdac9ff3906e27f7a618d47b676941baa6c80c" +deps = ["CommonWorldInvalidations", "IfElse", "PrecompileTools"] +git-tree-sha1 = "87d51a3ee9a4b0d2fe054bdd3fc2436258db2603" uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3" -version = "0.8.10" +version = "1.1.1" [[deps.StaticArrayInterface]] -deps = ["ArrayInterface", "Compat", "IfElse", "LinearAlgebra", "PrecompileTools", "Requires", "SparseArrays", "Static", "SuiteSparse"] -git-tree-sha1 = "5d66818a39bb04bf328e92bc933ec5b4ee88e436" +deps = ["ArrayInterface", "Compat", "IfElse", "LinearAlgebra", "PrecompileTools", "Static"] +git-tree-sha1 = "96381d50f1ce85f2663584c8e886a6ca97e60554" uuid = "0d7ed370-da01-4f52-bd93-41d350b8b718" -version = "1.5.0" +version = "1.8.0" weakdeps = ["OffsetArrays", "StaticArrays"] [deps.StaticArrayInterface.extensions] @@ -515,9 +559,9 @@ weakdeps = ["OffsetArrays", "StaticArrays"] [[deps.StaticArrays]] deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] -git-tree-sha1 = "bf074c045d3d5ffd956fa0a461da38a44685d6b2" +git-tree-sha1 = "eeafab08ae20c62c44c8399ccb9354a04b80db50" uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.9.3" +version = "1.9.7" [deps.StaticArrays.extensions] StaticArraysChainRulesCoreExt = "ChainRulesCore" @@ -528,9 +572,9 @@ version = "1.9.3" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [[deps.StaticArraysCore]] -git-tree-sha1 = "36b3d696ce6366023a0ea192b4cd442268995a0d" +git-tree-sha1 = "192954ef1208c7019899fbf8049e717f92959682" uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" -version = "1.4.2" +version = "1.4.3" [[deps.Statistics]] deps = ["LinearAlgebra", "SparseArrays"] @@ -539,13 +583,9 @@ version = "1.10.0" [[deps.StrideArraysCore]] deps = ["ArrayInterface", "CloseOpenIntervals", "IfElse", "LayoutPointers", "LinearAlgebra", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface", "ThreadingUtilities"] -git-tree-sha1 = "b518da45c50dfab8384125ba829f1739bda41034" +git-tree-sha1 = "f35f6ab602df8413a50c4a25ca14de821e8605fb" uuid = "7792a7ef-975c-4747-a70f-980b88e8d1da" -version = "0.5.5" - -[[deps.SuiteSparse]] -deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"] -uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9" +version = "0.5.7" [[deps.SuiteSparse_jll]] deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] @@ -588,15 +628,21 @@ uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" [[deps.VectorizationBase]] deps = ["ArrayInterface", "CPUSummary", "HostCPUFeatures", "IfElse", "LayoutPointers", "Libdl", "LinearAlgebra", "SIMDTypes", "Static", "StaticArrayInterface"] -git-tree-sha1 = "ac377f0a248753a1b1d58bbc92a64f5a726dfb71" +git-tree-sha1 = "e7f5b81c65eb858bed630fe006837b935518aca5" uuid = "3d5dd08c-fd9d-11e8-17fa-ed2836048c2f" -version = "0.21.66" +version = "0.21.70" [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] -git-tree-sha1 = "532e22cf7be8462035d092ff21fada7527e2c488" +git-tree-sha1 = "d9717ce3518dc68a99e6b96300813760d887a01d" uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" -version = "2.12.6+0" +version = "2.13.1+0" + +[[deps.XZ_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "ac88fb95ae6447c8dda6a5503f3bafd496ae8632" +uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800" +version = "5.4.6+0" [[deps.Zlib_jll]] deps = ["Libdl"] @@ -620,6 +666,12 @@ deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" version = "5.8.0+1" +[[deps.libzip_jll]] +deps = ["Artifacts", "Bzip2_jll", "GnuTLS_jll", "JLLWrappers", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] +git-tree-sha1 = "3282b7d16ae7ac3e57ec2f3fa8fafb564d8f9f7f" +uuid = "337d8026-41b4-5cde-a456-74a10e5b31d1" +version = "1.10.1+0" + [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" diff --git a/Project.toml b/Project.toml index 54d2ea8b6..99e762213 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Wflow" uuid = "d48b7d99-76e7-47ae-b1d5-ff0c1cf9a818" authors = ["Deltares and contributors"] -version = "0.7.3" +version = "0.8.0" [deps] BasicModelInterface = "59605e27-edc0-445a-b93d-c09a3a50b330" diff --git a/build/create_binaries/Manifest.toml b/build/create_binaries/Manifest.toml index 0d2640764..9826d50d1 100644 --- a/build/create_binaries/Manifest.toml +++ b/build/create_binaries/Manifest.toml @@ -11,9 +11,9 @@ version = "0.4.5" [[deps.Adapt]] deps = ["LinearAlgebra", "Requires"] -git-tree-sha1 = "0fb305e0253fd4e833d486914367a2ee2c2e78d0" +git-tree-sha1 = "6a55b747d1812e699320963ffde36f1ebdda4099" uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" -version = "4.0.1" +version = "4.0.4" weakdeps = ["StaticArrays"] [deps.Adapt.extensions] @@ -25,22 +25,25 @@ version = "1.1.1" [[deps.ArnoldiMethod]] deps = ["LinearAlgebra", "Random", "StaticArrays"] -git-tree-sha1 = "62e51b39331de8911e4a7ff6f5aaf38a5f4cc0ae" +git-tree-sha1 = "d57bd3762d308bded22c3b82d033bff85f6195c6" uuid = "ec485272-7323-5ecc-a04f-4719b315124d" -version = "0.2.0" +version = "0.4.0" [[deps.ArrayInterface]] -deps = ["Adapt", "LinearAlgebra", "Requires", "SparseArrays", "SuiteSparse"] -git-tree-sha1 = "881e43f1aa014a6f75c8fc0847860e00a1500846" +deps = ["Adapt", "LinearAlgebra"] +git-tree-sha1 = "f54c23a5d304fb87110de62bace7777d59088c34" uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" -version = "7.8.0" +version = "7.15.0" [deps.ArrayInterface.extensions] ArrayInterfaceBandedMatricesExt = "BandedMatrices" ArrayInterfaceBlockBandedMatricesExt = "BlockBandedMatrices" ArrayInterfaceCUDAExt = "CUDA" + ArrayInterfaceCUDSSExt = "CUDSS" + ArrayInterfaceChainRulesExt = "ChainRules" ArrayInterfaceGPUArraysCoreExt = "GPUArraysCore" ArrayInterfaceReverseDiffExt = "ReverseDiff" + ArrayInterfaceSparseArraysExt = "SparseArrays" ArrayInterfaceStaticArraysCoreExt = "StaticArraysCore" ArrayInterfaceTrackerExt = "Tracker" @@ -48,8 +51,11 @@ version = "7.8.0" BandedMatrices = "aae01518-5342-5314-be14-df237901396f" BlockBandedMatrices = "ffab5731-97b5-5995-9138-79e8c1846df0" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" + CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" + ChainRules = "082447d4-558c-5d27-93f4-14fc19e9eca2" GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527" ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" + SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" @@ -66,9 +72,15 @@ version = "0.1.0" [[deps.BitTwiddlingConvenienceFunctions]] deps = ["Static"] -git-tree-sha1 = "0c5f81f47bbbcf4aea7b2959135713459170798b" +git-tree-sha1 = "f21cfd4950cb9f0587d5067e69405ad2acd27b87" uuid = "62783981-4cbd-42fc-bca8-16325de8dc4b" -version = "0.1.5" +version = "0.1.6" + +[[deps.Blosc_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Lz4_jll", "Zlib_jll", "Zstd_jll"] +git-tree-sha1 = "19b98ee7e3db3b4eff74c5c9c72bf32144e24f10" +uuid = "0b7ba130-8d10-5ba8-a3d6-c5182647fed9" +version = "1.21.5+0" [[deps.Bzip2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] @@ -84,27 +96,32 @@ version = "0.1.3" [[deps.CPUSummary]] deps = ["CpuId", "IfElse", "PrecompileTools", "Static"] -git-tree-sha1 = "601f7e7b3d36f18790e2caf83a882d88e9b71ff1" +git-tree-sha1 = "5a97e67919535d6841172016c9530fd69494e5ec" uuid = "2a0fbf3d-bb9c-48f3-b0a9-814d99fd7ab9" -version = "0.2.4" +version = "0.2.6" [[deps.CloseOpenIntervals]] deps = ["Static", "StaticArrayInterface"] -git-tree-sha1 = "70232f82ffaab9dc52585e0dd043b5e0c6b714f1" +git-tree-sha1 = "05ba0d07cd4fd8b7a39541e31a7b0254704ea581" uuid = "fb6a15b2-703c-40df-9091-08a04967cfa9" -version = "0.1.12" +version = "0.1.13" [[deps.CommonDataModel]] -deps = ["CFTime", "DataStructures", "Dates", "Preferences", "Printf"] -git-tree-sha1 = "7f5717cbb2c1ce650cfd454451f282df33103596" +deps = ["CFTime", "DataStructures", "Dates", "Preferences", "Printf", "Statistics"] +git-tree-sha1 = "d6fb5bf939a2753c74984b11434ea25d6c397a58" uuid = "1fbeeb36-5f17-413c-809b-666fb144f157" -version = "0.2.5" +version = "0.3.6" + +[[deps.CommonWorldInvalidations]] +git-tree-sha1 = "ae52d1c52048455e85a387fbee9be553ec2b68d0" +uuid = "f70d9fcc-98c5-4d4a-abd7-e4cdeebd8ca8" +version = "1.0.0" [[deps.Compat]] deps = ["TOML", "UUIDs"] -git-tree-sha1 = "c955881e3c981181362ae4088b35995446298b80" +git-tree-sha1 = "8ae8d32e09f0dcf42a36b90d4e17f5dd2e4c4215" uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "4.14.0" +version = "4.16.0" weakdeps = ["Dates", "LinearAlgebra"] [deps.Compat.extensions] @@ -123,9 +140,9 @@ version = "0.3.1" [[deps.DataStructures]] deps = ["Compat", "InteractiveUtils", "OrderedCollections"] -git-tree-sha1 = "1fb174f0d48fe7d142e1109a10636bc1d14f5ac2" +git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82" uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.18.17" +version = "0.18.20" [[deps.Dates]] deps = ["Printf"] @@ -166,34 +183,45 @@ version = "0.3.1" [[deps.FileWatching]] uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +[[deps.GMP_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "781609d7-10c4-51f6-84f2-b8444358ff6d" +version = "6.2.1+6" + [[deps.Glob]] git-tree-sha1 = "97285bbd5230dd766e9ef6749b80fc617126d496" uuid = "c27321d9-0574-5035-807b-f59d2c89b15c" version = "1.3.1" +[[deps.GnuTLS_jll]] +deps = ["Artifacts", "GMP_jll", "JLLWrappers", "Libdl", "Nettle_jll", "P11Kit_jll", "Zlib_jll"] +git-tree-sha1 = "383db7d3f900f4c1f47a8a04115b053c095e48d3" +uuid = "0951126a-58fd-58f1-b5b3-b08c7c4a876d" +version = "3.8.4+0" + [[deps.Graphs]] deps = ["ArnoldiMethod", "Compat", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] -git-tree-sha1 = "899050ace26649433ef1af25bc17a815b3db52b7" +git-tree-sha1 = "ebd18c326fa6cee1efb7da9a3b45cf69da2ed4d9" uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" -version = "1.9.0" +version = "1.11.2" [[deps.HDF5_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "libaec_jll"] -git-tree-sha1 = "38c8874692d48d5440d5752d6c74b0c6b0b60739" +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "libaec_jll"] +git-tree-sha1 = "82a471768b513dc39e471540fdadc84ff80ff997" uuid = "0234f1f7-429e-5d53-9886-15a909be8d59" -version = "1.14.2+1" +version = "1.14.3+3" [[deps.HostCPUFeatures]] deps = ["BitTwiddlingConvenienceFunctions", "IfElse", "Libdl", "Static"] -git-tree-sha1 = "eb8fed28f4994600e29beef49744639d985a04b2" +git-tree-sha1 = "8e070b599339d622e9a081d17230d74a5c473293" uuid = "3e5b6fbb-0976-4d2c-9146-d79de83f2fb0" -version = "0.1.16" +version = "0.1.17" [[deps.Hwloc_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "ca0f6bf568b4bfc807e7537f081c81e35ceca114" +git-tree-sha1 = "5e19e1e4fa3e71b774ce746274364aef0234634e" uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8" -version = "2.10.0+0" +version = "2.11.1+0" [[deps.IfElse]] git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1" @@ -201,9 +229,9 @@ uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173" version = "0.1.1" [[deps.Inflate]] -git-tree-sha1 = "ea8031dea4aff6bd41f1df8f2fdfb25b33626381" +git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d" uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" -version = "0.1.4" +version = "0.1.5" [[deps.InteractiveUtils]] deps = ["Markdown"] @@ -215,12 +243,6 @@ git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca" uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" version = "1.5.0" -[[deps.LLVMOpenMP_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "d986ce2d884d49126836ea94ed5bfb0f12679713" -uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" -version = "15.0.7+0" - [[deps.LRUCache]] git-tree-sha1 = "b3cc6698599b10e652832c2f23db3cab99d51b59" uuid = "8ac3fa9e-de4c-5943-b1dc-09c6b5f20637" @@ -232,9 +254,9 @@ weakdeps = ["Serialization"] [[deps.LayoutPointers]] deps = ["ArrayInterface", "LinearAlgebra", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface"] -git-tree-sha1 = "62edfee3211981241b57ff1cedf4d74d79519277" +git-tree-sha1 = "a9eaadb366f5493a5654e843864c13d8b107548c" uuid = "10f19ff3-798f-405d-979b-55457f8fc047" -version = "0.1.15" +version = "0.1.17" [[deps.LazyArtifacts]] deps = ["Artifacts", "Pkg"] @@ -294,9 +316,9 @@ version = "1.0.3" [[deps.LoopVectorization]] deps = ["ArrayInterface", "CPUSummary", "CloseOpenIntervals", "DocStringExtensions", "HostCPUFeatures", "IfElse", "LayoutPointers", "LinearAlgebra", "OffsetArrays", "PolyesterWeave", "PrecompileTools", "SIMDTypes", "SLEEFPirates", "Static", "StaticArrayInterface", "ThreadingUtilities", "UnPack", "VectorizationBase"] -git-tree-sha1 = "0f5648fbae0d015e3abe5867bca2b362f67a5894" +git-tree-sha1 = "8084c25a250e00ae427a379a5b607e7aed96a2dd" uuid = "bdcacae8-1622-11e9-2a5c-532679323890" -version = "0.12.166" +version = "0.12.171" [deps.LoopVectorization.extensions] ForwardDiffExt = ["ChainRulesCore", "ForwardDiff"] @@ -307,23 +329,29 @@ version = "0.12.166" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" +[[deps.Lz4_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "7f26c8fc5229e68484e0b3447312c98e16207d11" +uuid = "5ced341a-0733-55b8-9ab6-a4889d929147" +version = "1.10.0+0" + [[deps.MPICH_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] -git-tree-sha1 = "656036b9ed6f942d35e536e249600bc31d0f9df8" +git-tree-sha1 = "19d4bd098928a3263693991500d05d74dbdc2004" uuid = "7cb0a576-ebde-5e09-9194-50597f1243b4" -version = "4.2.0+0" +version = "4.2.2+0" [[deps.MPIPreferences]] deps = ["Libdl", "Preferences"] -git-tree-sha1 = "8f6af051b9e8ec597fa09d8885ed79fd582f33c9" +git-tree-sha1 = "c105fe467859e7f6e9a852cb15cb4301126fac07" uuid = "3da0fdf6-3ccc-4f1b-acd9-58baa6c99267" -version = "0.1.10" +version = "0.1.11" [[deps.MPItrampoline_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] -git-tree-sha1 = "77c3bd69fdb024d75af38713e883d0f249ce19c2" +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] +git-tree-sha1 = "8c35d5420193841b2f367e658540e8d9e0601ed0" uuid = "f1f71cc9-e9ae-5b93-9b94-4fe0e1ad3748" -version = "5.3.2+0" +version = "5.4.0+0" [[deps.MacroTools]] deps = ["Markdown", "Random"] @@ -360,24 +388,30 @@ version = "2023.1.10" [[deps.NCDatasets]] deps = ["CFTime", "CommonDataModel", "DataStructures", "Dates", "DiskArrays", "NetCDF_jll", "NetworkOptions", "Printf"] -git-tree-sha1 = "173a378f357e9bb24b22019efb5e4778223ce8cf" +git-tree-sha1 = "a640912695952b074672edb5f9aaee2f7f9fd59a" uuid = "85f8d34a-cbdd-5861-8df4-14fed0d494ab" -version = "0.13.2" +version = "0.14.4" [[deps.NetCDF_jll]] -deps = ["Artifacts", "Bzip2_jll", "HDF5_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "XML2_jll", "Zlib_jll", "Zstd_jll"] -git-tree-sha1 = "10c612c81eaffdd6b7c28a45a554cdd9d2f40ff1" +deps = ["Artifacts", "Blosc_jll", "Bzip2_jll", "HDF5_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "TOML", "XML2_jll", "Zlib_jll", "Zstd_jll", "libzip_jll"] +git-tree-sha1 = "4686378c4ae1d1948cfbe46c002a11a4265dcb07" uuid = "7243133f-43d8-5620-bbf4-c2c921802cf3" -version = "400.902.208+0" +version = "400.902.211+1" + +[[deps.Nettle_jll]] +deps = ["Artifacts", "GMP_jll", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "eca63e3847dad608cfa6a3329b95ef674c7160b4" +uuid = "4c82536e-c426-54e4-b420-14f461c4ed8b" +version = "3.7.2+0" [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" version = "1.2.0" [[deps.OffsetArrays]] -git-tree-sha1 = "6a731f2b5c03157418a20c12195eb4b74c8f8621" +git-tree-sha1 = "1a27764e945a152f7ca7efa04de513d473e9542e" uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" -version = "1.13.0" +version = "1.14.1" weakdeps = ["Adapt"] [deps.OffsetArrays.extensions] @@ -389,27 +423,27 @@ uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" version = "0.3.23+2" [[deps.OpenMPI_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "PMIx_jll", "TOML", "Zlib_jll", "libevent_jll", "prrte_jll"] -git-tree-sha1 = "f46caf663e069027a06942d00dced37f1eb3d8ad" +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] +git-tree-sha1 = "e25c1778a98e34219a00455d6e4384e017ea9762" uuid = "fe0851c0-eecd-5654-98d4-656369965a5c" -version = "5.0.2+0" +version = "4.1.6+0" [[deps.OpenSSL_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "60e3045590bd104a16fefb12836c00c0ef8c7f8c" +git-tree-sha1 = "a028ee3cb5641cccc4c24e90c36b0a4f7707bdf5" uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" -version = "3.0.13+0" +version = "3.0.14+0" [[deps.OrderedCollections]] git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" version = "1.6.3" -[[deps.PMIx_jll]] -deps = ["Artifacts", "Hwloc_jll", "JLLWrappers", "Libdl", "Zlib_jll", "libevent_jll"] -git-tree-sha1 = "8b3b19351fa24791f94d7ae85faf845ca1362541" -uuid = "32165bc3-0280-59bc-8c0b-c33b6203efab" -version = "4.2.7+0" +[[deps.P11Kit_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "2cd396108e178f3ae8dedbd8e938a18726ab2fbf" +uuid = "c2071276-7c44-58a7-b746-946036e04d0a" +version = "0.24.1+0" [[deps.PackageCompiler]] deps = ["Artifacts", "Glob", "LazyArtifacts", "Libdl", "Pkg", "Printf", "RelocatableFolders", "TOML", "UUIDs", "p7zip_jll"] @@ -429,22 +463,22 @@ uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" version = "1.10.0" [[deps.Polyester]] -deps = ["ArrayInterface", "BitTwiddlingConvenienceFunctions", "CPUSummary", "IfElse", "ManualMemory", "PolyesterWeave", "Requires", "Static", "StaticArrayInterface", "StrideArraysCore", "ThreadingUtilities"] -git-tree-sha1 = "fca25670784a1ae44546bcb17288218310af2778" +deps = ["ArrayInterface", "BitTwiddlingConvenienceFunctions", "CPUSummary", "IfElse", "ManualMemory", "PolyesterWeave", "Static", "StaticArrayInterface", "StrideArraysCore", "ThreadingUtilities"] +git-tree-sha1 = "6d38fea02d983051776a856b7df75b30cf9a3c1f" uuid = "f517fe37-dbe3-4b94-8317-1923a5111588" -version = "0.7.9" +version = "0.7.16" [[deps.PolyesterWeave]] deps = ["BitTwiddlingConvenienceFunctions", "CPUSummary", "IfElse", "Static", "ThreadingUtilities"] -git-tree-sha1 = "240d7170f5ffdb285f9427b92333c3463bf65bf6" +git-tree-sha1 = "645bed98cd47f72f67316fd42fc47dee771aefcd" uuid = "1d0040c9-8b98-4ee7-8388-3f51789ca0ad" -version = "0.2.1" +version = "0.2.2" [[deps.PrecompileTools]] deps = ["Preferences"] -git-tree-sha1 = "03b4c25b43cb84cee5c90aa9b5ea0a78fd848d2f" +git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" -version = "1.2.0" +version = "1.2.1" [[deps.Preferences]] deps = ["TOML"] @@ -493,9 +527,9 @@ version = "0.1.0" [[deps.SLEEFPirates]] deps = ["IfElse", "Static", "VectorizationBase"] -git-tree-sha1 = "3aac6d68c5e57449f5b9b865c9ba50ac2970c4cf" +git-tree-sha1 = "456f610ca2fbd1c14f5fcf31c6bfadc55e7d66e0" uuid = "476501e8-09a2-5ece-8869-fb82de89a1fa" -version = "0.6.42" +version = "0.6.43" [[deps.Scratch]] deps = ["Dates"] @@ -525,16 +559,16 @@ uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" version = "1.10.0" [[deps.Static]] -deps = ["IfElse"] -git-tree-sha1 = "d2fdac9ff3906e27f7a618d47b676941baa6c80c" +deps = ["CommonWorldInvalidations", "IfElse", "PrecompileTools"] +git-tree-sha1 = "87d51a3ee9a4b0d2fe054bdd3fc2436258db2603" uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3" -version = "0.8.10" +version = "1.1.1" [[deps.StaticArrayInterface]] -deps = ["ArrayInterface", "Compat", "IfElse", "LinearAlgebra", "PrecompileTools", "Requires", "SparseArrays", "Static", "SuiteSparse"] -git-tree-sha1 = "5d66818a39bb04bf328e92bc933ec5b4ee88e436" +deps = ["ArrayInterface", "Compat", "IfElse", "LinearAlgebra", "PrecompileTools", "Static"] +git-tree-sha1 = "96381d50f1ce85f2663584c8e886a6ca97e60554" uuid = "0d7ed370-da01-4f52-bd93-41d350b8b718" -version = "1.5.0" +version = "1.8.0" weakdeps = ["OffsetArrays", "StaticArrays"] [deps.StaticArrayInterface.extensions] @@ -543,9 +577,9 @@ weakdeps = ["OffsetArrays", "StaticArrays"] [[deps.StaticArrays]] deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] -git-tree-sha1 = "bf074c045d3d5ffd956fa0a461da38a44685d6b2" +git-tree-sha1 = "eeafab08ae20c62c44c8399ccb9354a04b80db50" uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.9.3" +version = "1.9.7" [deps.StaticArrays.extensions] StaticArraysChainRulesCoreExt = "ChainRulesCore" @@ -556,9 +590,9 @@ version = "1.9.3" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [[deps.StaticArraysCore]] -git-tree-sha1 = "36b3d696ce6366023a0ea192b4cd442268995a0d" +git-tree-sha1 = "192954ef1208c7019899fbf8049e717f92959682" uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" -version = "1.4.2" +version = "1.4.3" [[deps.Statistics]] deps = ["LinearAlgebra", "SparseArrays"] @@ -566,14 +600,10 @@ uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" version = "1.10.0" [[deps.StrideArraysCore]] -deps = ["ArrayInterface", "CloseOpenIntervals", "IfElse", "LayoutPointers", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface", "ThreadingUtilities"] -git-tree-sha1 = "d6415f66f3d89c615929af907fdc6a3e17af0d8c" +deps = ["ArrayInterface", "CloseOpenIntervals", "IfElse", "LayoutPointers", "LinearAlgebra", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface", "ThreadingUtilities"] +git-tree-sha1 = "f35f6ab602df8413a50c4a25ca14de821e8605fb" uuid = "7792a7ef-975c-4747-a70f-980b88e8d1da" -version = "0.5.2" - -[[deps.SuiteSparse]] -deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"] -uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9" +version = "0.5.7" [[deps.SuiteSparse_jll]] deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] @@ -616,13 +646,13 @@ uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" [[deps.VectorizationBase]] deps = ["ArrayInterface", "CPUSummary", "HostCPUFeatures", "IfElse", "LayoutPointers", "Libdl", "LinearAlgebra", "SIMDTypes", "Static", "StaticArrayInterface"] -git-tree-sha1 = "7209df901e6ed7489fe9b7aa3e46fb788e15db85" +git-tree-sha1 = "e7f5b81c65eb858bed630fe006837b935518aca5" uuid = "3d5dd08c-fd9d-11e8-17fa-ed2836048c2f" -version = "0.21.65" +version = "0.21.70" [[deps.Wflow]] deps = ["BasicModelInterface", "CFTime", "Dates", "DelimitedFiles", "FieldMetadata", "Glob", "Graphs", "IfElse", "LoggingExtras", "LoopVectorization", "NCDatasets", "Parameters", "Polyester", "ProgressLogging", "Random", "StaticArrays", "Statistics", "TOML", "TerminalLoggers", "UnPack"] -git-tree-sha1 = "29fdf975f5a32b15890182f114b72ea6ba7b21cc" +git-tree-sha1 = "414d32f07561872f03cb4f347df719962dfddc5e" repo-rev = "master" repo-url = "../.." uuid = "d48b7d99-76e7-47ae-b1d5-ff0c1cf9a818" @@ -630,9 +660,15 @@ version = "0.7.3" [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] -git-tree-sha1 = "07e470dabc5a6a4254ffebc29a1b3fc01464e105" +git-tree-sha1 = "d9717ce3518dc68a99e6b96300813760d887a01d" uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" -version = "2.12.5+0" +version = "2.13.1+0" + +[[deps.XZ_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "ac88fb95ae6447c8dda6a5503f3bafd496ae8632" +uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800" +version = "5.4.6+0" [[deps.Zlib_jll]] deps = ["Libdl"] @@ -641,9 +677,9 @@ version = "1.2.13+1" [[deps.Zstd_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "49ce682769cd5de6c72dcf1b94ed7790cd08974c" +git-tree-sha1 = "e678132f07ddb5bfa46857f0d7620fb9be675d3b" uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" -version = "1.5.5+0" +version = "1.5.6+0" [[deps.libaec_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] @@ -656,11 +692,11 @@ deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" version = "5.8.0+1" -[[deps.libevent_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "OpenSSL_jll"] -git-tree-sha1 = "f04ec6d9a186115fb38f858f05c0c4e1b7fc9dcb" -uuid = "1080aeaf-3a6a-583e-a51c-c537b09f60ec" -version = "2.1.13+1" +[[deps.libzip_jll]] +deps = ["Artifacts", "Bzip2_jll", "GnuTLS_jll", "JLLWrappers", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] +git-tree-sha1 = "3282b7d16ae7ac3e57ec2f3fa8fafb564d8f9f7f" +uuid = "337d8026-41b4-5cde-a456-74a10e5b31d1" +version = "1.10.1+0" [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] @@ -671,9 +707,3 @@ version = "1.52.0+1" deps = ["Artifacts", "Libdl"] uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" version = "17.4.0+2" - -[[deps.prrte_jll]] -deps = ["Artifacts", "Hwloc_jll", "JLLWrappers", "Libdl", "PMIx_jll", "libevent_jll"] -git-tree-sha1 = "5adb2d7a18a30280feb66cad6f1a1dfdca2dc7b0" -uuid = "eb928a42-fffd-568d-ab9c-3f5d54fc65b9" -version = "3.0.2+0" diff --git a/build/wflow_cli/Manifest.toml b/build/wflow_cli/Manifest.toml index 55b9ab3f1..1073bedc1 100644 --- a/build/wflow_cli/Manifest.toml +++ b/build/wflow_cli/Manifest.toml @@ -11,9 +11,9 @@ version = "0.4.5" [[deps.Adapt]] deps = ["LinearAlgebra", "Requires"] -git-tree-sha1 = "0fb305e0253fd4e833d486914367a2ee2c2e78d0" +git-tree-sha1 = "6a55b747d1812e699320963ffde36f1ebdda4099" uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" -version = "4.0.1" +version = "4.0.4" weakdeps = ["StaticArrays"] [deps.Adapt.extensions] @@ -25,22 +25,25 @@ version = "1.1.1" [[deps.ArnoldiMethod]] deps = ["LinearAlgebra", "Random", "StaticArrays"] -git-tree-sha1 = "62e51b39331de8911e4a7ff6f5aaf38a5f4cc0ae" +git-tree-sha1 = "d57bd3762d308bded22c3b82d033bff85f6195c6" uuid = "ec485272-7323-5ecc-a04f-4719b315124d" -version = "0.2.0" +version = "0.4.0" [[deps.ArrayInterface]] -deps = ["Adapt", "LinearAlgebra", "Requires", "SparseArrays", "SuiteSparse"] -git-tree-sha1 = "881e43f1aa014a6f75c8fc0847860e00a1500846" +deps = ["Adapt", "LinearAlgebra"] +git-tree-sha1 = "f54c23a5d304fb87110de62bace7777d59088c34" uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" -version = "7.8.0" +version = "7.15.0" [deps.ArrayInterface.extensions] ArrayInterfaceBandedMatricesExt = "BandedMatrices" ArrayInterfaceBlockBandedMatricesExt = "BlockBandedMatrices" ArrayInterfaceCUDAExt = "CUDA" + ArrayInterfaceCUDSSExt = "CUDSS" + ArrayInterfaceChainRulesExt = "ChainRules" ArrayInterfaceGPUArraysCoreExt = "GPUArraysCore" ArrayInterfaceReverseDiffExt = "ReverseDiff" + ArrayInterfaceSparseArraysExt = "SparseArrays" ArrayInterfaceStaticArraysCoreExt = "StaticArraysCore" ArrayInterfaceTrackerExt = "Tracker" @@ -48,8 +51,11 @@ version = "7.8.0" BandedMatrices = "aae01518-5342-5314-be14-df237901396f" BlockBandedMatrices = "ffab5731-97b5-5995-9138-79e8c1846df0" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" + CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" + ChainRules = "082447d4-558c-5d27-93f4-14fc19e9eca2" GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527" ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" + SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" @@ -66,9 +72,15 @@ version = "0.1.0" [[deps.BitTwiddlingConvenienceFunctions]] deps = ["Static"] -git-tree-sha1 = "0c5f81f47bbbcf4aea7b2959135713459170798b" +git-tree-sha1 = "f21cfd4950cb9f0587d5067e69405ad2acd27b87" uuid = "62783981-4cbd-42fc-bca8-16325de8dc4b" -version = "0.1.5" +version = "0.1.6" + +[[deps.Blosc_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Lz4_jll", "Zlib_jll", "Zstd_jll"] +git-tree-sha1 = "19b98ee7e3db3b4eff74c5c9c72bf32144e24f10" +uuid = "0b7ba130-8d10-5ba8-a3d6-c5182647fed9" +version = "1.21.5+0" [[deps.Bzip2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] @@ -84,27 +96,32 @@ version = "0.1.3" [[deps.CPUSummary]] deps = ["CpuId", "IfElse", "PrecompileTools", "Static"] -git-tree-sha1 = "601f7e7b3d36f18790e2caf83a882d88e9b71ff1" +git-tree-sha1 = "5a97e67919535d6841172016c9530fd69494e5ec" uuid = "2a0fbf3d-bb9c-48f3-b0a9-814d99fd7ab9" -version = "0.2.4" +version = "0.2.6" [[deps.CloseOpenIntervals]] deps = ["Static", "StaticArrayInterface"] -git-tree-sha1 = "70232f82ffaab9dc52585e0dd043b5e0c6b714f1" +git-tree-sha1 = "05ba0d07cd4fd8b7a39541e31a7b0254704ea581" uuid = "fb6a15b2-703c-40df-9091-08a04967cfa9" -version = "0.1.12" +version = "0.1.13" [[deps.CommonDataModel]] -deps = ["CFTime", "DataStructures", "Dates", "Preferences", "Printf"] -git-tree-sha1 = "7f5717cbb2c1ce650cfd454451f282df33103596" +deps = ["CFTime", "DataStructures", "Dates", "Preferences", "Printf", "Statistics"] +git-tree-sha1 = "d6fb5bf939a2753c74984b11434ea25d6c397a58" uuid = "1fbeeb36-5f17-413c-809b-666fb144f157" -version = "0.2.5" +version = "0.3.6" + +[[deps.CommonWorldInvalidations]] +git-tree-sha1 = "ae52d1c52048455e85a387fbee9be553ec2b68d0" +uuid = "f70d9fcc-98c5-4d4a-abd7-e4cdeebd8ca8" +version = "1.0.0" [[deps.Compat]] deps = ["TOML", "UUIDs"] -git-tree-sha1 = "c955881e3c981181362ae4088b35995446298b80" +git-tree-sha1 = "8ae8d32e09f0dcf42a36b90d4e17f5dd2e4c4215" uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "4.14.0" +version = "4.16.0" weakdeps = ["Dates", "LinearAlgebra"] [deps.Compat.extensions] @@ -123,9 +140,9 @@ version = "0.3.1" [[deps.DataStructures]] deps = ["Compat", "InteractiveUtils", "OrderedCollections"] -git-tree-sha1 = "1fb174f0d48fe7d142e1109a10636bc1d14f5ac2" +git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82" uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.18.17" +version = "0.18.20" [[deps.Dates]] deps = ["Printf"] @@ -166,34 +183,45 @@ version = "0.3.1" [[deps.FileWatching]] uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +[[deps.GMP_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "781609d7-10c4-51f6-84f2-b8444358ff6d" +version = "6.2.1+6" + [[deps.Glob]] git-tree-sha1 = "97285bbd5230dd766e9ef6749b80fc617126d496" uuid = "c27321d9-0574-5035-807b-f59d2c89b15c" version = "1.3.1" +[[deps.GnuTLS_jll]] +deps = ["Artifacts", "GMP_jll", "JLLWrappers", "Libdl", "Nettle_jll", "P11Kit_jll", "Zlib_jll"] +git-tree-sha1 = "383db7d3f900f4c1f47a8a04115b053c095e48d3" +uuid = "0951126a-58fd-58f1-b5b3-b08c7c4a876d" +version = "3.8.4+0" + [[deps.Graphs]] deps = ["ArnoldiMethod", "Compat", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] -git-tree-sha1 = "899050ace26649433ef1af25bc17a815b3db52b7" +git-tree-sha1 = "ebd18c326fa6cee1efb7da9a3b45cf69da2ed4d9" uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" -version = "1.9.0" +version = "1.11.2" [[deps.HDF5_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "libaec_jll"] -git-tree-sha1 = "e4591176488495bf44d7456bd73179d87d5e6eab" +git-tree-sha1 = "82a471768b513dc39e471540fdadc84ff80ff997" uuid = "0234f1f7-429e-5d53-9886-15a909be8d59" -version = "1.14.3+1" +version = "1.14.3+3" [[deps.HostCPUFeatures]] deps = ["BitTwiddlingConvenienceFunctions", "IfElse", "Libdl", "Static"] -git-tree-sha1 = "eb8fed28f4994600e29beef49744639d985a04b2" +git-tree-sha1 = "8e070b599339d622e9a081d17230d74a5c473293" uuid = "3e5b6fbb-0976-4d2c-9146-d79de83f2fb0" -version = "0.1.16" +version = "0.1.17" [[deps.Hwloc_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "ca0f6bf568b4bfc807e7537f081c81e35ceca114" +git-tree-sha1 = "5e19e1e4fa3e71b774ce746274364aef0234634e" uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8" -version = "2.10.0+0" +version = "2.11.1+0" [[deps.IfElse]] git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1" @@ -201,9 +229,9 @@ uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173" version = "0.1.1" [[deps.Inflate]] -git-tree-sha1 = "ea8031dea4aff6bd41f1df8f2fdfb25b33626381" +git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d" uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" -version = "0.1.4" +version = "0.1.5" [[deps.InteractiveUtils]] deps = ["Markdown"] @@ -226,9 +254,9 @@ weakdeps = ["Serialization"] [[deps.LayoutPointers]] deps = ["ArrayInterface", "LinearAlgebra", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface"] -git-tree-sha1 = "62edfee3211981241b57ff1cedf4d74d79519277" +git-tree-sha1 = "a9eaadb366f5493a5654e843864c13d8b107548c" uuid = "10f19ff3-798f-405d-979b-55457f8fc047" -version = "0.1.15" +version = "0.1.17" [[deps.LazyArtifacts]] deps = ["Artifacts", "Pkg"] @@ -288,9 +316,9 @@ version = "1.0.3" [[deps.LoopVectorization]] deps = ["ArrayInterface", "CPUSummary", "CloseOpenIntervals", "DocStringExtensions", "HostCPUFeatures", "IfElse", "LayoutPointers", "LinearAlgebra", "OffsetArrays", "PolyesterWeave", "PrecompileTools", "SIMDTypes", "SLEEFPirates", "Static", "StaticArrayInterface", "ThreadingUtilities", "UnPack", "VectorizationBase"] -git-tree-sha1 = "0f5648fbae0d015e3abe5867bca2b362f67a5894" +git-tree-sha1 = "8084c25a250e00ae427a379a5b607e7aed96a2dd" uuid = "bdcacae8-1622-11e9-2a5c-532679323890" -version = "0.12.166" +version = "0.12.171" [deps.LoopVectorization.extensions] ForwardDiffExt = ["ChainRulesCore", "ForwardDiff"] @@ -301,23 +329,29 @@ version = "0.12.166" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" +[[deps.Lz4_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "7f26c8fc5229e68484e0b3447312c98e16207d11" +uuid = "5ced341a-0733-55b8-9ab6-a4889d929147" +version = "1.10.0+0" + [[deps.MPICH_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] -git-tree-sha1 = "656036b9ed6f942d35e536e249600bc31d0f9df8" +git-tree-sha1 = "19d4bd098928a3263693991500d05d74dbdc2004" uuid = "7cb0a576-ebde-5e09-9194-50597f1243b4" -version = "4.2.0+0" +version = "4.2.2+0" [[deps.MPIPreferences]] deps = ["Libdl", "Preferences"] -git-tree-sha1 = "8f6af051b9e8ec597fa09d8885ed79fd582f33c9" +git-tree-sha1 = "c105fe467859e7f6e9a852cb15cb4301126fac07" uuid = "3da0fdf6-3ccc-4f1b-acd9-58baa6c99267" -version = "0.1.10" +version = "0.1.11" [[deps.MPItrampoline_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] -git-tree-sha1 = "77c3bd69fdb024d75af38713e883d0f249ce19c2" +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] +git-tree-sha1 = "8c35d5420193841b2f367e658540e8d9e0601ed0" uuid = "f1f71cc9-e9ae-5b93-9b94-4fe0e1ad3748" -version = "5.3.2+0" +version = "5.4.0+0" [[deps.MacroTools]] deps = ["Markdown", "Random"] @@ -354,24 +388,30 @@ version = "2023.1.10" [[deps.NCDatasets]] deps = ["CFTime", "CommonDataModel", "DataStructures", "Dates", "DiskArrays", "NetCDF_jll", "NetworkOptions", "Printf"] -git-tree-sha1 = "173a378f357e9bb24b22019efb5e4778223ce8cf" +git-tree-sha1 = "a640912695952b074672edb5f9aaee2f7f9fd59a" uuid = "85f8d34a-cbdd-5861-8df4-14fed0d494ab" -version = "0.13.2" +version = "0.14.4" [[deps.NetCDF_jll]] -deps = ["Artifacts", "Bzip2_jll", "HDF5_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "XML2_jll", "Zlib_jll", "Zstd_jll"] -git-tree-sha1 = "10c612c81eaffdd6b7c28a45a554cdd9d2f40ff1" +deps = ["Artifacts", "Blosc_jll", "Bzip2_jll", "HDF5_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "TOML", "XML2_jll", "Zlib_jll", "Zstd_jll", "libzip_jll"] +git-tree-sha1 = "4686378c4ae1d1948cfbe46c002a11a4265dcb07" uuid = "7243133f-43d8-5620-bbf4-c2c921802cf3" -version = "400.902.208+0" +version = "400.902.211+1" + +[[deps.Nettle_jll]] +deps = ["Artifacts", "GMP_jll", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "eca63e3847dad608cfa6a3329b95ef674c7160b4" +uuid = "4c82536e-c426-54e4-b420-14f461c4ed8b" +version = "3.7.2+0" [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" version = "1.2.0" [[deps.OffsetArrays]] -git-tree-sha1 = "6a731f2b5c03157418a20c12195eb4b74c8f8621" +git-tree-sha1 = "1a27764e945a152f7ca7efa04de513d473e9542e" uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" -version = "1.13.0" +version = "1.14.1" weakdeps = ["Adapt"] [deps.OffsetArrays.extensions] @@ -390,15 +430,21 @@ version = "4.1.6+0" [[deps.OpenSSL_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "60e3045590bd104a16fefb12836c00c0ef8c7f8c" +git-tree-sha1 = "a028ee3cb5641cccc4c24e90c36b0a4f7707bdf5" uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" -version = "3.0.13+0" +version = "3.0.14+0" [[deps.OrderedCollections]] git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" version = "1.6.3" +[[deps.P11Kit_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "2cd396108e178f3ae8dedbd8e938a18726ab2fbf" +uuid = "c2071276-7c44-58a7-b746-946036e04d0a" +version = "0.24.1+0" + [[deps.Parameters]] deps = ["OrderedCollections", "UnPack"] git-tree-sha1 = "34c0e9ad262e5f7fc75b10a9952ca7692cfc5fbe" @@ -411,22 +457,22 @@ uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" version = "1.10.0" [[deps.Polyester]] -deps = ["ArrayInterface", "BitTwiddlingConvenienceFunctions", "CPUSummary", "IfElse", "ManualMemory", "PolyesterWeave", "Requires", "Static", "StaticArrayInterface", "StrideArraysCore", "ThreadingUtilities"] -git-tree-sha1 = "fca25670784a1ae44546bcb17288218310af2778" +deps = ["ArrayInterface", "BitTwiddlingConvenienceFunctions", "CPUSummary", "IfElse", "ManualMemory", "PolyesterWeave", "Static", "StaticArrayInterface", "StrideArraysCore", "ThreadingUtilities"] +git-tree-sha1 = "6d38fea02d983051776a856b7df75b30cf9a3c1f" uuid = "f517fe37-dbe3-4b94-8317-1923a5111588" -version = "0.7.9" +version = "0.7.16" [[deps.PolyesterWeave]] deps = ["BitTwiddlingConvenienceFunctions", "CPUSummary", "IfElse", "Static", "ThreadingUtilities"] -git-tree-sha1 = "240d7170f5ffdb285f9427b92333c3463bf65bf6" +git-tree-sha1 = "645bed98cd47f72f67316fd42fc47dee771aefcd" uuid = "1d0040c9-8b98-4ee7-8388-3f51789ca0ad" -version = "0.2.1" +version = "0.2.2" [[deps.PrecompileTools]] deps = ["Preferences"] -git-tree-sha1 = "03b4c25b43cb84cee5c90aa9b5ea0a78fd848d2f" +git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" -version = "1.2.0" +version = "1.2.1" [[deps.Preferences]] deps = ["TOML"] @@ -469,9 +515,9 @@ version = "0.1.0" [[deps.SLEEFPirates]] deps = ["IfElse", "Static", "VectorizationBase"] -git-tree-sha1 = "3aac6d68c5e57449f5b9b865c9ba50ac2970c4cf" +git-tree-sha1 = "456f610ca2fbd1c14f5fcf31c6bfadc55e7d66e0" uuid = "476501e8-09a2-5ece-8869-fb82de89a1fa" -version = "0.6.42" +version = "0.6.43" [[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" @@ -495,16 +541,16 @@ uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" version = "1.10.0" [[deps.Static]] -deps = ["IfElse"] -git-tree-sha1 = "d2fdac9ff3906e27f7a618d47b676941baa6c80c" +deps = ["CommonWorldInvalidations", "IfElse", "PrecompileTools"] +git-tree-sha1 = "87d51a3ee9a4b0d2fe054bdd3fc2436258db2603" uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3" -version = "0.8.10" +version = "1.1.1" [[deps.StaticArrayInterface]] -deps = ["ArrayInterface", "Compat", "IfElse", "LinearAlgebra", "PrecompileTools", "Requires", "SparseArrays", "Static", "SuiteSparse"] -git-tree-sha1 = "5d66818a39bb04bf328e92bc933ec5b4ee88e436" +deps = ["ArrayInterface", "Compat", "IfElse", "LinearAlgebra", "PrecompileTools", "Static"] +git-tree-sha1 = "96381d50f1ce85f2663584c8e886a6ca97e60554" uuid = "0d7ed370-da01-4f52-bd93-41d350b8b718" -version = "1.5.0" +version = "1.8.0" weakdeps = ["OffsetArrays", "StaticArrays"] [deps.StaticArrayInterface.extensions] @@ -513,9 +559,9 @@ weakdeps = ["OffsetArrays", "StaticArrays"] [[deps.StaticArrays]] deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] -git-tree-sha1 = "bf074c045d3d5ffd956fa0a461da38a44685d6b2" +git-tree-sha1 = "eeafab08ae20c62c44c8399ccb9354a04b80db50" uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.9.3" +version = "1.9.7" [deps.StaticArrays.extensions] StaticArraysChainRulesCoreExt = "ChainRulesCore" @@ -526,9 +572,9 @@ version = "1.9.3" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [[deps.StaticArraysCore]] -git-tree-sha1 = "36b3d696ce6366023a0ea192b4cd442268995a0d" +git-tree-sha1 = "192954ef1208c7019899fbf8049e717f92959682" uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" -version = "1.4.2" +version = "1.4.3" [[deps.Statistics]] deps = ["LinearAlgebra", "SparseArrays"] @@ -536,14 +582,10 @@ uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" version = "1.10.0" [[deps.StrideArraysCore]] -deps = ["ArrayInterface", "CloseOpenIntervals", "IfElse", "LayoutPointers", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface", "ThreadingUtilities"] -git-tree-sha1 = "d6415f66f3d89c615929af907fdc6a3e17af0d8c" +deps = ["ArrayInterface", "CloseOpenIntervals", "IfElse", "LayoutPointers", "LinearAlgebra", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface", "ThreadingUtilities"] +git-tree-sha1 = "f35f6ab602df8413a50c4a25ca14de821e8605fb" uuid = "7792a7ef-975c-4747-a70f-980b88e8d1da" -version = "0.5.2" - -[[deps.SuiteSparse]] -deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"] -uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9" +version = "0.5.7" [[deps.SuiteSparse_jll]] deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] @@ -590,13 +632,13 @@ uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" [[deps.VectorizationBase]] deps = ["ArrayInterface", "CPUSummary", "HostCPUFeatures", "IfElse", "LayoutPointers", "Libdl", "LinearAlgebra", "SIMDTypes", "Static", "StaticArrayInterface"] -git-tree-sha1 = "7209df901e6ed7489fe9b7aa3e46fb788e15db85" +git-tree-sha1 = "e7f5b81c65eb858bed630fe006837b935518aca5" uuid = "3d5dd08c-fd9d-11e8-17fa-ed2836048c2f" -version = "0.21.65" +version = "0.21.70" [[deps.Wflow]] deps = ["BasicModelInterface", "CFTime", "Dates", "DelimitedFiles", "FieldMetadata", "Glob", "Graphs", "IfElse", "LoggingExtras", "LoopVectorization", "NCDatasets", "Parameters", "Polyester", "ProgressLogging", "Random", "StaticArrays", "Statistics", "TOML", "TerminalLoggers", "UnPack"] -git-tree-sha1 = "29fdf975f5a32b15890182f114b72ea6ba7b21cc" +git-tree-sha1 = "414d32f07561872f03cb4f347df719962dfddc5e" repo-rev = "master" repo-url = "../.." uuid = "d48b7d99-76e7-47ae-b1d5-ff0c1cf9a818" @@ -604,9 +646,15 @@ version = "0.7.3" [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] -git-tree-sha1 = "07e470dabc5a6a4254ffebc29a1b3fc01464e105" +git-tree-sha1 = "d9717ce3518dc68a99e6b96300813760d887a01d" uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" -version = "2.12.5+0" +version = "2.13.1+0" + +[[deps.XZ_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "ac88fb95ae6447c8dda6a5503f3bafd496ae8632" +uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800" +version = "5.4.6+0" [[deps.Zlib_jll]] deps = ["Libdl"] @@ -615,9 +663,9 @@ version = "1.2.13+1" [[deps.Zstd_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "49ce682769cd5de6c72dcf1b94ed7790cd08974c" +git-tree-sha1 = "e678132f07ddb5bfa46857f0d7620fb9be675d3b" uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" -version = "1.5.5+0" +version = "1.5.6+0" [[deps.libaec_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] @@ -630,6 +678,12 @@ deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" version = "5.8.0+1" +[[deps.libzip_jll]] +deps = ["Artifacts", "Bzip2_jll", "GnuTLS_jll", "JLLWrappers", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] +git-tree-sha1 = "3282b7d16ae7ac3e57ec2f3fa8fafb564d8f9f7f" +uuid = "337d8026-41b4-5cde-a456-74a10e5b31d1" +version = "1.10.1+0" + [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" diff --git a/build/wflow_cli/Setup/Installer/Application.Setup.wixproj b/build/wflow_cli/Setup/Installer/Application.Setup.wixproj index b8f77e563..77abb9a1b 100644 --- a/build/wflow_cli/Setup/Installer/Application.Setup.wixproj +++ b/build/wflow_cli/Setup/Installer/Application.Setup.wixproj @@ -6,7 +6,7 @@ 3.10 {39da8083-e405-42e6-850c-d25685e91f81} 2.0 - 0.7.3 + 0.8.0 Package false diff --git a/docs/src/changelog.md b/docs/src/changelog.md index 27121405a..fbea727e6 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [unreleased] +## v0.8.0 - 2024-08-19 ### Fixed - Added missing BMI function `get_grid_size`, it is used for unstructured grids, for example diff --git a/server/Manifest.toml b/server/Manifest.toml index aba619527..456d646af 100644 --- a/server/Manifest.toml +++ b/server/Manifest.toml @@ -1,6 +1,6 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.10.2" +julia_version = "1.10.0" manifest_format = "2.0" project_hash = "b955683a8ff3d663c1ae626b1dc754fbae9fa2f7" @@ -11,9 +11,9 @@ version = "0.4.5" [[deps.Adapt]] deps = ["LinearAlgebra", "Requires"] -git-tree-sha1 = "e2a9873379849ce2ac9f9fa34b0e37bde5d5fe0a" +git-tree-sha1 = "6a55b747d1812e699320963ffde36f1ebdda4099" uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" -version = "4.0.2" +version = "4.0.4" weakdeps = ["StaticArrays"] [deps.Adapt.extensions] @@ -25,22 +25,25 @@ version = "1.1.1" [[deps.ArnoldiMethod]] deps = ["LinearAlgebra", "Random", "StaticArrays"] -git-tree-sha1 = "62e51b39331de8911e4a7ff6f5aaf38a5f4cc0ae" +git-tree-sha1 = "d57bd3762d308bded22c3b82d033bff85f6195c6" uuid = "ec485272-7323-5ecc-a04f-4719b315124d" -version = "0.2.0" +version = "0.4.0" [[deps.ArrayInterface]] -deps = ["Adapt", "LinearAlgebra", "Requires", "SparseArrays", "SuiteSparse"] -git-tree-sha1 = "881e43f1aa014a6f75c8fc0847860e00a1500846" +deps = ["Adapt", "LinearAlgebra"] +git-tree-sha1 = "f54c23a5d304fb87110de62bace7777d59088c34" uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" -version = "7.8.0" +version = "7.15.0" [deps.ArrayInterface.extensions] ArrayInterfaceBandedMatricesExt = "BandedMatrices" ArrayInterfaceBlockBandedMatricesExt = "BlockBandedMatrices" ArrayInterfaceCUDAExt = "CUDA" + ArrayInterfaceCUDSSExt = "CUDSS" + ArrayInterfaceChainRulesExt = "ChainRules" ArrayInterfaceGPUArraysCoreExt = "GPUArraysCore" ArrayInterfaceReverseDiffExt = "ReverseDiff" + ArrayInterfaceSparseArraysExt = "SparseArrays" ArrayInterfaceStaticArraysCoreExt = "StaticArraysCore" ArrayInterfaceTrackerExt = "Tracker" @@ -48,8 +51,11 @@ version = "7.8.0" BandedMatrices = "aae01518-5342-5314-be14-df237901396f" BlockBandedMatrices = "ffab5731-97b5-5995-9138-79e8c1846df0" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" + CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" + ChainRules = "082447d4-558c-5d27-93f4-14fc19e9eca2" GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527" ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" + SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" @@ -66,9 +72,15 @@ version = "0.1.0" [[deps.BitTwiddlingConvenienceFunctions]] deps = ["Static"] -git-tree-sha1 = "0c5f81f47bbbcf4aea7b2959135713459170798b" +git-tree-sha1 = "f21cfd4950cb9f0587d5067e69405ad2acd27b87" uuid = "62783981-4cbd-42fc-bca8-16325de8dc4b" -version = "0.1.5" +version = "0.1.6" + +[[deps.Blosc_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Lz4_jll", "Zlib_jll", "Zstd_jll"] +git-tree-sha1 = "19b98ee7e3db3b4eff74c5c9c72bf32144e24f10" +uuid = "0b7ba130-8d10-5ba8-a3d6-c5182647fed9" +version = "1.21.5+0" [[deps.Bzip2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] @@ -84,27 +96,32 @@ version = "0.1.3" [[deps.CPUSummary]] deps = ["CpuId", "IfElse", "PrecompileTools", "Static"] -git-tree-sha1 = "601f7e7b3d36f18790e2caf83a882d88e9b71ff1" +git-tree-sha1 = "5a97e67919535d6841172016c9530fd69494e5ec" uuid = "2a0fbf3d-bb9c-48f3-b0a9-814d99fd7ab9" -version = "0.2.4" +version = "0.2.6" [[deps.CloseOpenIntervals]] deps = ["Static", "StaticArrayInterface"] -git-tree-sha1 = "70232f82ffaab9dc52585e0dd043b5e0c6b714f1" +git-tree-sha1 = "05ba0d07cd4fd8b7a39541e31a7b0254704ea581" uuid = "fb6a15b2-703c-40df-9091-08a04967cfa9" -version = "0.1.12" +version = "0.1.13" [[deps.CommonDataModel]] -deps = ["CFTime", "DataStructures", "Dates", "Preferences", "Printf"] -git-tree-sha1 = "7f5717cbb2c1ce650cfd454451f282df33103596" +deps = ["CFTime", "DataStructures", "Dates", "Preferences", "Printf", "Statistics"] +git-tree-sha1 = "d6fb5bf939a2753c74984b11434ea25d6c397a58" uuid = "1fbeeb36-5f17-413c-809b-666fb144f157" -version = "0.2.5" +version = "0.3.6" + +[[deps.CommonWorldInvalidations]] +git-tree-sha1 = "ae52d1c52048455e85a387fbee9be553ec2b68d0" +uuid = "f70d9fcc-98c5-4d4a-abd7-e4cdeebd8ca8" +version = "1.0.0" [[deps.Compat]] deps = ["TOML", "UUIDs"] -git-tree-sha1 = "c955881e3c981181362ae4088b35995446298b80" +git-tree-sha1 = "8ae8d32e09f0dcf42a36b90d4e17f5dd2e4c4215" uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "4.14.0" +version = "4.16.0" weakdeps = ["Dates", "LinearAlgebra"] [deps.Compat.extensions] @@ -113,7 +130,7 @@ weakdeps = ["Dates", "LinearAlgebra"] [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" -version = "1.1.0+0" +version = "1.0.5+1" [[deps.CpuId]] deps = ["Markdown"] @@ -123,9 +140,9 @@ version = "0.3.1" [[deps.DataStructures]] deps = ["Compat", "InteractiveUtils", "OrderedCollections"] -git-tree-sha1 = "0f4b5d62a88d8f59003e43c25a8a90de9eb76317" +git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82" uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.18.18" +version = "0.18.20" [[deps.Dates]] deps = ["Printf"] @@ -166,34 +183,45 @@ version = "0.3.1" [[deps.FileWatching]] uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +[[deps.GMP_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "781609d7-10c4-51f6-84f2-b8444358ff6d" +version = "6.2.1+6" + [[deps.Glob]] git-tree-sha1 = "97285bbd5230dd766e9ef6749b80fc617126d496" uuid = "c27321d9-0574-5035-807b-f59d2c89b15c" version = "1.3.1" +[[deps.GnuTLS_jll]] +deps = ["Artifacts", "GMP_jll", "JLLWrappers", "Libdl", "Nettle_jll", "P11Kit_jll", "Zlib_jll"] +git-tree-sha1 = "383db7d3f900f4c1f47a8a04115b053c095e48d3" +uuid = "0951126a-58fd-58f1-b5b3-b08c7c4a876d" +version = "3.8.4+0" + [[deps.Graphs]] deps = ["ArnoldiMethod", "Compat", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] -git-tree-sha1 = "899050ace26649433ef1af25bc17a815b3db52b7" +git-tree-sha1 = "ebd18c326fa6cee1efb7da9a3b45cf69da2ed4d9" uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" -version = "1.9.0" +version = "1.11.2" [[deps.HDF5_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "libaec_jll"] -git-tree-sha1 = "e4591176488495bf44d7456bd73179d87d5e6eab" +git-tree-sha1 = "82a471768b513dc39e471540fdadc84ff80ff997" uuid = "0234f1f7-429e-5d53-9886-15a909be8d59" -version = "1.14.3+1" +version = "1.14.3+3" [[deps.HostCPUFeatures]] deps = ["BitTwiddlingConvenienceFunctions", "IfElse", "Libdl", "Static"] -git-tree-sha1 = "eb8fed28f4994600e29beef49744639d985a04b2" +git-tree-sha1 = "8e070b599339d622e9a081d17230d74a5c473293" uuid = "3e5b6fbb-0976-4d2c-9146-d79de83f2fb0" -version = "0.1.16" +version = "0.1.17" [[deps.Hwloc_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "ca0f6bf568b4bfc807e7537f081c81e35ceca114" +git-tree-sha1 = "5e19e1e4fa3e71b774ce746274364aef0234634e" uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8" -version = "2.10.0+0" +version = "2.11.1+0" [[deps.IfElse]] git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1" @@ -201,9 +229,9 @@ uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173" version = "0.1.1" [[deps.Inflate]] -git-tree-sha1 = "ea8031dea4aff6bd41f1df8f2fdfb25b33626381" +git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d" uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" -version = "0.1.4" +version = "0.1.5" [[deps.InteractiveUtils]] deps = ["Markdown"] @@ -238,9 +266,9 @@ weakdeps = ["Serialization"] [[deps.LayoutPointers]] deps = ["ArrayInterface", "LinearAlgebra", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface"] -git-tree-sha1 = "62edfee3211981241b57ff1cedf4d74d79519277" +git-tree-sha1 = "a9eaadb366f5493a5654e843864c13d8b107548c" uuid = "10f19ff3-798f-405d-979b-55457f8fc047" -version = "0.1.15" +version = "0.1.17" [[deps.LazyArtifacts]] deps = ["Artifacts", "Pkg"] @@ -300,9 +328,9 @@ version = "1.0.3" [[deps.LoopVectorization]] deps = ["ArrayInterface", "CPUSummary", "CloseOpenIntervals", "DocStringExtensions", "HostCPUFeatures", "IfElse", "LayoutPointers", "LinearAlgebra", "OffsetArrays", "PolyesterWeave", "PrecompileTools", "SIMDTypes", "SLEEFPirates", "Static", "StaticArrayInterface", "ThreadingUtilities", "UnPack", "VectorizationBase"] -git-tree-sha1 = "0f5648fbae0d015e3abe5867bca2b362f67a5894" +git-tree-sha1 = "8084c25a250e00ae427a379a5b607e7aed96a2dd" uuid = "bdcacae8-1622-11e9-2a5c-532679323890" -version = "0.12.166" +version = "0.12.171" [deps.LoopVectorization.extensions] ForwardDiffExt = ["ChainRulesCore", "ForwardDiff"] @@ -313,23 +341,29 @@ version = "0.12.166" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" +[[deps.Lz4_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "7f26c8fc5229e68484e0b3447312c98e16207d11" +uuid = "5ced341a-0733-55b8-9ab6-a4889d929147" +version = "1.10.0+0" + [[deps.MPICH_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] -git-tree-sha1 = "656036b9ed6f942d35e536e249600bc31d0f9df8" +git-tree-sha1 = "19d4bd098928a3263693991500d05d74dbdc2004" uuid = "7cb0a576-ebde-5e09-9194-50597f1243b4" -version = "4.2.0+0" +version = "4.2.2+0" [[deps.MPIPreferences]] deps = ["Libdl", "Preferences"] -git-tree-sha1 = "8f6af051b9e8ec597fa09d8885ed79fd582f33c9" +git-tree-sha1 = "c105fe467859e7f6e9a852cb15cb4301126fac07" uuid = "3da0fdf6-3ccc-4f1b-acd9-58baa6c99267" -version = "0.1.10" +version = "0.1.11" [[deps.MPItrampoline_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] -git-tree-sha1 = "77c3bd69fdb024d75af38713e883d0f249ce19c2" +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] +git-tree-sha1 = "8c35d5420193841b2f367e658540e8d9e0601ed0" uuid = "f1f71cc9-e9ae-5b93-9b94-4fe0e1ad3748" -version = "5.3.2+0" +version = "5.4.0+0" [[deps.MacroTools]] deps = ["Markdown", "Random"] @@ -366,24 +400,30 @@ version = "2023.1.10" [[deps.NCDatasets]] deps = ["CFTime", "CommonDataModel", "DataStructures", "Dates", "DiskArrays", "NetCDF_jll", "NetworkOptions", "Printf"] -git-tree-sha1 = "173a378f357e9bb24b22019efb5e4778223ce8cf" +git-tree-sha1 = "a640912695952b074672edb5f9aaee2f7f9fd59a" uuid = "85f8d34a-cbdd-5861-8df4-14fed0d494ab" -version = "0.13.2" +version = "0.14.4" [[deps.NetCDF_jll]] -deps = ["Artifacts", "Bzip2_jll", "HDF5_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "XML2_jll", "Zlib_jll", "Zstd_jll"] -git-tree-sha1 = "10c612c81eaffdd6b7c28a45a554cdd9d2f40ff1" +deps = ["Artifacts", "Blosc_jll", "Bzip2_jll", "HDF5_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "TOML", "XML2_jll", "Zlib_jll", "Zstd_jll", "libzip_jll"] +git-tree-sha1 = "4686378c4ae1d1948cfbe46c002a11a4265dcb07" uuid = "7243133f-43d8-5620-bbf4-c2c921802cf3" -version = "400.902.208+0" +version = "400.902.211+1" + +[[deps.Nettle_jll]] +deps = ["Artifacts", "GMP_jll", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "eca63e3847dad608cfa6a3329b95ef674c7160b4" +uuid = "4c82536e-c426-54e4-b420-14f461c4ed8b" +version = "3.7.2+0" [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" version = "1.2.0" [[deps.OffsetArrays]] -git-tree-sha1 = "6a731f2b5c03157418a20c12195eb4b74c8f8621" +git-tree-sha1 = "1a27764e945a152f7ca7efa04de513d473e9542e" uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" -version = "1.13.0" +version = "1.14.1" weakdeps = ["Adapt"] [deps.OffsetArrays.extensions] @@ -392,7 +432,7 @@ weakdeps = ["Adapt"] [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.23+4" +version = "0.3.23+2" [[deps.OpenMPI_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] @@ -402,15 +442,21 @@ version = "4.1.6+0" [[deps.OpenSSL_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "60e3045590bd104a16fefb12836c00c0ef8c7f8c" +git-tree-sha1 = "a028ee3cb5641cccc4c24e90c36b0a4f7707bdf5" uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" -version = "3.0.13+0" +version = "3.0.14+0" [[deps.OrderedCollections]] git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" version = "1.6.3" +[[deps.P11Kit_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "2cd396108e178f3ae8dedbd8e938a18726ab2fbf" +uuid = "c2071276-7c44-58a7-b746-946036e04d0a" +version = "0.24.1+0" + [[deps.Parameters]] deps = ["OrderedCollections", "UnPack"] git-tree-sha1 = "34c0e9ad262e5f7fc75b10a9952ca7692cfc5fbe" @@ -429,22 +475,22 @@ uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" version = "1.10.0" [[deps.Polyester]] -deps = ["ArrayInterface", "BitTwiddlingConvenienceFunctions", "CPUSummary", "IfElse", "ManualMemory", "PolyesterWeave", "Requires", "Static", "StaticArrayInterface", "StrideArraysCore", "ThreadingUtilities"] -git-tree-sha1 = "fca25670784a1ae44546bcb17288218310af2778" +deps = ["ArrayInterface", "BitTwiddlingConvenienceFunctions", "CPUSummary", "IfElse", "ManualMemory", "PolyesterWeave", "Static", "StaticArrayInterface", "StrideArraysCore", "ThreadingUtilities"] +git-tree-sha1 = "6d38fea02d983051776a856b7df75b30cf9a3c1f" uuid = "f517fe37-dbe3-4b94-8317-1923a5111588" -version = "0.7.9" +version = "0.7.16" [[deps.PolyesterWeave]] deps = ["BitTwiddlingConvenienceFunctions", "CPUSummary", "IfElse", "Static", "ThreadingUtilities"] -git-tree-sha1 = "240d7170f5ffdb285f9427b92333c3463bf65bf6" +git-tree-sha1 = "645bed98cd47f72f67316fd42fc47dee771aefcd" uuid = "1d0040c9-8b98-4ee7-8388-3f51789ca0ad" -version = "0.2.1" +version = "0.2.2" [[deps.PrecompileTools]] deps = ["Preferences"] -git-tree-sha1 = "03b4c25b43cb84cee5c90aa9b5ea0a78fd848d2f" +git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" -version = "1.2.0" +version = "1.2.1" [[deps.Preferences]] deps = ["TOML"] @@ -487,9 +533,9 @@ version = "0.1.0" [[deps.SLEEFPirates]] deps = ["IfElse", "Static", "VectorizationBase"] -git-tree-sha1 = "3aac6d68c5e57449f5b9b865c9ba50ac2970c4cf" +git-tree-sha1 = "456f610ca2fbd1c14f5fcf31c6bfadc55e7d66e0" uuid = "476501e8-09a2-5ece-8869-fb82de89a1fa" -version = "0.6.42" +version = "0.6.43" [[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" @@ -513,16 +559,16 @@ uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" version = "1.10.0" [[deps.Static]] -deps = ["IfElse"] -git-tree-sha1 = "d2fdac9ff3906e27f7a618d47b676941baa6c80c" +deps = ["CommonWorldInvalidations", "IfElse", "PrecompileTools"] +git-tree-sha1 = "87d51a3ee9a4b0d2fe054bdd3fc2436258db2603" uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3" -version = "0.8.10" +version = "1.1.1" [[deps.StaticArrayInterface]] -deps = ["ArrayInterface", "Compat", "IfElse", "LinearAlgebra", "PrecompileTools", "Requires", "SparseArrays", "Static", "SuiteSparse"] -git-tree-sha1 = "5d66818a39bb04bf328e92bc933ec5b4ee88e436" +deps = ["ArrayInterface", "Compat", "IfElse", "LinearAlgebra", "PrecompileTools", "Static"] +git-tree-sha1 = "96381d50f1ce85f2663584c8e886a6ca97e60554" uuid = "0d7ed370-da01-4f52-bd93-41d350b8b718" -version = "1.5.0" +version = "1.8.0" weakdeps = ["OffsetArrays", "StaticArrays"] [deps.StaticArrayInterface.extensions] @@ -531,9 +577,9 @@ weakdeps = ["OffsetArrays", "StaticArrays"] [[deps.StaticArrays]] deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] -git-tree-sha1 = "bf074c045d3d5ffd956fa0a461da38a44685d6b2" +git-tree-sha1 = "eeafab08ae20c62c44c8399ccb9354a04b80db50" uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.9.3" +version = "1.9.7" [deps.StaticArrays.extensions] StaticArraysChainRulesCoreExt = "ChainRulesCore" @@ -544,9 +590,9 @@ version = "1.9.3" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [[deps.StaticArraysCore]] -git-tree-sha1 = "36b3d696ce6366023a0ea192b4cd442268995a0d" +git-tree-sha1 = "192954ef1208c7019899fbf8049e717f92959682" uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" -version = "1.4.2" +version = "1.4.3" [[deps.Statistics]] deps = ["LinearAlgebra", "SparseArrays"] @@ -554,10 +600,10 @@ uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" version = "1.10.0" [[deps.StrideArraysCore]] -deps = ["ArrayInterface", "CloseOpenIntervals", "IfElse", "LayoutPointers", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface", "ThreadingUtilities"] -git-tree-sha1 = "d6415f66f3d89c615929af907fdc6a3e17af0d8c" +deps = ["ArrayInterface", "CloseOpenIntervals", "IfElse", "LayoutPointers", "LinearAlgebra", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface", "ThreadingUtilities"] +git-tree-sha1 = "f35f6ab602df8413a50c4a25ca14de821e8605fb" uuid = "7792a7ef-975c-4747-a70f-980b88e8d1da" -version = "0.5.2" +version = "0.5.7" [[deps.StructTypes]] deps = ["Dates", "UUIDs"] @@ -565,10 +611,6 @@ git-tree-sha1 = "ca4bccb03acf9faaf4137a9abc1881ed1841aa70" uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" version = "1.10.0" -[[deps.SuiteSparse]] -deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"] -uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9" - [[deps.SuiteSparse_jll]] deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" @@ -610,27 +652,33 @@ uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" [[deps.VectorizationBase]] deps = ["ArrayInterface", "CPUSummary", "HostCPUFeatures", "IfElse", "LayoutPointers", "Libdl", "LinearAlgebra", "SIMDTypes", "Static", "StaticArrayInterface"] -git-tree-sha1 = "7209df901e6ed7489fe9b7aa3e46fb788e15db85" +git-tree-sha1 = "e7f5b81c65eb858bed630fe006837b935518aca5" uuid = "3d5dd08c-fd9d-11e8-17fa-ed2836048c2f" -version = "0.21.65" +version = "0.21.70" [[deps.Wflow]] deps = ["BasicModelInterface", "CFTime", "Dates", "DelimitedFiles", "FieldMetadata", "Glob", "Graphs", "IfElse", "LoggingExtras", "LoopVectorization", "NCDatasets", "Parameters", "Polyester", "ProgressLogging", "Random", "StaticArrays", "Statistics", "TOML", "TerminalLoggers", "UnPack"] path = ".." uuid = "d48b7d99-76e7-47ae-b1d5-ff0c1cf9a818" -version = "0.7.3" +version = "0.8.0" [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] -git-tree-sha1 = "07e470dabc5a6a4254ffebc29a1b3fc01464e105" +git-tree-sha1 = "d9717ce3518dc68a99e6b96300813760d887a01d" uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" -version = "2.12.5+0" +version = "2.13.1+0" + +[[deps.XZ_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "ac88fb95ae6447c8dda6a5503f3bafd496ae8632" +uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800" +version = "5.4.6+0" [[deps.ZMQ]] -deps = ["FileWatching", "Sockets", "ZeroMQ_jll"] -git-tree-sha1 = "356d2bdcc0bce90aabee1d1c0f6d6f301eda8f77" +deps = ["FileWatching", "PrecompileTools", "Sockets", "ZeroMQ_jll"] +git-tree-sha1 = "18cfd00df3cbbebf8ea4ec7ea6bbceb3af716bd0" uuid = "c2297ded-f4af-51ae-bb23-16f91089e4e1" -version = "1.2.2" +version = "1.3.0" [[deps.ZeroMQ_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "libsodium_jll"] @@ -645,9 +693,9 @@ version = "1.2.13+1" [[deps.Zstd_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "49ce682769cd5de6c72dcf1b94ed7790cd08974c" +git-tree-sha1 = "e678132f07ddb5bfa46857f0d7620fb9be675d3b" uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" -version = "1.5.5+0" +version = "1.5.6+0" [[deps.libaec_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] @@ -666,6 +714,12 @@ git-tree-sha1 = "848ab3d00fe39d6fbc2a8641048f8f272af1c51e" uuid = "a9144af2-ca23-56d9-984f-0d03f7b5ccf8" version = "1.0.20+0" +[[deps.libzip_jll]] +deps = ["Artifacts", "Bzip2_jll", "GnuTLS_jll", "JLLWrappers", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] +git-tree-sha1 = "3282b7d16ae7ac3e57ec2f3fa8fafb564d8f9f7f" +uuid = "337d8026-41b4-5cde-a456-74a10e5b31d1" +version = "1.10.1+0" + [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" From 678d0b645fff71360e7a9ec6e2cebbef365a4123 Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Mon, 19 Aug 2024 16:54:31 +0200 Subject: [PATCH 02/27] Fix AutoMerge issue `Polyester` --- Manifest.toml | 2 +- Project.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Manifest.toml b/Manifest.toml index 7f8e34277..0f3053ecd 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -2,7 +2,7 @@ julia_version = "1.10.0" manifest_format = "2.0" -project_hash = "d28d785486f7576a44ae0401e6a18f49d6f3007c" +project_hash = "acf9296ffbaffd3f335bdb6005badef66b3e10fe" [[deps.AbstractTrees]] git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177" diff --git a/Project.toml b/Project.toml index 99e762213..d6a591b37 100644 --- a/Project.toml +++ b/Project.toml @@ -40,7 +40,7 @@ LoggingExtras = "0.4.6,0.5,1" LoopVectorization = "0.12" NCDatasets = "0.13.2, 0.14" Parameters = "0.12" -Polyester = ">=0.7.10" +Polyester = "0.7.10" Polynomials = "3,4" ProgressLogging = "0.1" QuadGK = "2" From ec0f7fdcc7a8d56b4aac01842d6d65c0a55c6a8d Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Wed, 21 Aug 2024 08:14:49 +0200 Subject: [PATCH 03/27] Fix wflow version Manifest.toml files --- build/create_binaries/Manifest.toml | 2 +- build/wflow_cli/Manifest.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/create_binaries/Manifest.toml b/build/create_binaries/Manifest.toml index 9826d50d1..0db49b355 100644 --- a/build/create_binaries/Manifest.toml +++ b/build/create_binaries/Manifest.toml @@ -656,7 +656,7 @@ git-tree-sha1 = "414d32f07561872f03cb4f347df719962dfddc5e" repo-rev = "master" repo-url = "../.." uuid = "d48b7d99-76e7-47ae-b1d5-ff0c1cf9a818" -version = "0.7.3" +version = "0.8.0" [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] diff --git a/build/wflow_cli/Manifest.toml b/build/wflow_cli/Manifest.toml index 1073bedc1..5fa131b12 100644 --- a/build/wflow_cli/Manifest.toml +++ b/build/wflow_cli/Manifest.toml @@ -642,7 +642,7 @@ git-tree-sha1 = "414d32f07561872f03cb4f347df719962dfddc5e" repo-rev = "master" repo-url = "../.." uuid = "d48b7d99-76e7-47ae-b1d5-ff0c1cf9a818" -version = "0.7.3" +version = "0.8.0" [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] From 42461bf32f1df6b50a570a5f3c29094d7ab3451e Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Wed, 21 Aug 2024 09:36:52 +0200 Subject: [PATCH 04/27] Fix wflow path in Manifest.toml files build --- build/create_binaries/Manifest.toml | 2 +- build/wflow_cli/Manifest.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/create_binaries/Manifest.toml b/build/create_binaries/Manifest.toml index 0db49b355..965d16ff2 100644 --- a/build/create_binaries/Manifest.toml +++ b/build/create_binaries/Manifest.toml @@ -654,7 +654,7 @@ version = "0.21.70" deps = ["BasicModelInterface", "CFTime", "Dates", "DelimitedFiles", "FieldMetadata", "Glob", "Graphs", "IfElse", "LoggingExtras", "LoopVectorization", "NCDatasets", "Parameters", "Polyester", "ProgressLogging", "Random", "StaticArrays", "Statistics", "TOML", "TerminalLoggers", "UnPack"] git-tree-sha1 = "414d32f07561872f03cb4f347df719962dfddc5e" repo-rev = "master" -repo-url = "../.." +path = "../.." uuid = "d48b7d99-76e7-47ae-b1d5-ff0c1cf9a818" version = "0.8.0" diff --git a/build/wflow_cli/Manifest.toml b/build/wflow_cli/Manifest.toml index 5fa131b12..1f3195e4f 100644 --- a/build/wflow_cli/Manifest.toml +++ b/build/wflow_cli/Manifest.toml @@ -640,7 +640,7 @@ version = "0.21.70" deps = ["BasicModelInterface", "CFTime", "Dates", "DelimitedFiles", "FieldMetadata", "Glob", "Graphs", "IfElse", "LoggingExtras", "LoopVectorization", "NCDatasets", "Parameters", "Polyester", "ProgressLogging", "Random", "StaticArrays", "Statistics", "TOML", "TerminalLoggers", "UnPack"] git-tree-sha1 = "414d32f07561872f03cb4f347df719962dfddc5e" repo-rev = "master" -repo-url = "../.." +path = "../.." uuid = "d48b7d99-76e7-47ae-b1d5-ff0c1cf9a818" version = "0.8.0" From e2fa040bacf3531ee439f81247cb10961c367652 Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Wed, 21 Aug 2024 12:20:31 +0200 Subject: [PATCH 05/27] Manifest.toml files build and wflow version - Remove wflow version from Manifest.toml files (build) otherwise test-wflow-cli workflow fails (ERROR: expected package `Wflow [d48b7d99]` to exist at path `C:\Users\svc-teamcity-ansible\.julia\packages\Wflow\zzRQr`) - Extract wflow version from Project.toml file --- build/create_binaries/Manifest.toml | 2 -- build/create_binaries/add_metadata.jl | 2 +- build/wflow_cli/Manifest.toml | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/build/create_binaries/Manifest.toml b/build/create_binaries/Manifest.toml index 965d16ff2..1fbd18b77 100644 --- a/build/create_binaries/Manifest.toml +++ b/build/create_binaries/Manifest.toml @@ -653,10 +653,8 @@ version = "0.21.70" [[deps.Wflow]] deps = ["BasicModelInterface", "CFTime", "Dates", "DelimitedFiles", "FieldMetadata", "Glob", "Graphs", "IfElse", "LoggingExtras", "LoopVectorization", "NCDatasets", "Parameters", "Polyester", "ProgressLogging", "Random", "StaticArrays", "Statistics", "TOML", "TerminalLoggers", "UnPack"] git-tree-sha1 = "414d32f07561872f03cb4f347df719962dfddc5e" -repo-rev = "master" path = "../.." uuid = "d48b7d99-76e7-47ae-b1d5-ff0c1cf9a818" -version = "0.8.0" [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] diff --git a/build/create_binaries/add_metadata.jl b/build/create_binaries/add_metadata.jl index 64e8fd441..0bf14242f 100644 --- a/build/create_binaries/add_metadata.jl +++ b/build/create_binaries/add_metadata.jl @@ -42,7 +42,7 @@ function add_metadata(project_dir, license_file, output_dir, git_repo) julia_version = manifest["julia_version"] wflow_entry = only(manifest["deps"]["Wflow"]) tree = wflow_entry["git-tree-sha1"] - version = wflow_entry["version"] + version = TOML.parsefile(normpath(git_repo, "Project.toml"))["version"] repo = GitRepo(git_repo) branch = LibGit2.head(repo) # commit = LibGit2.peel(LibGit2.GitCommit, branch) diff --git a/build/wflow_cli/Manifest.toml b/build/wflow_cli/Manifest.toml index 1f3195e4f..238ef922d 100644 --- a/build/wflow_cli/Manifest.toml +++ b/build/wflow_cli/Manifest.toml @@ -639,10 +639,8 @@ version = "0.21.70" [[deps.Wflow]] deps = ["BasicModelInterface", "CFTime", "Dates", "DelimitedFiles", "FieldMetadata", "Glob", "Graphs", "IfElse", "LoggingExtras", "LoopVectorization", "NCDatasets", "Parameters", "Polyester", "ProgressLogging", "Random", "StaticArrays", "Statistics", "TOML", "TerminalLoggers", "UnPack"] git-tree-sha1 = "414d32f07561872f03cb4f347df719962dfddc5e" -repo-rev = "master" path = "../.." uuid = "d48b7d99-76e7-47ae-b1d5-ff0c1cf9a818" -version = "0.8.0" [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] From 0c902d546191f54ecbefd9332efd07daba0edee3 Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Tue, 27 Aug 2024 09:31:18 +0200 Subject: [PATCH 06/27] Reduce allocations in update vertical `SBM` concept (#450) * Reduce allocations related to water demand in `SBM` Defining water demand fields in the SBM struct of Union type (e.g. Union{Paddy,Nothing}) results in significant more allocations and slower run times (if statement with !isnothing()). This has been fixed by Including these fields as type parameters. * Reduce allocations `update_total_water_storage` * Update changelog --- docs/src/changelog.md | 5 +++++ src/sbm.jl | 41 ++++++++++++++++++++++++----------------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/docs/src/changelog.md b/docs/src/changelog.md index fbea727e6..898ac5f9e 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Fixed +- Reduce allocations in update of vertical `SBM` concept. + ## v0.8.0 - 2024-08-19 ### Fixed diff --git a/src/sbm.jl b/src/sbm.jl index 60bfc6f24..baab14691 100644 --- a/src/sbm.jl +++ b/src/sbm.jl @@ -1,4 +1,14 @@ -@get_units @exchange @grid_type @grid_location @with_kw struct SBM{T,N,M} +@get_units @exchange @grid_type @grid_location @with_kw struct SBM{ + T, + N, + M, + P<:Union{Paddy,Nothing}, + NP<:Union{NonPaddy,Nothing}, + D<:Union{NonIrrigationDemand,Nothing}, + L<:Union{NonIrrigationDemand,Nothing}, + I<:Union{NonIrrigationDemand,Nothing}, + A<:Union{AllocationLand,Nothing}, +} # Model time step [s] dt::T | "s" | 0 | "none" | "none" # Maximum number of soil layers @@ -225,15 +235,15 @@ # Total water storage (excluding floodplain volume, lakes and reservoirs) [mm] total_storage::Vector{T} | "mm" # Water demand structs (of arrays) - paddy::Union{Paddy,Nothing} | "-" | 0 - nonpaddy::Union{NonPaddy,Nothing} | "-" | 0 - domestic::Union{NonIrrigationDemand,Nothing} | "-" | 0 - livestock::Union{NonIrrigationDemand,Nothing} | "-" | 0 - industry::Union{NonIrrigationDemand,Nothing} | "-" | 0 - allocation::Union{AllocationLand,Nothing} | "-" | 0 + paddy::P | "-" | 0 + nonpaddy::NP | "-" | 0 + domestic::D | "-" | 0 + livestock::L | "-" | 0 + industry::I | "-" | 0 + allocation::A | "-" | 0 - function SBM{T,N,M}(args...) where {T,N,M} + function SBM{T,N,M,P,NP,D,L,I,A}(args...) where {T,N,M,P,NP,D,L,I,A} equal_size_vectors(args) return new(args...) end @@ -1327,19 +1337,16 @@ function update_total_water_storage( river_routing, land_routing, ) - # Get length active river cells - nriv = length(river_network) - # Set the total storage to zero fill!(sbm.total_storage, 0) # Burn the river routing values - sbm.total_storage[river_network] = ( - ( - river_routing.h_av[1:nriv] .* river_routing.width[1:nriv] .* - river_routing.dl[1:nriv] - ) ./ (area[river_network]) * 1000 # Convert to mm - ) + for (i, index_river) in enumerate(river_network) + sbm.total_storage[index_river] = ( + (river_routing.h_av[i] * river_routing.width[i] * river_routing.dl[i]) / + (area[index_river]) * 1000 # Convert to mm + ) + end # Chunk the data for parallel computing threaded_foreach(1:sbm.n, basesize = 1000) do i From f07446f303668cc1e83cd1f69333e7101b62d8a7 Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Tue, 27 Aug 2024 10:11:33 +0200 Subject: [PATCH 07/27] Bump version --- Project.toml | 2 +- build/wflow_cli/Setup/Installer/Application.Setup.wixproj | 2 +- docs/src/changelog.md | 2 +- server/Manifest.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index d6a591b37..ac49bd9a5 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Wflow" uuid = "d48b7d99-76e7-47ae-b1d5-ff0c1cf9a818" authors = ["Deltares and contributors"] -version = "0.8.0" +version = "0.8.1" [deps] BasicModelInterface = "59605e27-edc0-445a-b93d-c09a3a50b330" diff --git a/build/wflow_cli/Setup/Installer/Application.Setup.wixproj b/build/wflow_cli/Setup/Installer/Application.Setup.wixproj index 77abb9a1b..d7c6c0b29 100644 --- a/build/wflow_cli/Setup/Installer/Application.Setup.wixproj +++ b/build/wflow_cli/Setup/Installer/Application.Setup.wixproj @@ -6,7 +6,7 @@ 3.10 {39da8083-e405-42e6-850c-d25685e91f81} 2.0 - 0.8.0 + 0.8.1 Package false diff --git a/docs/src/changelog.md b/docs/src/changelog.md index 898ac5f9e..de9f4cffb 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## v0.8.1 - 2024-08-27 ### Fixed - Reduce allocations in update of vertical `SBM` concept. diff --git a/server/Manifest.toml b/server/Manifest.toml index 456d646af..a7b1a202c 100644 --- a/server/Manifest.toml +++ b/server/Manifest.toml @@ -660,7 +660,7 @@ version = "0.21.70" deps = ["BasicModelInterface", "CFTime", "Dates", "DelimitedFiles", "FieldMetadata", "Glob", "Graphs", "IfElse", "LoggingExtras", "LoopVectorization", "NCDatasets", "Parameters", "Polyester", "ProgressLogging", "Random", "StaticArrays", "Statistics", "TOML", "TerminalLoggers", "UnPack"] path = ".." uuid = "d48b7d99-76e7-47ae-b1d5-ff0c1cf9a818" -version = "0.8.0" +version = "0.8.1" [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] From 1e6cda717577b4cdc3c215bd253cc7969047eca7 Mon Sep 17 00:00:00 2001 From: Peter <46059512+petershintech@users.noreply.github.com> Date: Wed, 4 Sep 2024 03:00:29 +1000 Subject: [PATCH 08/27] Editorial review on user case doc (#456) * Editorial review on user case doc * Update docs/src/intro/use_cases.md Co-authored-by: Willem van Verseveld * Update docs/src/intro/use_cases.md Co-authored-by: Willem van Verseveld * Update docs/src/intro/use_cases.md Co-authored-by: Willem van Verseveld * Update docs/src/intro/use_cases.md Co-authored-by: Willem van Verseveld * Update docs/src/intro/use_cases.md Co-authored-by: Willem van Verseveld * Update docs/src/intro/use_cases.md Co-authored-by: Willem van Verseveld * Update docs/src/intro/use_cases.md Co-authored-by: Willem van Verseveld * Update docs/src/intro/use_cases.md Co-authored-by: Willem van Verseveld --------- Co-authored-by: Peter Shin Co-authored-by: Willem van Verseveld --- docs/src/intro/use_cases.md | 56 ++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/src/intro/use_cases.md b/docs/src/intro/use_cases.md index bce129657..196c7ff66 100644 --- a/docs/src/intro/use_cases.md +++ b/docs/src/intro/use_cases.md @@ -2,16 +2,16 @@ ## [Wflow models for the Meuse and Rhine](@id case_rws) -Reliable hydrological models for the Rhine and the Meuse river basin are necessary for +Reliable hydrological models for the Rhine and the Meuse river basins are necessary for short-term forecasting of river flows and long-term predictions for strategic water -management planning. In collaboration with Rijkswaterstaat, Deltares is setting up a new +management planning. In collaboration with Rijkswaterstaat, Deltares is developing a new line of models for the Rhine and the Meuse basins. The models will be used for forecasting and to estimate the impact of climate change on water resources and extreme streamflow. In -the model development, we seek to improve hydrological predictions by including relevant +the model development, we aim to improve hydrological predictions by including relevant processes in the model schematization. The modularity of the wflow framework is ideal for this as we can easily evaluate the combination of different vertical and lateral model components. For example, the local inertial routing for river and overland flow enables us -to consider retention of water in the floodplains, which will likely improve extreme +to consider retention of water in the floodplains, which is likely to improve extreme streamflow predictions. ![fig_case_rws](../images/case_rhine_meuse.png) @@ -20,26 +20,26 @@ streamflow predictions. In Australia, there was a need for high-resolution, fast and accurate rainfall-runoff models to provide boundary conditions for a fast and detailed flood inundation model (SFINCS). The -domain of the flood model covers the complete North and East Coast of Australia. Although +domain of the flood model covers the entire North and East Coast of Australia. Although many gauging stations are available to provide real-time information, many rivers are not covered. For these locations, wflow\_sbm models are used to provide this real-time information. Additionally, these models are used to provide projections for potential future scenarios. Using the HydroMT library, all wflow\_sbm models were automatically built. The -high level of flexibility in spatial and temporal resolution, and the physics-based nature -of the concept makes wflow\_sbm a very suitable model for ungauged basins. Furthermore, the -model is sufficiently detailed and computationally efficient for a coupling with the fast +high level of flexibility in spatial and temporal resolution, combined with the physics-based nature +of the concept, makes Wflow\_sbm particularly suitable for ungauged basins. Furthermore, the +model is detailed and computationally efficient enough for coupling with the fast flood inundation model SFINCS. ![fig_case_flifs](../images/case_flifs_1.png) -The results of this Proof of Concept are very promising. Technically, we were able to very -quickly setup the wflow\_sbm models, couple them to the flood inundation models (SFINCS) and -run the models operationally under the Delft-FEWS platform. Model validation was carried out -for two basins by comparing the results of wflow\_sbm against the observations and the -results of calibrated URBS models. This validation showed that the uncalibrated wflow\_sbm -model results were already quite satisfying, especially given the complex nature of these -basins, including several small and big reservoirs. We could also demonstrate the potential -for further calibration by changing the KsatHorFrac parameter. +The results of this proof of concept are very promising. Technically, we were able to +quickly set up the wflow\_sbm models, couple them to the flood inundation models (SFINCS), and +run the models operationally under the Delft-FEWS platform. Model validation was conducted +for two basins by comparing the results of Wflow\_sbm against observations and the +results of calibrated URBS models. This validation demonstrated that the uncalibrated Wflow\_sbm +model results were already quite satisfactory, especially given the complex nature of these +basins, which include several small and large reservoirs. We could also show the potential +for further calibration by adjusting the KsatHorFrac parameter. Reference: De Kleermaeker, S., Leijnse, T., Morales, Y., Druery, C., Maguire, S., 2022. Developing a real-time data and modelling framework for operational flood inundation @@ -51,30 +51,30 @@ Reference: De Kleermaeker, S., Leijnse, T., Morales, Y., Druery, C., Maguire, S. ## [Simulating plastic transport in Thailand](@id case_mfa) -For the Polution Control Board of the Government of Thailand and the World Bank, we -supported the material flow analysis of plastics in Thailand using wflow. Plastic polution -is a growing global problem. Plastic waste enters the rivers and is transported to the ocean -where it remains and threatens the health of the ocean, seas and coasts. The initial +For the Pollution Control Board of the Government of Thailand and the World Bank, we +supported a material flow analysis of plastics in Thailand using wflow. Plastic pollution +is a growing global issue. Plastic waste enters rivers and is transported to the ocean +where it persists and threatens the health of the ocean, seas and coasts. The initial movement of plastic waste is in many cases triggered by runoff from (heavy) rainfall and -transported by the flow of water towards small streams and rivers. Therefore there is strong +transported by water flow towards small streams and rivers. Therefore there is strong relation to rainfall-runoff processes, which can be modeled using high-resolution rainfall-runoff models. In this study we applied the wflow\_sbm model in combination with a fate-and-transport and -water quality model (DelWaq) to simulate the movement of plastics through 5 large river -basins and on 3 island and coastal zones (Krabi, Phuket and Ko Samui, see screenshot of the +water quality model (DelWaq) to simulate the movement of plastics through five large river +basins and on three island and coastal zones (Krabi, Phuket, and Ko Samui; see screenshot of the model below) in Thailand. Together with our partners Panya Consultants and HII, we were able -to show hotspots of plastic polution, how much plastic waste would end up in the Gulf of -Thailand and what would be priority areas for reducing plastic waste reaching the sea. +to identify hotspots of plastic pollution, estimate how much plastic waste would end up in the Gulf of +Thailand and recommend priority areas for reducing plastic waste reaching the sea. ![fig_case_mfa](../images/case_mfa_1.png) -The wflow\_sbm models for the 5 large basins were calibrated. The presence of large dams and -reservoirs complicated the calibration, but with the input for the operation of the dams, +The wflow\_sbm models for the five large basins were calibrated. The presence of large dams and +reservoirs complicated calibration, but with the input for the dam operation, the model performance for these basins could be largely improved. The figure below shows the calibrated model results for the Chao Phraya, just upstream of Bangkok. The input from the hydrological wflow\_sbm model was used as input for the fate and transport model to assess -how much plastic would be transported to the ocean. +the amount of plastic transported to the ocean. ![fig_case_mfa](../images/case_mfa_3.png) From 2477003986e242d5d170a673f187b19e81f76aa5 Mon Sep 17 00:00:00 2001 From: Peter <46059512+petershintech@users.noreply.github.com> Date: Thu, 5 Sep 2024 21:12:10 +1000 Subject: [PATCH 09/27] Editorial changes on user_guide/intro.md (#458) Co-authored-by: Peter Shin --- docs/src/user_guide/intro.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/src/user_guide/intro.md b/docs/src/user_guide/intro.md index a42634ed6..6a778cfaf 100644 --- a/docs/src/user_guide/intro.md +++ b/docs/src/user_guide/intro.md @@ -1,13 +1,13 @@ # About the user guide -The purpose of this user guide is to describe the steps to install the wflow julia software -and to set up a simple model. The user guide also contains a step-wise process how to -configure your model using the TOML file (model settings) and the netCDF gridded datasets. +The purpose of this user guide is to describe the steps for installing the wflow Julia software +and setting up a simple model. The guide also includes a step-by-step process for +configuring your model using the TOML file (model settings) and the netCDF gridded datasets. -Sample data and model set up is also included for the Moselle River Basin (a major tributary -of the Rhine River), which can be used to explore the model software. The model is set up -for the wflow\_sbm, wflow\_hbv and wflow\_sediment model concepts. +Sample data and model setup are also provided for the Moselle River Basin (a major tributary +of the Rhine River), which can be used to explore the model software. The guide covers model setups +for the `wflow\_sbm`, `wflow\_hbv` and `wflow\_sediment` model concepts. -Finally, information is provided on setting up your own model, including building a model +Finally, the guide offers information on setting up your own model, including building a model from scratch or alternatively using Deltares [HydroMT](https://github.com/Deltares/hydromt) -model building tools which are open source. +model building tools, which are open source. From 37cf476794b232a2987f5ef98afe7b5a4bdb63db Mon Sep 17 00:00:00 2001 From: Peter <46059512+petershintech@users.noreply.github.com> Date: Tue, 10 Sep 2024 17:34:43 +1000 Subject: [PATCH 10/27] Editorial changes on user guide/install.md (#460) * Editorial changes on user_guide/install.md * Rewrap text --------- Co-authored-by: Peter Shin Co-authored-by: Willem van Verseveld --- docs/src/user_guide/install.md | 95 ++++++++++++++++------------------ 1 file changed, 46 insertions(+), 49 deletions(-) diff --git a/docs/src/user_guide/install.md b/docs/src/user_guide/install.md index 9caf70983..3509b933b 100644 --- a/docs/src/user_guide/install.md +++ b/docs/src/user_guide/install.md @@ -1,13 +1,13 @@ # How to install -First download and install the [current stable release of -Julia](https://julialang.org/downloads/#current_stable_release). Please see [platform -specific instructions](https://julialang.org/downloads/platform/) for further installation -instructions and if you have trouble installing Julia. - -If you are new to Julia, it may be a good idea to check out the [Getting Started section of -the Julia Manual](https://docs.julialang.org/en/v1/manual/getting-started/). Links to other -learning resources can be found at +First, download and install the [current stable release of +Julia](https://julialang.org/downloads/#current_stable_release). If you have any issues +installing Julia, please see [platform specific +instructions](https://julialang.org/downloads/platform/) for further instructions. + +If you are new to Julia, it might be a good idea to check out the [Getting Started section +of the Julia Manual](https://docs.julialang.org/en/v1/manual/getting-started/). You can also +find additional learning resources at [julialang.org/learning](https://julialang.org/learning/). @@ -16,17 +16,17 @@ Wflow can be used in two different ways, depending on the required use of the co - If you want to stay up-to-date with the latest version, explore and modify the model code, and write your own Julia scripts around the wflow package, we recommend installing wflow as a [Julia](https://julialang.org/) package. -- If you don't need extra features, but just want to run simulations, a complied executable - version is available. This consists of a single executable, `wflow_cli`, allowing you to - run the model via the command line. +- If you don't need extra features, but just want to run simulations, a compiled executable + version is available. This version includes a single executable, `wflow_cli`, which allows + you to run the model via the command line. Below we describe how to install both versions of wflow. ## Installing as Julia package -Wflow is a [Julia](https://julialang.org/) package, that can be installed through several -different ways. Below we show how to install wflow from Julia's package repository, and -how to install the latest version from GitHub. +Wflow is a [Julia](https://julialang.org/) package that can be installed in several ways. +Below, we show how to install wflow from Julia's package repository and how to install the +latest version from GitHub. ### Install from Julia's package repository @@ -34,44 +34,44 @@ To access Julia's package manager, press `]` in the Julia REPL. To get back to REPL, press backspace or ^C. !!! tip - If you have not used Julia in a while, it can be a good idea to run `up` to update your + If you haven't used Julia in a while, it's a good idea to run `up` to update your packages. ```julia-repl pkg> up ``` -Access Julia's package manager and install wflow using: +To access Julia's package manager and install wflow, use: ```julia-repl pkg> add Wflow ``` -This can take a while, especially the first time, since compatible dependencies are also -automatically looked up and installed from the Pkg General registry. +This process can take a while, especially on the first run, as compatible dependencies are +automatically resolved and installed from the Pkg General registry. ### Install from GitHub -It is also possible to install wflow from the `master` branch as follows: +You can also install wflow from the `master` branch on the repository as follows: ```julia-repl pkg> add Wflow#master ``` -This command will track the `master` branch, and will update to the latest commit on that -branch when you run `update`, or simply `up`, in the Pkg REPL. The use of `add` will install -wflow in you home directory under `.julia/packages/Wflow`. Note that packages installed -under `packages` by `add` are supposed to never be altered in that location, for Pkg and -it's automatic dependency handling to work well. +This command tracks the `master` branch and updates to the latest commit on that branch when +you run `update`, or simply `up`, in the Pkg REPL. The `add` installs wflow in your home +directory under `.julia/packages/Wflow`. Note that packages installed under `packages` by +`add` should not be changed in the directory, as the change could disrupt Pkg's automatic +dependency handling. -If you want to make any changes to any of the files in the repository, you need to do -a development install. This can be done using: +If you want to modify any files in the repository, you need to do a development install. +This can be done using: ```julia-repl pkg> dev Wflow ``` -This will clone the git repository, put it under your home directory in `.julia/dev/Wflow`, -and add the wflow package to your project environment. Note that to receive updates, you -have to pull in the latest changes yourself using `git pull`. +This will clone the git repository, place it under your home directory in +`.julia/dev/Wflow`, and add the wflow package to your project environment. To receive +updates, you'll need to pull the latest changes manually using `git pull`. ### Check installation of wflow @@ -81,22 +81,22 @@ Finally, go back to the Julia REPL and try to load wflow: julia> using Wflow ``` -The first time this will take longer as any package that is new or changed needs to be -pre-compiled first, to allow faster loading on subsequent uses. No error messages should -appear, indicating that you have now successfully installed wflow. +The first time you do this, it may take longer as any new or changed packages need to be +precompiled to enable faster loading on subsequent uses. No error messages should appear, +which indicates that you have successfully installed wflow. -Before ending this section, we want to recommend a few tools that can make using and -developing Julia code easier. +Before concluding this section, we recommend a few tools that can make using and developing +Julia code easier. !!! tip There is a section on editors and IDEs for Julia on , scroll down to see it. We use and recommend Microsoft's free and open source [Visual Studio - Code](https://code.visualstudio.com/). When combined with the [Julia + Code](https://code.visualstudio.com/). Combined with the [Julia extension](https://www.julia-vscode.org/) it provides a powerful and interactive development experience. !!! tip - When planning to make changes to the code of wflow, we recommend installing the `Revise.jl` + If you plan to modify the code of wflow, we recommend installing the `Revise.jl` package. This package allows you to modify code and use the changes without restarting Julia. Install it with `add Revise` from the Pkg REPL. Then create a file called `.julia/config/startup.jl`, and put `using Revise` there. This will load Revise every @@ -106,28 +106,25 @@ developing Julia code easier. Binaries of `wflow_cli` can be downloaded from our website [download.deltares.nl](https://download.deltares.nl/en/download/wflow/), and are currently -available for Windows. Download and install the `.msi` file. After installing you can see -two folders in the installation directory. It is only the `bin/wflow_cli` that is used. The -artifacts folder contains binary dependencies such as netCDF. +available for Windows. Download and install the `.msi` file. After installation, you will +see two folders in the installation directory. Only the `bin/wflow_cli` is used. The +`artifacts` folder contains binary dependencies such as netCDF. ``` artifacts\ bin\wflow_cli ``` -Check whether the installation was performed successfully by running `wflow_cli` with no -arguments in the command line. This will give the following message: +To verify whether the installation was completed successfully, run `wflow_cli` with no +arguments in the command line. This will display the following message: ``` Usage: wflow_cli 'path/to/config.toml' ``` -!!! note - The old version of wflow, based on Python and PCRaster libraries is also available to - download from our website [download.deltares.nl](https://download.deltares.nl/en/download/wflow/). - We recommend installing the Julia version, as this documentation is written to support - this version. - - - +!!! note + The old version of wflow, which was based on Python and PCRaster libraries, is also + available for download from our website + [download.deltares.nl](https://download.deltares.nl/en/download/wflow/). We recommend + installing the Julia version, as this documentation is written to support this version. From 1a94439e7f84f69a29d528195cdb98c65390e5e4 Mon Sep 17 00:00:00 2001 From: Peter <46059512+petershintech@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:21:15 +1000 Subject: [PATCH 11/27] Editorial changes to user_guide/step1_requirements.md (#461) * Editorial changes to user_guide/step1_req.md * Rewrap text * Fix typo and indent list model configurations --------- Co-authored-by: Peter Shin Co-authored-by: Willem van Verseveld --- docs/src/user_guide/step1_requirements.md | 53 +++++++++++------------ 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/docs/src/user_guide/step1_requirements.md b/docs/src/user_guide/step1_requirements.md index 01c0d0ce5..4e6427013 100644 --- a/docs/src/user_guide/step1_requirements.md +++ b/docs/src/user_guide/step1_requirements.md @@ -1,37 +1,36 @@ # Step 1: Understanding the requirements -In order to run wflow, several files are required. These consist of a settings file and -input data. The input data is typically separated into static maps and forcing data, both -are supplied in a netCDF file, except for lake storage and rating curves that are supplied -via CSV files. A brief overview of the different files: +To run wflow, several files are required. These include a settings file and input data. The +input data is typically separated into static maps and forcing data, and both are provided +in netCDF files, except for lake storage and rating curves that are supplied via CSV files. +Below is a brief overview of the different files: - The `settings.toml` file contains information on the simulation period, links to the - input files (and their names in the netCDF files), and links the correct names of the - variables in the netCDF files to the variables and parameters of wflow. - - The `staticmaps.nc` file contains spatial information on the elevation, locations of the - gauges, land-use, drainage direction, etc. This file can also contain maps with parameter + input files (and their names in the netCDF files), and connect the correct variable names + in the netCDF files to the variables and parameters of wflow. + - The `staticmaps.nc` file contains spatial information such as elevation, gauge locations, + land use, and drainage direction, etc. This file can also contain maps with parameter values. - - The `forcing.nc` file contains the precipitation, temperature and potential evaporation - time series (as a 3D array). + - The `forcing.nc` file contains time series data for precipitation, temperature and + potential evaporation (as a 3D array). -There are several model configurations supported by wflow. These model configurations -require slightly different input requirements, yet the general structure is similar for each -model. A wflow model configuration consists of a `vertical` concept like the [SBM](@ref -vert_sbm), [HBV](@ref vert_hbv) or [FLEXTOPO](@ref vert_flextopo) in +Wflow supports several model configurations, each requiring slightly different input, but +with a similar general structure. A wflow model configuration consists of a `vertical` +concept like [SBM](@ref vert_sbm), [HBV](@ref vert_hbv) or [FLEXTOPO](@ref vert_flextopo) in combination with `lateral` concepts that control how water is routed for example over the -land or river domain. For the wflow\_sbm model different model configurations are possible. -The following model configurations are supported in wflow: +land or river domain. For the wflow\_sbm model, different model configurations are possible. +The following configurations are supported in wflow: - - wflow\_sbm: - - SBM + kinematic wave for subsurface and surface flow - - SBM + kinematic wave for subsurface and overland flow + local inertial river (+ - optional floodplain) - - SBM + kinematic wave for subsurface flow + local inertial river (1D) and land (2D) - - SBM + groundwater flow + kinematic wave for surface flow - - wflow\_hbv: HBV + kinematic wave for surface routing - - wflow\_flextopo: FLEXTOPO + kinematic wave for surface routing - - wflow\_sediment as post processing of wflow\_sbm or wflow\_hbv output +- wflow\_sbm: + - SBM + kinematic wave for subsurface and surface flow + - SBM + kinematic wave for subsurface and overland flow + local inertial river (+ optional + floodplain) + - SBM + kinematic wave for subsurface flow + local inertial river (1D) and land (2D) + - SBM + groundwater flow + kinematic wave for surface flow +- wflow\_hbv: HBV + kinematic wave for surface routing +- wflow\_flextopo: FLEXTOPO + kinematic wave for surface routing +- wflow\_sediment as post processing of wflow\_sbm or wflow\_hbv output In the following pages, some examples will be given on how to prepare a basic wflow\_sbm -model. Example data for other model configurations is provided in the section with [sample -data](@ref sample_data). \ No newline at end of file +model. Sample data for other model configurations is provided in the [sample data](@ref +sample_data) section. \ No newline at end of file From dfd28044f09aff4cbe12e524544e0f72ba4c5fdb Mon Sep 17 00:00:00 2001 From: Peter <46059512+petershintech@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:48:46 +1000 Subject: [PATCH 12/27] Editorial changes to user case/step2_ requirements.md (#462) * Editorial changes to step2 Setting files * Rewrap text --------- Co-authored-by: Peter Shin Co-authored-by: Willem van Verseveld --- docs/src/user_guide/step2_settings_file.md | 225 ++++++++++----------- 1 file changed, 112 insertions(+), 113 deletions(-) diff --git a/docs/src/user_guide/step2_settings_file.md b/docs/src/user_guide/step2_settings_file.md index 490679524..158f3ada1 100644 --- a/docs/src/user_guide/step2_settings_file.md +++ b/docs/src/user_guide/step2_settings_file.md @@ -1,40 +1,40 @@ # [Step 2: Preparing the settings file](@id config_toml) -A settings file is essential for wflow, as it contains information on the model +A settings file is essential for wflow, as it contains information about the model configuration, simulation period, input files, and parameters. The settings are provided in a TOML file. The settings file is structured in several sections, which are explained below. -The filepaths that are provided in this file are relative to the location of the TOML file, -or to `dir_input` and `dir_output` if they are given. +The file paths provided in this file are relative to the location of the TOML file, or to +`dir_input` and `dir_output` if they are specified. ## General time info -Time information is optional. When left out, for each timestamp in the forcing netCDF wflow -will do computations, except for the first forcing timestamp that is considered equal to the -initial conditions of the wflow model (state time). If you wish to calculate a subset of -this time range, or a different timestep, you can specify a `starttime`, `endtime` and -`timestepsecs` yourself. The `starttime` is defined as the model state time. In the TOML -file settings below the `starttime` is 2000-01-01T00:00:00 (state time) and the first update -(and output) of the wflow model is at 2000-01-02T00:00:00. The `time_units` optional -information is used by the `writer` of the model, for model output in netCDF format. The -`calendar` option allows you to calculate in one of the different [CF conventions -calendars](http://cfconventions.org/cf-conventions/cf-conventions.html#calendar) provided by -the [CFTime.jl package](https://juliageo.org/CFTime.jl/latest/), such as `"360_day"`. This -is useful if you want to calculate climate scenarios which are sometimes provided in these -alternative calendars. +Time information is optional. When omitted, wflow will perform computations for each +timestamp in the forcing netCDF file, except for the first forcing timestamp, which is +considered equal to the initial conditions of the wflow model (state time). If you wish to +calculate a subset of this time range, or a different timestep, you can specify a +`starttime`, `endtime` and `timestepsecs`. The `starttime` is defined as the model state +time. In the TOML file settings below, the `starttime` is 2000-01-01T00:00:00 (state time) +and the first update (and output) of the wflow model is at 2000-01-02T00:00:00. The +`time_units` optional information is used by the `writer` of the model, for model output in +netCDF format. The `calendar` option allows you to calculate in one of the different [CF +conventions calendars](http://cfconventions.org/cf-conventions/cf-conventions.html#calendar) +provided by the [CFTime.jl package](https://juliageo.org/CFTime.jl/latest/), such as +`"360_day"`. This is useful if you want to calculate climate scenarios which are sometimes +provided in these alternative calendars. ```toml -calendar = "standard" # optional, this is default value +calendar = "standard" # optional, this is the default value starttime = 2000-01-01T00:00:00 # optional, default from forcing netCDF endtime = 2000-02-01T00:00:00 # optional, default from forcing netCDF -time_units = "days since 1900-01-01 00:00:00" # optional, this is default value +time_units = "days since 1900-01-01 00:00:00" # optional, this is the default value timestepsecs = 86400 # optional, default from forcing netCDF dir_input = "data/input" # optional, default is the path of the TOML dir_output = "data/output" # optional, default is the path of the TOML ``` ## [Logging](@id logging_toml) -Wflow emits logging messages at various levels such as debug, info, and error. These get -sent to both the terminal as well as a log file. Note that logging to a file is only part of -the `Wflow.run(tomlpath::AbstractString)` method. If you want to debug an issue it can be -helpful to set `loglevel = "debug"` in the TOML. To avoid flooding the screen, debug +Wflow prints logging messages at various levels such as debug, info, and error. These +messages are sent to both the terminal and a log file. Note that logging to a file is only +part of the `Wflow.run(tomlpath::AbstractString)` method. If you want to debug an issue, it +can be helpful to set `loglevel = "debug"` in the TOML. To avoid flooding the screen, debug messages are only sent to the log file. The following settings will affect the logging: ```toml @@ -44,15 +44,16 @@ path_log = "log.txt" # optional, default is "log.txt" fews_run = false # optional, default value is false ``` -`silent` avoids logging to the terminal, and only writes the log file. `loglevel` controls -which levels are filtered out, so the default setting `"info"` does not show any debug level -messages. Note that for finer control, you can also pass an integer log level, see Julia's +`silent` avoids logging to the terminal, and only writes to the log file. `loglevel` +controls which levels are filtered out; for instance, the default setting `"info"` does not +print any debug-level messages. Note that for finer control, you can also pass an integer +log level. For details, see Julia's [Logging](https://docs.julialang.org/en/v1/stdlib/Logging/#Log-event-structure) -documentation. `path_log` sets the desired output path for the log file. For information -regarding `fews_run`, see [Run from Delft-FEWS](@ref run_fews). +documentation. `path_log` sets the desired output path for the log file. For information on +`fews_run`, see [Run from Delft-FEWS](@ref run_fews). ## Model section -Model specific settings can be included in the model section of the TOML file. +Model-specific settings can be included in the model section of the TOML file. ```toml [model] @@ -62,7 +63,7 @@ snow = false # include snow modelling, default is false reinit = true # cold (reinit = true) or warm state (reinit = false), default is true reservoirs = false # include reservoir modelling, default is false kin_wave_iteration = false # enable kinematic wave iterations in the model, default is false -thicknesslayers = [100, 300, 800] # specific SBM setting: for each soil layer a thickness [mm] is specified +thicknesslayers = [100, 300, 800] # specific SBM setting: for each soil layer, a thickness [mm] is specified min_streamorder_river = 5 # minimum stream order to delineate subbasins for river domain, default is 6 (for multi-threading computing purposes) min_streamorder_land = 4 # minimum stream order to delineate subbasins for land domain, default is 5 (for multi-threading computing purposes) @@ -70,13 +71,12 @@ min_streamorder_land = 4 # minimum stream order to delineate subbasin ## State options The `state` section in the TOML file provides information about the location of input and -output states of the model. This section is mostly relevant if the model needs to be started -with a "warm" state (i.e. based on the results of a previous simulation). The example below -shows how to save the output states of the current simulation, so it can be used to -initialize another model in the future. Details on the settings required to start a model -with a warm state can be found in the [additional model options](@ref reinit). If it is not -required to store the outstates of the current simulation, the entire `state` section can be -removed. +output states of the model. This section is mostly relevant if the model needs to start with +a "warm" state (i.e. based on the results of a previous simulation). The example below shows +how to save the output states of the current simulation, so it can be used to initialize +another model in the future. Details on the settings required to start a model with a warm +state can be found in the [additional model options](@ref reinit). If it is not required to +store the outstates of the current simulation, the entire `state` section can be removed. ```toml [state] @@ -110,16 +110,16 @@ h_av = "h_av_land" ## Input section The `input` section of the TOML file contains information about the input forcing and model -parameters files (netCDF format). Forcing is applied to the vertical component of the model, -and needs to be mapped to the external netCDF variable name. `forcing` lists the internal -model forcing parameters, and these are mapped to the external netCDF variables listed under -the section `[input.vertical]`. It is possible to provide cyclic parameters to the model -(minimum time step of 1 day). In the example below this is done for the internal -`vertical.leaf_area_index` model parameter, that is linked to the external netCDF variable -"LAI" variable. Cyclic time inputs of parameters can be different (for example daily and -monthly). The `time` dimension name of these cylic input parameters in the model parameter -netCDF file should start with "time". If a model parameter is not mapped, a default value -will be used if available. +parameters files (in netCDF format). Forcing is applied to the vertical component of the +model, and needs to be mapped to the external netCDF variable name. `forcing` lists the +internal model forcing parameters, and these are mapped to the external netCDF variables +listed under the section `[input.vertical]`. It is possible to provide cyclic parameters to +the model (minimum time step of 1 day). In the example below, the internal +`vertical.leaf_area_index` model parameter is mapped to the external netCDF variable "LAI" +variable. Cyclic time inputs of parameters can be different (e.g., daily or monthly). The +`time` dimension name of these cylic input parameters in the model parameter netCDF file +should start with "time". If a model parameter is not mapped, a default value will be used, +if available. ```toml [input] @@ -143,7 +143,7 @@ forcing = [ cyclic = ["vertical.leaf_area_index"] -[input.vertical] # Map internal model variable/parameter names to names of the variables in the netCDF files +[input.vertical] # Map internal model variable/parameter names to variable names in the netCDF files altitude = "wflow_dem" c = "c" cf_soil = "cf_soil" @@ -192,23 +192,22 @@ slope = "Slope" ## Output netCDF section ### Grid data -This optional section of the TOML file contains the output netCDF file for writing gridded -model output, including a mapping between internal model parameter components and external +This optional section of the TOML file specifies the output netCDF file for writing gridded +model output. It includes a mapping between internal model parameter components and external netCDF variables. -To limit the size of the resulting netCDF file, file compression can be enabled. This causes -an increase in computational time, but can significantly reduce the file size of the netCDF -file. This can be enabled by setting the `compressionlevel` variable to any value between -`0` and `9`. A setting of `0` indicates that compression is not enabled, and values between -1 and 9 indicate different levels of compression (1: least compression, smallest impact on -run time, 9: highest compression level, biggest impact on run times). If file size becomes -an issue, we recommend using a value of `1`, as higher compression levels generally have -only a limited effect on the file size. +To limit the size of the resulting netCDF file, file compression can be enabled. Compression +increases computational time but can significantly reduce the size of the netCDF file. Set +the `compressionlevel` variable to a value between `0` and `9`. A setting of `0` means no +compression, while values between 1 and 9 indicate increasing levels of compression (1: +least compression, minimal run-time impact, 9: highest compression, maximum run-time +impact). If file size is a concern, we recommend using a value of `1`, as higher compression +levels generally have a limited effect on file size. ```toml [output] path = "output_moselle.nc" # Location of the output file -compressionlevel = 1 # Amount of compression (default 0) +compressionlevel = 1 # Compression level (default 0) [output.vertical] # Mapping of names between internal model components and external netCDF variables satwaterdepth = "satwaterdepth" @@ -234,36 +233,36 @@ h = "h_land" ``` ### Scalar data -Besides gridded data, it is also possible to write scalar data to a netCDF file. Below is an -example that writes scalar data to the file "output\_scalar\_moselle.nc". For each netCDF -variable a `name` (external variable name) and `parameter` (internal model parameter) is -required. A `reducer` can be specified to apply to the model output, see for more -information the following section [Output CSV section](@ref). When a `map` is provided to -extract data for certain locations (e.g. `gauges`) or areas (e.g. `subcatchment`), the -netCDF location names are extracted from these maps. For a specific location (grid cell) a -`location` is required. For layered model parameters and variables that have an extra -dimension `layer` and are part of the vertical `sbm` concept it is possible to specify an -internal layer index (see also example below). For model parameters and variables that have -an extra dimension `classes` and are part of the vertical `FLEXTopo` concept it is possible -to specify the class name. If multiple layers or classes are desired, this can be specified -in separate `[[netcdf.variable]]` entries. Note that the specification of the extra -dimension is not optional when wflow is integrated with Delft-FEWS, for netCDF scalar data -an extra dimension is not allowed by the `importNetcdfActivity` of the Delft-FEWS General -Adapter. In the section [Output CSV section](@ref), similar functionality is available for -CSV. For integration with Delft-FEWS, see also [Run from Delft-FEWS](@ref run_fews), it is -recommended to write scalar data to netCDF format since the General Adapter of Delft-FEWS -can ingest this data format directly. +In addition to gridded data, scalar data can also be written to a netCDF file. Below is an +example that shows how to write scalar data to the file "output\_scalar\_moselle.nc". For +each netCDF variable, a `name` (external variable name) and a `parameter` (internal model +parameter) are required. A `reducer` can be specified to apply to the model output. See more +details in the [Output CSV section](@ref) section. If a `map` is provided to extract data +for specific locations (e.g. `gauges`) or areas (e.g. `subcatchment`), the netCDF location +names are extracted from these maps. For a specific location (grid cell) a `location` is +required. For layered model parameters and variables that have an extra `layer` dimension +and are part of the vertical `sbm` concept, an internal layer index can be specified (an +example is provided below). Similarly, for model parameters and variables that have an extra +dimension `classes` and are part of the vertical `FLEXTopo` concept, the class name can be +specified. If multiple layers or classes are desired, this can be specified in separate +`[[netcdf.variable]]` entries. Note that the additional dimension should be specified when +wflow is integrated with Delft-FEWS, for netCDF scalar data an extra dimension is not +allowed by the `importNetcdfActivity` of the Delft-FEWS General Adapter. In the section +[Output CSV section](@ref), similar functionality is available for CSV. For integration with +Delft-FEWS, it is recommended to write scalar data to netCDF format, as the General Adapter +of Delft-FEWS can directly ingest data from netCDF files. For more information, see [Run +from Delft-FEWS](@ref run_fews). ```toml [netcdf] path = "output_scalar_moselle.nc" # Location of the results -[[netcdf.variable]] # Extract the values of lateral.river.q using the gauges map, and assigning it with the name 'Q' as variable to the netCDF +[[netcdf.variable]] # Extract the values of lateral.river.q using the gauges map, and assign it with the name 'Q' as a variable in the netCDF file name = "Q" map = "gauges" parameter = "lateral.river.q" -[[netcdf.variable]] # Using coordinates to extract the temperature +[[netcdf.variable]] # Using coordinates to extract temperature coordinate.x = 6.255 coordinate.y = 50.012 name = "vwc_layer2_bycoord" @@ -271,7 +270,7 @@ location = "vwc_bycoord" parameter = "vertical.vwc" layer = 2 -[[netcdf.variable]] # Using indices to extract the temperature +[[netcdf.variable]] # Using indices to extract temperature location = "temp_byindex" name = "temp_index" index.x = 100 @@ -280,9 +279,9 @@ parameter = "vertical.temperature" ``` ## Output CSV section -Model output can also be written to CSV output. Below is an example that writes model output -to the file "output_moselle.csv". For each CSV column a `header` and `parameter` (internal -model parameter) is required. A `reducer` can be specified to apply to the model output, +Model output can also be written to a CSV file. Below is an example that writes model output +to the file "output_moselle.csv". For each CSV column, a `header` and `parameter` (internal +model parameter) are required. A `reducer` can be specified to apply to the model output, with the following available reducers: + maximum @@ -296,18 +295,19 @@ with the following available reducers: with `only` as the default. To extract data for a specific location (grid cell), the `index` of the vector, the coordinates `coordinate.x` and `coordinate.y`, or the x and y indices of -the 2D array (`index.x` and `index.y`) can be provided. Finally a `map` can be provided to -extract data for certain locations (e.g. `gauges`) or areas (e.g. `subcatchment`). In this -case a single entry can lead to multiple columns in the CSV file, which will be of the form -`header_id`, e.g. `Q_20`, for a gauge with integer ID 20. For layered model parameters and -variables that have an extra dimension `layer` and are part of the vertical `sbm` concept an -internal layer index (see also example below) should be specified. For model parameters and -variables that have an extra dimension `classes` and are part of the vertical `FLEXTopo` -concept it is possible to specify the class name. If multiple layers or classes are desired, -this can be specified in separate `[[csv.column]]` entries. - -The double brackets in `[[csv.column]]` is TOML syntax to indicate that it is part of a -list. You may specify as many entries as you wish. +the 2D array (`index.x` and `index.y`) can be provided. Additionally, a `map` can be +provided to extract data for certain locations (e.g. `gauges`) or areas (e.g. +`subcatchment`). In this case, a single entry can lead to multiple columns in the CSV file, +which will be of the form `header_id`, e.g. `Q_20`, for a gauge with integer ID 20. For +layered model parameters and variables that have an extra dimension `layer` and are part of +the vertical `sbm` concept an internal layer index (see also example below) should be +specified. For model parameters and variables that have an extra dimension `classes` and are +part of the vertical `FLEXTopo` concept, it is possible to specify the class name. If +multiple layers or classes are desired, this can be specified in separate `[[csv.column]]` +entries. + +The double brackets in `[[csv.column]]` follow TOML syntax, indicating that it is part of a +list. You can specify as many entries as you want. ```toml [csv] @@ -358,11 +358,11 @@ reducer = "mean" It is possible to modify model parameters and forcing through the TOML file. Two options to modify input parameters are available: -- Set an input parameter (static) to an uniform value. -- Modify an input parameter (cyclic and static) or forcing variable through the use of a - `scale` factor and `offset`. +- Set an input parameter (static) to a uniform value. +- Modify an input parameter (cyclic and static) or forcing variable using a `scale` factor + and `offset`. -To set for example the input parameter `cfmax` to an uniform value of 2.5: +For example, to set the input parameter `cfmax` to an uniform value of 2.5: ```toml [input.vertical] @@ -371,10 +371,9 @@ waterfrac = "WaterFrac" cfmax.value = 2.5 ``` -For input parameters with an extra dimension (e.g. `layer` or `classes`) one uniform value -can be provided or a list of values that should be equal to the length of the extra -dimension. For example, for input parameter `c`, a list of values can be provided as -follows: +For input parameters with an extra dimension (e.g. `layer` or `classes`), one uniform value +can be provided or a list of values that matches the length of the additional dimension. For +example, a list of values can be provided for input parameter `c` as follows: ```toml [input.vertical] @@ -383,7 +382,7 @@ waterfrac = "WaterFrac" c.value = [10.5, 11.25, 9.5, 7.0] ``` -To change for example the forcing variable `precipitation` with a `scale` factor of 1.5 and +To change the forcing variable `precipitation` with a `scale` factor of 1.5 and an `offset` of 0.5: ```toml @@ -393,10 +392,10 @@ scale = 1.5 offset = 0.5 ``` -For input parameters with an extra dimension it is also possible to modify multiple indices -at once with different `scale` and `offset` values. In the example below the external -netCDF variable `c` is modified at `layer` index 1 and 2, with a `scale` factor of 2.0 and -1.5 respectively, and an `offset` of 0.0 for both indices: +For input parameters with an extra dimension, it is also possible to modify multiple indices +simultaneously with different `scale` and `offset` values. In the example below, the +external netCDF variable `c` is modified at `layer` index 1 and 2, with a `scale` factor of +2.0 and 1.5 respectively, and an `offset` of 0.0 for both indices: ```toml [input.vertical.c] @@ -407,9 +406,9 @@ layer = [1, 2] ``` ## Fixed forcing values -It is possible to set fixed values for forcing parameters through the TOML file. To set for -example `temperature` to a fixed value of 10 ``\degree``C, the complete `forcing` list is -required: +It is possible to set fixed values for forcing parameters through the TOML file. For +example, to set `temperature` to a fixed value of 10 ``\degree``C, the complete `forcing` +list is required: ```toml forcing = [ @@ -422,8 +421,8 @@ forcing = [ value = 10 ``` -Note that the mapping to the external netCDF variable listed under the section -`[input.vertical]` needs to be removed or commented out: +Note that the mapping to the external netCDF variable listed under the `[input.vertical]` +section needs to be removed or commented out: ```toml [input.vertical] From 6a2f4bb311965edc4da35021d39f665585264fe7 Mon Sep 17 00:00:00 2001 From: aweerts Date: Wed, 18 Sep 2024 11:56:39 +0200 Subject: [PATCH 13/27] Update publications.md (#466) * Update publications.md * Rewrap text --------- Co-authored-by: Willem van Verseveld --- docs/src/intro/publications.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/intro/publications.md b/docs/src/intro/publications.md index aea1353b1..9a1385621 100644 --- a/docs/src/intro/publications.md +++ b/docs/src/intro/publications.md @@ -53,6 +53,11 @@ to improve hydrological assessment and snow water equivalent estimation in a sub watershed. Hydrol. Earth Syst. Sci., 22, 4685–4697. . +Droppers, B., Rakovec, O., Avila, L., Azimi, S., Cortés-Torres N., De León Pérez, D., +Imhoff, R., Francés, F., Kollet, S., Rigon, R., Weerts, A. & Samaniego, L, 2024. Multi-model +hydrological reference dataset over continental Europe and an African basin. Sci Data, 11, +1009\. . + Emerton, R.E., Stephens, E.M., Pappenberger, F., Pagano, T.C., Weerts, A.H., Wood, A.W., Salamon, P., Brown, J.D., Hjerdt, N., Donnelly, C., Baugh, C.A., Cloke, H.L., 2016. Continental and global scale flood forecasting systems. WIREs Water 3, 391–418. From b2fea671220d59053dace729e2142d482a7dc7d9 Mon Sep 17 00:00:00 2001 From: aweerts Date: Tue, 24 Sep 2024 09:09:36 +0200 Subject: [PATCH 14/27] Update publications.md (#467) * Update publications.md * Rewrap text and fix typo --------- Co-authored-by: Willem van Verseveld --- docs/src/intro/publications.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/src/intro/publications.md b/docs/src/intro/publications.md index 9a1385621..737b45edb 100644 --- a/docs/src/intro/publications.md +++ b/docs/src/intro/publications.md @@ -243,6 +243,11 @@ Tretjakova, D., 2015. Investigating the effect of using fully-distributed model assimilation on the performance of hydrological forecasting in the Karasu catchment, Turkey, MSc thesis, Wageningen University. +van der Gaast, R.H., 2024. Evaluating the transferability of data-driven pedo-transfer +functions for the wflow\_sbm parameter KsatHorFrac in central and Western Europe. +Universiteit Twente, Enschede, The Netherlands, +. + Verbrugge, M., 2019. Reservoir Operation Optimization, a case study in the Chao Phraya Basin, BSc thesis, Hydrology and Quantitative Water Management Group, Wageningen University. @@ -256,8 +261,8 @@ Wageningen University. Visser, B., 2020. Impact of climate change on local water resources of European catchments, Intersnhip report, Deltares. -Wannasin, C., 2023. Modelling and forecasting daily streamflow with reservoir operation in the -upper Chao Phraya River basin, Thailand. +Wannasin, C., 2023. Modelling and forecasting daily streamflow with reservoir operation in +the upper Chao Phraya River basin, Thailand. Wageningen University, Doctoral dissertation, . ### Reports From eef74e09aceb3f089553a264e409ee414669971e Mon Sep 17 00:00:00 2001 From: aweerts Date: Tue, 24 Sep 2024 09:10:17 +0200 Subject: [PATCH 15/27] Update publications.md (#468) * Update publications.md * Rewrap text and fix typos --------- Co-authored-by: Willem van Verseveld --- docs/src/intro/publications.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/src/intro/publications.md b/docs/src/intro/publications.md index 737b45edb..5583fd7db 100644 --- a/docs/src/intro/publications.md +++ b/docs/src/intro/publications.md @@ -86,10 +86,15 @@ response to land use and land cover changes using satellite data and hydrologica case study of Dinder and Rahad tributaries of the Blue Nile (Ethiopia–Sudan), Hydrol. Earth Syst. Sci., 21, 5217–5242. . +Imhoff, R.O., Buitink, J., van Verseveld, W.J., Weerts, A.H., 2024. A fast high resolution +distributed hydrological model for forecasting, climate scenarios and digital twin +applications using wflow_sbm. Environmental Modelling & Software, 179, 106099. + + Imhoff, R.O, van Verseveld, W.J., van Osnabrugge, B., Weerts, A.H., 2020. Scaling Point-Scale (Pedo)transfer Functions to Seamless Large-Domain Parameter Estimates for High-Resolution Distributed Hydrologic Modeling: An Example for the Rhine River. Water -Resources Research,56,e2019WR026807. . +Resources Research, 56, e2019WR026807. . Imhoff, R.O., van Verseveld, W., van Osnabrugge, B., Weerts, A.H., 2020. Ruimtelijk schaalbare hydrologische modelparameters uit open-source omgevingsdata: een voorbeeld voor From 78d6bc0111fe979e2c6dcc0091f2f86d8c5ce111 Mon Sep 17 00:00:00 2001 From: aweerts Date: Tue, 1 Oct 2024 13:37:19 +0200 Subject: [PATCH 16/27] Update publications.md (#469) * Update publications.md * Rewrap text and avoid list creation --------- Co-authored-by: Willem van Verseveld --- docs/src/intro/publications.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/intro/publications.md b/docs/src/intro/publications.md index 5583fd7db..15db4a328 100644 --- a/docs/src/intro/publications.md +++ b/docs/src/intro/publications.md @@ -149,6 +149,11 @@ T., van den Oord, G., Weerts, A., Whan, K., 2020. The role of spatial and tempor resolution in a flood event storyline approach in western Norway. Weather and Climate Extremes, doi: . +Seizarwati, W. and M. Syahidah, 2021. Rainfall-Runoff Simulation for Water Availability +Estimation in Small Island Using Distributed Hydrological Model wflow. IOP Conf. Ser.: +Earth Environ. Sci., 930,012050, doi:10.1088/1755-1315/930/1/012050. + + Sperna Weiland, F.C., R.D. Visser, P. Greve, B. Bisselink, L. Brunner and A.H. Weerts, 2021\. Estimating Regionalized Hydrological Impacts of Climate Change Over Europe by Performance-Based Weighting of CORDEX Projections, Frontiers of Water, From 59f5ef6653ca0952dfdde03978781455285f2029 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Wed, 2 Oct 2024 09:35:34 +0200 Subject: [PATCH 17/27] Update julia to 1.10.5, update manifests (#471) * Update julia to 1.10.5, update manifests * Disable persistent tasks check Only fails on Windows --- Manifest.toml | 48 +- build/create_binaries/Manifest.toml | 50 +- build/create_binaries/download_test_data.jl | 5 +- build/wflow_cli/Manifest.toml | 50 +- pixi.lock | 529 +++++++++++--------- pixi.toml | 2 +- server/Manifest.toml | 56 +-- test/runtests.jl | 2 +- 8 files changed, 386 insertions(+), 356 deletions(-) diff --git a/Manifest.toml b/Manifest.toml index 0f3053ecd..a72a3f92b 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -1,6 +1,6 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.10.0" +julia_version = "1.10.5" manifest_format = "2.0" project_hash = "acf9296ffbaffd3f335bdb6005badef66b3e10fe" @@ -31,9 +31,9 @@ version = "0.4.0" [[deps.ArrayInterface]] deps = ["Adapt", "LinearAlgebra"] -git-tree-sha1 = "f54c23a5d304fb87110de62bace7777d59088c34" +git-tree-sha1 = "3640d077b6dafd64ceb8fd5c1ec76f7ca53bcf76" uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" -version = "7.15.0" +version = "7.16.0" [deps.ArrayInterface.extensions] ArrayInterfaceBandedMatricesExt = "BandedMatrices" @@ -130,7 +130,7 @@ weakdeps = ["Dates", "LinearAlgebra"] [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" -version = "1.0.5+1" +version = "1.1.1+0" [[deps.CpuId]] deps = ["Markdown"] @@ -201,9 +201,9 @@ version = "3.8.4+0" [[deps.Graphs]] deps = ["ArnoldiMethod", "Compat", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] -git-tree-sha1 = "ebd18c326fa6cee1efb7da9a3b45cf69da2ed4d9" +git-tree-sha1 = "1dc470db8b1131cfc7fb4c115de89fe391b9e780" uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" -version = "1.11.2" +version = "1.12.0" [[deps.HDF5_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "libaec_jll"] @@ -219,9 +219,9 @@ version = "0.1.17" [[deps.Hwloc_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "5e19e1e4fa3e71b774ce746274364aef0234634e" +git-tree-sha1 = "dd3b49277ec2bb2c6b94eb1604d4d0616016f7a6" uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8" -version = "2.11.1+0" +version = "2.11.2+0" [[deps.IfElse]] git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1" @@ -239,9 +239,9 @@ uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" [[deps.JLLWrappers]] deps = ["Artifacts", "Preferences"] -git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca" +git-tree-sha1 = "f389674c99bfcde17dc57454011aa44d5a260a40" uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" -version = "1.5.0" +version = "1.6.0" [[deps.LRUCache]] git-tree-sha1 = "b3cc6698599b10e652832c2f23db3cab99d51b59" @@ -331,9 +331,9 @@ version = "0.12.171" [[deps.Lz4_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "7f26c8fc5229e68484e0b3447312c98e16207d11" +git-tree-sha1 = "abf88ff67f4fd89839efcae2f4c39cbc4ecd0846" uuid = "5ced341a-0733-55b8-9ab6-a4889d929147" -version = "1.10.0+0" +version = "1.10.0+1" [[deps.MPICH_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] @@ -349,9 +349,9 @@ version = "0.1.11" [[deps.MPItrampoline_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] -git-tree-sha1 = "8c35d5420193841b2f367e658540e8d9e0601ed0" +git-tree-sha1 = "fde81c9f9c94fe5fbeaed7b3f1330305cf9a327c" uuid = "f1f71cc9-e9ae-5b93-9b94-4fe0e1ad3748" -version = "5.4.0+0" +version = "5.5.0+0" [[deps.MacroTools]] deps = ["Markdown", "Random"] @@ -388,9 +388,9 @@ version = "2023.1.10" [[deps.NCDatasets]] deps = ["CFTime", "CommonDataModel", "DataStructures", "Dates", "DiskArrays", "NetCDF_jll", "NetworkOptions", "Printf"] -git-tree-sha1 = "a640912695952b074672edb5f9aaee2f7f9fd59a" +git-tree-sha1 = "77df6d3708ec0eb3441551e1f20f7503b37c2393" uuid = "85f8d34a-cbdd-5861-8df4-14fed0d494ab" -version = "0.14.4" +version = "0.14.5" [[deps.NetCDF_jll]] deps = ["Artifacts", "Blosc_jll", "Bzip2_jll", "HDF5_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "TOML", "XML2_jll", "Zlib_jll", "Zstd_jll", "libzip_jll"] @@ -420,7 +420,7 @@ weakdeps = ["Adapt"] [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.23+2" +version = "0.3.23+4" [[deps.OpenMPI_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] @@ -430,9 +430,9 @@ version = "4.1.6+0" [[deps.OpenSSL_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a028ee3cb5641cccc4c24e90c36b0a4f7707bdf5" +git-tree-sha1 = "7493f61f55a6cce7325f197443aa80d32554ba10" uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" -version = "3.0.14+0" +version = "3.0.15+1" [[deps.OrderedCollections]] git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" @@ -634,9 +634,9 @@ version = "0.21.70" [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] -git-tree-sha1 = "d9717ce3518dc68a99e6b96300813760d887a01d" +git-tree-sha1 = "1165b0443d0eca63ac1e32b8c0eb69ed2f4f8127" uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" -version = "2.13.1+0" +version = "2.13.3+0" [[deps.XZ_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] @@ -651,9 +651,9 @@ version = "1.2.13+1" [[deps.Zstd_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e678132f07ddb5bfa46857f0d7620fb9be675d3b" +git-tree-sha1 = "555d1076590a6cc2fdee2ef1469451f872d8b41b" uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" -version = "1.5.6+0" +version = "1.5.6+1" [[deps.libaec_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] @@ -664,7 +664,7 @@ version = "1.1.2+0" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.8.0+1" +version = "5.11.0+0" [[deps.libzip_jll]] deps = ["Artifacts", "Bzip2_jll", "GnuTLS_jll", "JLLWrappers", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] diff --git a/build/create_binaries/Manifest.toml b/build/create_binaries/Manifest.toml index 1fbd18b77..9bc158936 100644 --- a/build/create_binaries/Manifest.toml +++ b/build/create_binaries/Manifest.toml @@ -1,6 +1,6 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.10.0" +julia_version = "1.10.5" manifest_format = "2.0" project_hash = "09d43e232d75c1e607afc4fcd6ce747acf2d3551" @@ -31,9 +31,9 @@ version = "0.4.0" [[deps.ArrayInterface]] deps = ["Adapt", "LinearAlgebra"] -git-tree-sha1 = "f54c23a5d304fb87110de62bace7777d59088c34" +git-tree-sha1 = "3640d077b6dafd64ceb8fd5c1ec76f7ca53bcf76" uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" -version = "7.15.0" +version = "7.16.0" [deps.ArrayInterface.extensions] ArrayInterfaceBandedMatricesExt = "BandedMatrices" @@ -130,7 +130,7 @@ weakdeps = ["Dates", "LinearAlgebra"] [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" -version = "1.0.5+1" +version = "1.1.1+0" [[deps.CpuId]] deps = ["Markdown"] @@ -201,9 +201,9 @@ version = "3.8.4+0" [[deps.Graphs]] deps = ["ArnoldiMethod", "Compat", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] -git-tree-sha1 = "ebd18c326fa6cee1efb7da9a3b45cf69da2ed4d9" +git-tree-sha1 = "1dc470db8b1131cfc7fb4c115de89fe391b9e780" uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" -version = "1.11.2" +version = "1.12.0" [[deps.HDF5_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "libaec_jll"] @@ -219,9 +219,9 @@ version = "0.1.17" [[deps.Hwloc_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "5e19e1e4fa3e71b774ce746274364aef0234634e" +git-tree-sha1 = "dd3b49277ec2bb2c6b94eb1604d4d0616016f7a6" uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8" -version = "2.11.1+0" +version = "2.11.2+0" [[deps.IfElse]] git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1" @@ -239,9 +239,9 @@ uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" [[deps.JLLWrappers]] deps = ["Artifacts", "Preferences"] -git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca" +git-tree-sha1 = "f389674c99bfcde17dc57454011aa44d5a260a40" uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" -version = "1.5.0" +version = "1.6.0" [[deps.LRUCache]] git-tree-sha1 = "b3cc6698599b10e652832c2f23db3cab99d51b59" @@ -331,9 +331,9 @@ version = "0.12.171" [[deps.Lz4_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "7f26c8fc5229e68484e0b3447312c98e16207d11" +git-tree-sha1 = "abf88ff67f4fd89839efcae2f4c39cbc4ecd0846" uuid = "5ced341a-0733-55b8-9ab6-a4889d929147" -version = "1.10.0+0" +version = "1.10.0+1" [[deps.MPICH_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] @@ -349,9 +349,9 @@ version = "0.1.11" [[deps.MPItrampoline_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] -git-tree-sha1 = "8c35d5420193841b2f367e658540e8d9e0601ed0" +git-tree-sha1 = "fde81c9f9c94fe5fbeaed7b3f1330305cf9a327c" uuid = "f1f71cc9-e9ae-5b93-9b94-4fe0e1ad3748" -version = "5.4.0+0" +version = "5.5.0+0" [[deps.MacroTools]] deps = ["Markdown", "Random"] @@ -388,9 +388,9 @@ version = "2023.1.10" [[deps.NCDatasets]] deps = ["CFTime", "CommonDataModel", "DataStructures", "Dates", "DiskArrays", "NetCDF_jll", "NetworkOptions", "Printf"] -git-tree-sha1 = "a640912695952b074672edb5f9aaee2f7f9fd59a" +git-tree-sha1 = "77df6d3708ec0eb3441551e1f20f7503b37c2393" uuid = "85f8d34a-cbdd-5861-8df4-14fed0d494ab" -version = "0.14.4" +version = "0.14.5" [[deps.NetCDF_jll]] deps = ["Artifacts", "Blosc_jll", "Bzip2_jll", "HDF5_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "TOML", "XML2_jll", "Zlib_jll", "Zstd_jll", "libzip_jll"] @@ -420,7 +420,7 @@ weakdeps = ["Adapt"] [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.23+2" +version = "0.3.23+4" [[deps.OpenMPI_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] @@ -430,9 +430,9 @@ version = "4.1.6+0" [[deps.OpenSSL_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a028ee3cb5641cccc4c24e90c36b0a4f7707bdf5" +git-tree-sha1 = "7493f61f55a6cce7325f197443aa80d32554ba10" uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" -version = "3.0.14+0" +version = "3.0.15+1" [[deps.OrderedCollections]] git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" @@ -652,15 +652,15 @@ version = "0.21.70" [[deps.Wflow]] deps = ["BasicModelInterface", "CFTime", "Dates", "DelimitedFiles", "FieldMetadata", "Glob", "Graphs", "IfElse", "LoggingExtras", "LoopVectorization", "NCDatasets", "Parameters", "Polyester", "ProgressLogging", "Random", "StaticArrays", "Statistics", "TOML", "TerminalLoggers", "UnPack"] -git-tree-sha1 = "414d32f07561872f03cb4f347df719962dfddc5e" path = "../.." uuid = "d48b7d99-76e7-47ae-b1d5-ff0c1cf9a818" +version = "0.8.1" [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] -git-tree-sha1 = "d9717ce3518dc68a99e6b96300813760d887a01d" +git-tree-sha1 = "1165b0443d0eca63ac1e32b8c0eb69ed2f4f8127" uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" -version = "2.13.1+0" +version = "2.13.3+0" [[deps.XZ_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] @@ -675,9 +675,9 @@ version = "1.2.13+1" [[deps.Zstd_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e678132f07ddb5bfa46857f0d7620fb9be675d3b" +git-tree-sha1 = "555d1076590a6cc2fdee2ef1469451f872d8b41b" uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" -version = "1.5.6+0" +version = "1.5.6+1" [[deps.libaec_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] @@ -688,7 +688,7 @@ version = "1.1.2+0" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.8.0+1" +version = "5.11.0+0" [[deps.libzip_jll]] deps = ["Artifacts", "Bzip2_jll", "GnuTLS_jll", "JLLWrappers", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] diff --git a/build/create_binaries/download_test_data.jl b/build/create_binaries/download_test_data.jl index 0ed330ed6..3ff5f5437 100644 --- a/build/create_binaries/download_test_data.jl +++ b/build/create_binaries/download_test_data.jl @@ -1,7 +1,6 @@ -using Wflow using Downloads -testdir = abspath(dirname(pathof(Wflow)), "..", "test") +testdir = normpath(@__DIR__, "../../test") # ensure test data is present # this code is copied from runtests.jl, and is a temporary solution to get the data in place @@ -56,4 +55,4 @@ forcing_calendar_noleap_path = forcing_piave_path = testdata(v"0.2.9", "inmaps-era5-2010-piave.nc", "forcing-piave.nc") staticmaps_piave_path = testdata(v"0.2.9", "staticmaps-piave.nc", "staticmaps-piave.nc") instates_piave_path = testdata(v"0.2.9", "instates-piave.nc", "instates-piave.nc") -instates_piave_gwf_path = testdata(v"0.2.9", "instates-piave-gwf.nc", "instates-piave-gwf.nc") \ No newline at end of file +instates_piave_gwf_path = testdata(v"0.2.9", "instates-piave-gwf.nc", "instates-piave-gwf.nc") diff --git a/build/wflow_cli/Manifest.toml b/build/wflow_cli/Manifest.toml index 238ef922d..11e38b836 100644 --- a/build/wflow_cli/Manifest.toml +++ b/build/wflow_cli/Manifest.toml @@ -1,6 +1,6 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.10.0" +julia_version = "1.10.5" manifest_format = "2.0" project_hash = "ea4cd58591c4d2b83f5376bb1cc8297e50705069" @@ -31,9 +31,9 @@ version = "0.4.0" [[deps.ArrayInterface]] deps = ["Adapt", "LinearAlgebra"] -git-tree-sha1 = "f54c23a5d304fb87110de62bace7777d59088c34" +git-tree-sha1 = "3640d077b6dafd64ceb8fd5c1ec76f7ca53bcf76" uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" -version = "7.15.0" +version = "7.16.0" [deps.ArrayInterface.extensions] ArrayInterfaceBandedMatricesExt = "BandedMatrices" @@ -130,7 +130,7 @@ weakdeps = ["Dates", "LinearAlgebra"] [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" -version = "1.0.5+1" +version = "1.1.1+0" [[deps.CpuId]] deps = ["Markdown"] @@ -201,9 +201,9 @@ version = "3.8.4+0" [[deps.Graphs]] deps = ["ArnoldiMethod", "Compat", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] -git-tree-sha1 = "ebd18c326fa6cee1efb7da9a3b45cf69da2ed4d9" +git-tree-sha1 = "1dc470db8b1131cfc7fb4c115de89fe391b9e780" uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" -version = "1.11.2" +version = "1.12.0" [[deps.HDF5_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "libaec_jll"] @@ -219,9 +219,9 @@ version = "0.1.17" [[deps.Hwloc_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "5e19e1e4fa3e71b774ce746274364aef0234634e" +git-tree-sha1 = "dd3b49277ec2bb2c6b94eb1604d4d0616016f7a6" uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8" -version = "2.11.1+0" +version = "2.11.2+0" [[deps.IfElse]] git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1" @@ -239,9 +239,9 @@ uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" [[deps.JLLWrappers]] deps = ["Artifacts", "Preferences"] -git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca" +git-tree-sha1 = "f389674c99bfcde17dc57454011aa44d5a260a40" uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" -version = "1.5.0" +version = "1.6.0" [[deps.LRUCache]] git-tree-sha1 = "b3cc6698599b10e652832c2f23db3cab99d51b59" @@ -331,9 +331,9 @@ version = "0.12.171" [[deps.Lz4_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "7f26c8fc5229e68484e0b3447312c98e16207d11" +git-tree-sha1 = "abf88ff67f4fd89839efcae2f4c39cbc4ecd0846" uuid = "5ced341a-0733-55b8-9ab6-a4889d929147" -version = "1.10.0+0" +version = "1.10.0+1" [[deps.MPICH_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] @@ -349,9 +349,9 @@ version = "0.1.11" [[deps.MPItrampoline_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] -git-tree-sha1 = "8c35d5420193841b2f367e658540e8d9e0601ed0" +git-tree-sha1 = "fde81c9f9c94fe5fbeaed7b3f1330305cf9a327c" uuid = "f1f71cc9-e9ae-5b93-9b94-4fe0e1ad3748" -version = "5.4.0+0" +version = "5.5.0+0" [[deps.MacroTools]] deps = ["Markdown", "Random"] @@ -388,9 +388,9 @@ version = "2023.1.10" [[deps.NCDatasets]] deps = ["CFTime", "CommonDataModel", "DataStructures", "Dates", "DiskArrays", "NetCDF_jll", "NetworkOptions", "Printf"] -git-tree-sha1 = "a640912695952b074672edb5f9aaee2f7f9fd59a" +git-tree-sha1 = "77df6d3708ec0eb3441551e1f20f7503b37c2393" uuid = "85f8d34a-cbdd-5861-8df4-14fed0d494ab" -version = "0.14.4" +version = "0.14.5" [[deps.NetCDF_jll]] deps = ["Artifacts", "Blosc_jll", "Bzip2_jll", "HDF5_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "TOML", "XML2_jll", "Zlib_jll", "Zstd_jll", "libzip_jll"] @@ -420,7 +420,7 @@ weakdeps = ["Adapt"] [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.23+2" +version = "0.3.23+4" [[deps.OpenMPI_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] @@ -430,9 +430,9 @@ version = "4.1.6+0" [[deps.OpenSSL_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a028ee3cb5641cccc4c24e90c36b0a4f7707bdf5" +git-tree-sha1 = "7493f61f55a6cce7325f197443aa80d32554ba10" uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" -version = "3.0.14+0" +version = "3.0.15+1" [[deps.OrderedCollections]] git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" @@ -638,15 +638,15 @@ version = "0.21.70" [[deps.Wflow]] deps = ["BasicModelInterface", "CFTime", "Dates", "DelimitedFiles", "FieldMetadata", "Glob", "Graphs", "IfElse", "LoggingExtras", "LoopVectorization", "NCDatasets", "Parameters", "Polyester", "ProgressLogging", "Random", "StaticArrays", "Statistics", "TOML", "TerminalLoggers", "UnPack"] -git-tree-sha1 = "414d32f07561872f03cb4f347df719962dfddc5e" path = "../.." uuid = "d48b7d99-76e7-47ae-b1d5-ff0c1cf9a818" +version = "0.8.1" [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] -git-tree-sha1 = "d9717ce3518dc68a99e6b96300813760d887a01d" +git-tree-sha1 = "1165b0443d0eca63ac1e32b8c0eb69ed2f4f8127" uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" -version = "2.13.1+0" +version = "2.13.3+0" [[deps.XZ_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] @@ -661,9 +661,9 @@ version = "1.2.13+1" [[deps.Zstd_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e678132f07ddb5bfa46857f0d7620fb9be675d3b" +git-tree-sha1 = "555d1076590a6cc2fdee2ef1469451f872d8b41b" uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" -version = "1.5.6+0" +version = "1.5.6+1" [[deps.libaec_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] @@ -674,7 +674,7 @@ version = "1.1.2+0" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.8.0+1" +version = "5.11.0+0" [[deps.libzip_jll]] deps = ["Artifacts", "Bzip2_jll", "GnuTLS_jll", "JLLWrappers", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] diff --git a/pixi.lock b/pixi.lock index c641b5934..2eb64b424 100644 --- a/pixi.lock +++ b/pixi.lock @@ -1,4 +1,4 @@ -version: 4 +version: 5 environments: default: channels: @@ -7,42 +7,43 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hd590300_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.2.2-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/juliaup-1.13.0-he8a937b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h41732ed_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.2-h59595ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.8.30-hbcca054_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/juliaup-1.17.6-h8fae777_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.3-h5888daf_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-13.2.0-h807b86a_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-13.2.0-h807b86a_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.1.0-h77fa898_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.1.0-h69a702a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.1.0-h77fa898_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.45.2-h2797004_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.46.1-hadc24fc_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-hd590300_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.4.20240210-h59595ed_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.2.1-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.2-hab00c5b_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-h4ab18f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.6-hc5c86c4_2_cpython.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h8827d51_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 win-64: - - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-hcfcfb64_5.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2024.2.2-h56e8100_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/juliaup-1.13.0-h975169c_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.6.2-h63175ca_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h2466b09_7.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2024.8.30-h56e8100_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/juliaup-1.17.6-ha073cba_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.6.3-he0c23c2_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.2-h8ffe710_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.45.2-hcfcfb64_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.13-hcfcfb64_5.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.2.1-hcfcfb64_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.12.2-h2628c8c_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.46.1-h2466b09_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.3.2-h2466b09_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.12.6-hce54a09_2_cpython.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h5226925_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h8827d51_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-hcf57466_18.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.38.33130-h82b7239_18.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.38.33130-hcb4865c_18.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h8a93ad2_21.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.40.33810-ha82c5b3_21.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.40.33810-h3bf8584_21.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2 packages: - kind: conda @@ -77,131 +78,143 @@ packages: - kind: conda name: bzip2 version: 1.0.8 - build: hcfcfb64_5 - build_number: 5 + build: h2466b09_7 + build_number: 7 subdir: win-64 - url: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-hcfcfb64_5.conda - sha256: ae5f47a5c86fd6db822931255dcf017eb12f60c77f07dc782ccb477f7808aab2 - md5: 26eb8ca6ea332b675e11704cce84a3be + url: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h2466b09_7.conda + sha256: 35a5dad92e88fdd7fc405e864ec239486f4f31eec229e31686e61a140a8e573b + md5: 276e7ffe9ffe39688abc665ef0f45596 depends: - ucrt >=10.0.20348.0 - vc >=14.2,<15 - vc14_runtime >=14.29.30139 license: bzip2-1.0.6 license_family: BSD - size: 124580 - timestamp: 1699280668742 + size: 54927 + timestamp: 1720974860185 - kind: conda name: bzip2 version: 1.0.8 - build: hd590300_5 - build_number: 5 + build: h4bc722e_7 + build_number: 7 subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hd590300_5.conda - sha256: 242c0c324507ee172c0e0dd2045814e746bb303d1eb78870d182ceb0abc726a8 - md5: 69b8b6202a07720f448be700e300ccf4 + url: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda + sha256: 5ced96500d945fb286c9c838e54fa759aa04a7129c59800f0846b4335cee770d + md5: 62ee74e96c5ebb0af99386de58cf9553 depends: + - __glibc >=2.17,<3.0.a0 - libgcc-ng >=12 license: bzip2-1.0.6 license_family: BSD - size: 254228 - timestamp: 1699279927352 + size: 252783 + timestamp: 1720974456583 - kind: conda name: ca-certificates - version: 2024.2.2 + version: 2024.8.30 build: h56e8100_0 subdir: win-64 - url: https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2024.2.2-h56e8100_0.conda - sha256: 4d587088ecccd393fec3420b64f1af4ee1a0e6897a45cfd5ef38055322cea5d0 - md5: 63da060240ab8087b60d1357051ea7d6 + url: https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2024.8.30-h56e8100_0.conda + sha256: 0fcac3a7ffcc556649e034a1802aedf795e64227eaa7194d207b01eaf26454c4 + md5: 4c4fd67c18619be5aa65dc5b6c72e490 license: ISC - size: 155886 - timestamp: 1706843918052 + size: 158773 + timestamp: 1725019107649 - kind: conda name: ca-certificates - version: 2024.2.2 + version: 2024.8.30 build: hbcca054_0 subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.2.2-hbcca054_0.conda - sha256: 91d81bfecdbb142c15066df70cc952590ae8991670198f92c66b62019b251aeb - md5: 2f4327a1cbe7f022401b236e915a5fef + url: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.8.30-hbcca054_0.conda + sha256: afee721baa6d988e27fef1832f68d6f32ac8cc99cdf6015732224c2841a09cea + md5: c27d1c142233b5bc9ca570c6e2e0c244 license: ISC - size: 155432 - timestamp: 1706843687645 + size: 159003 + timestamp: 1725018903918 - kind: conda name: juliaup - version: 1.13.0 - build: h975169c_0 - subdir: win-64 - url: https://conda.anaconda.org/conda-forge/win-64/juliaup-1.13.0-h975169c_0.conda - sha256: a14b9856b9c2f6ffd40d1b538dc1539b5e3058710df7c3c8fd8d837d2d43b1eb - md5: 78c45d1f9c8f0f4e563802d46db11ec0 + version: 1.17.6 + build: h8fae777_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/juliaup-1.17.6-h8fae777_0.conda + sha256: 3c536811bf6efc004b34b81e6f6b8670c070c93dca67d8dbf4869c311a977a40 + md5: f355be9b0191ef5c52e282808ae13ba2 depends: - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + constrains: + - __glibc >=2.17 license: MIT license_family: MIT - size: 1391312 - timestamp: 1706565757417 + size: 2630720 + timestamp: 1725414884974 - kind: conda name: juliaup - version: 1.13.0 - build: he8a937b_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/juliaup-1.13.0-he8a937b_0.conda - sha256: 7eec8c76b948ff51cfdb03e4d0d5303dba913b929de8173dc2df1ad5f8900cee - md5: 36f632298719dd4b37d57cca4ebd2ead + version: 1.17.6 + build: ha073cba_0 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/juliaup-1.17.6-ha073cba_0.conda + sha256: b8c826842cb49578f67f0fcdcb9d383e162d6618c31e30ff1aa2f37da5b0cd87 + md5: 3b56f87c6f2b17a246dad6d06d86127b depends: - - libgcc-ng >=12 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 license: MIT license_family: MIT - size: 3859930 - timestamp: 1706564340550 + size: 1517497 + timestamp: 1725415742700 - kind: conda name: ld_impl_linux-64 - version: '2.40' - build: h41732ed_0 + version: '2.43' + build: h712a8e2_1 + build_number: 1 subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h41732ed_0.conda - sha256: f6cc89d887555912d6c61b295d398cff9ec982a3417d38025c45d5dd9b9e79cd - md5: 7aca3059a1729aa76c597603f10b0dd3 + url: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_1.conda + sha256: 0c21387f9a411e3d1f7f2969026bacfece133c8f1e72faea9cde29c0c19e1f3a + md5: 83e1364586ceb8d0739fbc85b5c95837 + depends: + - __glibc >=2.17,<3.0.a0 constrains: - - binutils_impl_linux-64 2.40 + - binutils_impl_linux-64 2.43 license: GPL-3.0-only license_family: GPL - size: 704696 - timestamp: 1674833944779 + size: 669616 + timestamp: 1727304687962 - kind: conda name: libexpat - version: 2.6.2 - build: h59595ed_0 + version: 2.6.3 + build: h5888daf_0 subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.2-h59595ed_0.conda - sha256: 331bb7c7c05025343ebd79f86ae612b9e1e74d2687b8f3179faec234f986ce19 - md5: e7ba12deb7020dd080c6c70e7b6f6a3d + url: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.3-h5888daf_0.conda + sha256: 4bb47bb2cd09898737a5211e2992d63c555d63715a07ba56eae0aff31fb89c22 + md5: 59f4c43bb1b5ef1c71946ff2cbf59524 depends: - - libgcc-ng >=12 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 constrains: - - expat 2.6.2.* + - expat 2.6.3.* license: MIT license_family: MIT - size: 73730 - timestamp: 1710362120304 + size: 73616 + timestamp: 1725568742634 - kind: conda name: libexpat - version: 2.6.2 - build: h63175ca_0 + version: 2.6.3 + build: he0c23c2_0 subdir: win-64 - url: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.6.2-h63175ca_0.conda - sha256: 79f612f75108f3e16bbdc127d4885bb74729cf66a8702fca0373dad89d40c4b7 - md5: bc592d03f62779511d392c175dcece64 + url: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.6.3-he0c23c2_0.conda + sha256: 9543965d155b8da96fc67dd81705fe5c2571c7c00becc8de5534c850393d4e3c + md5: 21415fbf4d0de6767a621160b43e5dea + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 constrains: - - expat 2.6.2.* + - expat 2.6.3.* license: MIT license_family: MIT - size: 139224 - timestamp: 1710362609641 + size: 138992 + timestamp: 1725569106114 - kind: conda name: libffi version: 3.4.2 @@ -234,38 +247,54 @@ packages: size: 42063 timestamp: 1636489106777 - kind: conda - name: libgcc-ng - version: 13.2.0 - build: h807b86a_5 - build_number: 5 + name: libgcc + version: 14.1.0 + build: h77fa898_1 + build_number: 1 subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-13.2.0-h807b86a_5.conda - sha256: d32f78bfaac282cfe5205f46d558704ad737b8dbf71f9227788a5ca80facaba4 - md5: d4ff227c46917d3b4565302a2bbb276b + url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.1.0-h77fa898_1.conda + sha256: 10fa74b69266a2be7b96db881e18fa62cfa03082b65231e8d652e897c4b335a3 + md5: 002ef4463dd1e2b44a94a4ace468f5d2 depends: - _libgcc_mutex 0.1 conda_forge - _openmp_mutex >=4.5 constrains: - - libgomp 13.2.0 h807b86a_5 + - libgomp 14.1.0 h77fa898_1 + - libgcc-ng ==14.1.0=*_1 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL - size: 770506 - timestamp: 1706819192021 + size: 846380 + timestamp: 1724801836552 +- kind: conda + name: libgcc-ng + version: 14.1.0 + build: h69a702a_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.1.0-h69a702a_1.conda + sha256: b91f7021e14c3d5c840fbf0dc75370d6e1f7c7ff4482220940eaafb9c64613b7 + md5: 1efc0ad219877a73ef977af7dbb51f17 + depends: + - libgcc 14.1.0 h77fa898_1 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 52170 + timestamp: 1724801842101 - kind: conda name: libgomp - version: 13.2.0 - build: h807b86a_5 - build_number: 5 + version: 14.1.0 + build: h77fa898_1 + build_number: 1 subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgomp-13.2.0-h807b86a_5.conda - sha256: 0d3d4b1b0134283ea02d58e8eb5accf3655464cf7159abf098cc694002f8d34e - md5: d211c42b9ce49aee3734fdc828731689 + url: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.1.0-h77fa898_1.conda + sha256: c96724c8ae4ee61af7674c5d9e5a3fbcf6cd887a40ad5a52c99aa36f1d4f9680 + md5: 23c255b008c4f2ae008f81edcabaca89 depends: - _libgcc_mutex 0.1 conda_forge license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL - size: 419751 - timestamp: 1706819107383 + size: 460218 + timestamp: 1724801743478 - kind: conda name: libnsl version: 2.0.1 @@ -282,33 +311,34 @@ packages: timestamp: 1697359010159 - kind: conda name: libsqlite - version: 3.45.2 - build: h2797004_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.45.2-h2797004_0.conda - sha256: 8cdbeb7902729e319510a82d7c642402981818702b58812af265ef55d1315473 - md5: 866983a220e27a80cb75e85cb30466a1 - depends: - - libgcc-ng >=12 - - libzlib >=1.2.13,<1.3.0a0 - license: Unlicense - size: 857489 - timestamp: 1710254744982 -- kind: conda - name: libsqlite - version: 3.45.2 - build: hcfcfb64_0 + version: 3.46.1 + build: h2466b09_0 subdir: win-64 - url: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.45.2-hcfcfb64_0.conda - sha256: 4bb24b986550275a6d02835150d943c4c675808d05c0efc5c2a22154d007a69f - md5: f95359f8dc5abf7da7776ece9ef10bc5 + url: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.46.1-h2466b09_0.conda + sha256: ef83f90961630bc54a95e48062b05cf9c9173a822ea01784288029613a45eea4 + md5: 8a7c1ad01f58623bfbae8d601db7cf3b depends: - ucrt >=10.0.20348.0 - vc >=14.2,<15 - vc14_runtime >=14.29.30139 license: Unlicense - size: 869606 - timestamp: 1710255095740 + size: 876666 + timestamp: 1725354171439 +- kind: conda + name: libsqlite + version: 3.46.1 + build: hadc24fc_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.46.1-hadc24fc_0.conda + sha256: 9851c049abafed3ee329d6c7c2033407e2fc269d33a75c071110ab52300002b0 + md5: 36f79405ab16bf271edb55b213836dac + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libzlib >=1.3.1,<2.0a0 + license: Unlicense + size: 865214 + timestamp: 1725353659783 - kind: conda name: libuuid version: 2.38.1 @@ -339,147 +369,147 @@ packages: timestamp: 1702724383534 - kind: conda name: libzlib - version: 1.2.13 - build: hcfcfb64_5 - build_number: 5 + version: 1.3.1 + build: h2466b09_1 + build_number: 1 subdir: win-64 - url: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.13-hcfcfb64_5.conda - sha256: c161822ee8130b71e08b6d282b9919c1de2c5274b29921a867bca0f7d30cad26 - md5: 5fdb9c6a113b6b6cb5e517fd972d5f41 + url: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_1.conda + sha256: b13846a54a15243e15f96fec06b526d8155adc6a1ac2b6ed47a88f6a71a94b68 + md5: d4483ca8afc57ddf1f6dded53b36c17f depends: - ucrt >=10.0.20348.0 - vc >=14.2,<15 - vc14_runtime >=14.29.30139 constrains: - - zlib 1.2.13 *_5 + - zlib 1.3.1 *_1 license: Zlib license_family: Other - size: 55800 - timestamp: 1686575452215 + size: 56186 + timestamp: 1716874730539 - kind: conda name: libzlib - version: 1.2.13 - build: hd590300_5 - build_number: 5 + version: 1.3.1 + build: h4ab18f5_1 + build_number: 1 subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-hd590300_5.conda - sha256: 370c7c5893b737596fd6ca0d9190c9715d89d888b8c88537ae1ef168c25e82e4 - md5: f36c115f1ee199da648e0597ec2047ad + url: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-h4ab18f5_1.conda + sha256: adf6096f98b537a11ae3729eaa642b0811478f0ea0402ca67b5108fe2cb0010d + md5: 57d7dc60e9325e3de37ff8dffd18e814 depends: - libgcc-ng >=12 constrains: - - zlib 1.2.13 *_5 + - zlib 1.3.1 *_1 license: Zlib license_family: Other - size: 61588 - timestamp: 1686575217516 + size: 61574 + timestamp: 1716874187109 - kind: conda name: ncurses - version: 6.4.20240210 - build: h59595ed_0 + version: '6.5' + build: he02047a_1 + build_number: 1 subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.4.20240210-h59595ed_0.conda - sha256: aa0f005b6727aac6507317ed490f0904430584fa8ca722657e7f0fb94741de81 - md5: 97da8860a0da5413c7c98a3b3838a645 + url: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + sha256: 6a1d5d8634c1a07913f1c525db6455918cbc589d745fac46d9d6e30340c8731a + md5: 70caf8bb6cf39a0b6b7efc885f51c0fe depends: + - __glibc >=2.17,<3.0.a0 - libgcc-ng >=12 license: X11 AND BSD-3-Clause - size: 895669 - timestamp: 1710866638986 + size: 889086 + timestamp: 1724658547447 - kind: conda name: openssl - version: 3.2.1 - build: hcfcfb64_1 - build_number: 1 + version: 3.3.2 + build: h2466b09_0 subdir: win-64 - url: https://conda.anaconda.org/conda-forge/win-64/openssl-3.2.1-hcfcfb64_1.conda - sha256: 61ce4e11c3c26ed4e4d9b7e7e2483121a1741ad0f9c8db0a91a28b6e05182ce6 - md5: 958e0418e93e50c575bff70fbcaa12d8 + url: https://conda.anaconda.org/conda-forge/win-64/openssl-3.3.2-h2466b09_0.conda + sha256: a45c42f3577294e22ac39ddb6ef5a64fd5322e8a6725afefbf4f2b4109340bf9 + md5: 1dc86753693df5e3326bb8a85b74c589 depends: - ca-certificates - ucrt >=10.0.20348.0 - vc >=14.2,<15 - vc14_runtime >=14.29.30139 - constrains: - - pyopenssl >=22.1 license: Apache-2.0 license_family: Apache - size: 8230112 - timestamp: 1710796158475 + size: 8396053 + timestamp: 1725412961673 - kind: conda name: openssl - version: 3.2.1 - build: hd590300_1 - build_number: 1 + version: 3.3.2 + build: hb9d3cd8_0 subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.2.1-hd590300_1.conda - sha256: 2c689444ed19a603be457284cf2115ee728a3fafb7527326e96054dee7cdc1a7 - md5: 9d731343cff6ee2e5a25c4a091bf8e2a + url: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.2-hb9d3cd8_0.conda + sha256: cee91036686419f6dd6086902acf7142b4916e1c4ba042e9ca23e151da012b6d + md5: 4d638782050ab6faa27275bed57e9b4e depends: + - __glibc >=2.17,<3.0.a0 - ca-certificates - - libgcc-ng >=12 - constrains: - - pyopenssl >=22.1 + - libgcc >=13 license: Apache-2.0 license_family: Apache - size: 2865379 - timestamp: 1710793235846 + size: 2891789 + timestamp: 1725410790053 - kind: conda name: python - version: 3.12.2 - build: h2628c8c_0_cpython - subdir: win-64 - url: https://conda.anaconda.org/conda-forge/win-64/python-3.12.2-h2628c8c_0_cpython.conda - sha256: b8eda863b48ae4531635e23fd15e759d93212b6204c6847d591e25fa5fd67477 - md5: be8803e9f75a477df61d4aabea3c1246 + version: 3.12.6 + build: hc5c86c4_2_cpython + build_number: 2 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.6-hc5c86c4_2_cpython.conda + sha256: dda1e75f5227654c78d9143562366eff04444cc8b887cf8f0cc4f6236996b744 + md5: cebe1534cdebcac43acca87bec946b01 depends: + - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.5.0,<3.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.6.3,<3.0a0 - libffi >=3.4,<4.0a0 - - libsqlite >=3.45.1,<4.0a0 - - libzlib >=1.2.13,<1.3.0a0 - - openssl >=3.2.1,<4.0a0 + - libgcc >=13 + - libnsl >=2.0.1,<2.1.0a0 + - libsqlite >=3.46.1,<4.0a0 + - libuuid >=2.38.1,<3.0a0 + - libxcrypt >=4.4.36 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.3.2,<4.0a0 + - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - xz >=5.2.6,<6.0a0 constrains: - python_abi 3.12.* *_cp312 license: Python-2.0 - size: 16083296 - timestamp: 1708116662336 + size: 31531222 + timestamp: 1727721840884 - kind: conda name: python - version: 3.12.2 - build: hab00c5b_0_cpython - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.2-hab00c5b_0_cpython.conda - sha256: ddb7a2d8d78046bda5d7631e6814f9468d2eb054e10f86f4648c9d1fdaa30c0f - md5: ad7b68400f3a6ebe72b00be093c7f301 + version: 3.12.6 + build: hce54a09_2_cpython + build_number: 2 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/python-3.12.6-hce54a09_2_cpython.conda + sha256: 41325d4c2b5f8bda2b5dd4a71555ad12f3c78b7f0a00e41e57475822e7e89a73 + md5: c30b76855225babfbf18595408a377f3 depends: - bzip2 >=1.0.8,<2.0a0 - - ld_impl_linux-64 >=2.36.1 - - libexpat >=2.5.0,<3.0a0 + - libexpat >=2.6.3,<3.0a0 - libffi >=3.4,<4.0a0 - - libgcc-ng >=12 - - libnsl >=2.0.1,<2.1.0a0 - - libsqlite >=3.45.1,<4.0a0 - - libuuid >=2.38.1,<3.0a0 - - libxcrypt >=4.4.36 - - libzlib >=1.2.13,<1.3.0a0 - - ncurses >=6.4,<7.0a0 - - openssl >=3.2.1,<4.0a0 - - readline >=8.2,<9.0a0 + - libsqlite >=3.46.1,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.3.2,<4.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 - xz >=5.2.6,<6.0a0 constrains: - python_abi 3.12.* *_cp312 license: Python-2.0 - size: 32312631 - timestamp: 1708118077305 + size: 15854309 + timestamp: 1727719258211 - kind: conda name: readline version: '8.2' @@ -524,7 +554,7 @@ packages: md5: d453b98d9c83e71da0741bb0ff4d76bc depends: - libgcc-ng >=12 - - libzlib >=1.2.13,<1.3.0a0 + - libzlib >=1.2.13,<2.0.0a0 license: TCL license_family: BSD size: 3318875 @@ -532,15 +562,16 @@ packages: - kind: conda name: tzdata version: 2024a - build: h0c530f3_0 + build: h8827d51_1 + build_number: 1 subdir: noarch noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda - sha256: 7b2b69c54ec62a243eb6fba2391b5e443421608c3ae5dbff938ad33ca8db5122 - md5: 161081fc7cec0bfda0d86d7cb595f8d8 + url: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h8827d51_1.conda + sha256: 7d21c95f61319dba9209ca17d1935e6128af4235a67ee4e57a00908a1450081e + md5: 8bfdead4e0fff0383ae4c9c50d0531bd license: LicenseRef-Public-Domain - size: 119815 - timestamp: 1706886945727 + size: 124164 + timestamp: 1724736371498 - kind: conda name: ucrt version: 10.0.22621.0 @@ -558,52 +589,52 @@ packages: - kind: conda name: vc version: '14.3' - build: hcf57466_18 - build_number: 18 + build: h8a93ad2_21 + build_number: 21 subdir: win-64 - url: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-hcf57466_18.conda - sha256: 447a8d8292a7b2107dcc18afb67f046824711a652725fc0f522c368e7a7b8318 - md5: 20e1e652a4c740fa719002a8449994a2 + url: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h8a93ad2_21.conda + sha256: f14f5238c2e2516e292af43d91df88f212d769b4853eb46d03291793dcf00da9 + md5: e632a9b865d4b653aa656c9fb4f4817c depends: - - vc14_runtime >=14.38.33130 + - vc14_runtime >=14.40.33810 track_features: - vc14 license: BSD-3-Clause license_family: BSD - size: 16977 - timestamp: 1702511255313 + size: 17243 + timestamp: 1725984095174 - kind: conda name: vc14_runtime - version: 14.38.33130 - build: h82b7239_18 - build_number: 18 + version: 14.40.33810 + build: ha82c5b3_21 + build_number: 21 subdir: win-64 - url: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.38.33130-h82b7239_18.conda - sha256: bf94c9af4b2e9cba88207001197e695934eadc96a5c5e4cd7597e950aae3d8ff - md5: 8be79fdd2725ddf7bbf8a27a4c1f79ba + url: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.40.33810-ha82c5b3_21.conda + sha256: c3bf51bff7db39ad7e890dbef1b1026df0af36975aea24dea7c5fe1e0b382c40 + md5: b3ebb670caf046e32b835fbda056c4f9 depends: - ucrt >=10.0.20348.0 constrains: - - vs2015_runtime 14.38.33130.* *_18 + - vs2015_runtime 14.40.33810.* *_21 license: LicenseRef-ProprietaryMicrosoft license_family: Proprietary - size: 749868 - timestamp: 1702511239004 + size: 751757 + timestamp: 1725984166774 - kind: conda name: vs2015_runtime - version: 14.38.33130 - build: hcb4865c_18 - build_number: 18 + version: 14.40.33810 + build: h3bf8584_21 + build_number: 21 subdir: win-64 - url: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.38.33130-hcb4865c_18.conda - sha256: a2fec221f361d6263c117f4ea6d772b21c90a2f8edc6f3eb0eadec6bfe8843db - md5: 10d42885e3ed84e575b454db30f1aa93 + url: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.40.33810-h3bf8584_21.conda + sha256: 472410455c381e406ec8c1d3e0342b48ee23122ef7ffb22a09d9763ca5df4d20 + md5: b3f37db7b7ae1c22600fa26a63ed99b3 depends: - - vc14_runtime >=14.38.33130 + - vc14_runtime >=14.40.33810 license: BSD-3-Clause license_family: BSD - size: 16988 - timestamp: 1702511261442 + size: 17241 + timestamp: 1725984096440 - kind: conda name: xz version: 5.2.6 diff --git a/pixi.toml b/pixi.toml index 824ed2000..274e87b6e 100644 --- a/pixi.toml +++ b/pixi.toml @@ -10,7 +10,7 @@ platforms = ["win-64", "linux-64"] [tasks] # Installation -install-julia = "juliaup add 1.10.0 && juliaup override unset && juliaup override set 1.10.0" +install-julia = "juliaup add 1.10.5 && juliaup override unset && juliaup override set 1.10.5" # Julia update-registry-julia = "julia --eval='using Pkg; Registry.update()'" instantiate-julia = "julia --project --eval='using Pkg; Pkg.instantiate()'" diff --git a/server/Manifest.toml b/server/Manifest.toml index a7b1a202c..e4c10a2ac 100644 --- a/server/Manifest.toml +++ b/server/Manifest.toml @@ -1,6 +1,6 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.10.0" +julia_version = "1.10.5" manifest_format = "2.0" project_hash = "b955683a8ff3d663c1ae626b1dc754fbae9fa2f7" @@ -31,9 +31,9 @@ version = "0.4.0" [[deps.ArrayInterface]] deps = ["Adapt", "LinearAlgebra"] -git-tree-sha1 = "f54c23a5d304fb87110de62bace7777d59088c34" +git-tree-sha1 = "3640d077b6dafd64ceb8fd5c1ec76f7ca53bcf76" uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" -version = "7.15.0" +version = "7.16.0" [deps.ArrayInterface.extensions] ArrayInterfaceBandedMatricesExt = "BandedMatrices" @@ -130,7 +130,7 @@ weakdeps = ["Dates", "LinearAlgebra"] [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" -version = "1.0.5+1" +version = "1.1.1+0" [[deps.CpuId]] deps = ["Markdown"] @@ -201,9 +201,9 @@ version = "3.8.4+0" [[deps.Graphs]] deps = ["ArnoldiMethod", "Compat", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] -git-tree-sha1 = "ebd18c326fa6cee1efb7da9a3b45cf69da2ed4d9" +git-tree-sha1 = "1dc470db8b1131cfc7fb4c115de89fe391b9e780" uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" -version = "1.11.2" +version = "1.12.0" [[deps.HDF5_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "libaec_jll"] @@ -219,9 +219,9 @@ version = "0.1.17" [[deps.Hwloc_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "5e19e1e4fa3e71b774ce746274364aef0234634e" +git-tree-sha1 = "dd3b49277ec2bb2c6b94eb1604d4d0616016f7a6" uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8" -version = "2.11.1+0" +version = "2.11.2+0" [[deps.IfElse]] git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1" @@ -239,9 +239,9 @@ uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" [[deps.JLLWrappers]] deps = ["Artifacts", "Preferences"] -git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca" +git-tree-sha1 = "f389674c99bfcde17dc57454011aa44d5a260a40" uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" -version = "1.5.0" +version = "1.6.0" [[deps.JSON3]] deps = ["Dates", "Mmap", "Parsers", "PrecompileTools", "StructTypes", "UUIDs"] @@ -343,9 +343,9 @@ version = "0.12.171" [[deps.Lz4_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "7f26c8fc5229e68484e0b3447312c98e16207d11" +git-tree-sha1 = "abf88ff67f4fd89839efcae2f4c39cbc4ecd0846" uuid = "5ced341a-0733-55b8-9ab6-a4889d929147" -version = "1.10.0+0" +version = "1.10.0+1" [[deps.MPICH_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] @@ -361,9 +361,9 @@ version = "0.1.11" [[deps.MPItrampoline_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] -git-tree-sha1 = "8c35d5420193841b2f367e658540e8d9e0601ed0" +git-tree-sha1 = "fde81c9f9c94fe5fbeaed7b3f1330305cf9a327c" uuid = "f1f71cc9-e9ae-5b93-9b94-4fe0e1ad3748" -version = "5.4.0+0" +version = "5.5.0+0" [[deps.MacroTools]] deps = ["Markdown", "Random"] @@ -400,9 +400,9 @@ version = "2023.1.10" [[deps.NCDatasets]] deps = ["CFTime", "CommonDataModel", "DataStructures", "Dates", "DiskArrays", "NetCDF_jll", "NetworkOptions", "Printf"] -git-tree-sha1 = "a640912695952b074672edb5f9aaee2f7f9fd59a" +git-tree-sha1 = "77df6d3708ec0eb3441551e1f20f7503b37c2393" uuid = "85f8d34a-cbdd-5861-8df4-14fed0d494ab" -version = "0.14.4" +version = "0.14.5" [[deps.NetCDF_jll]] deps = ["Artifacts", "Blosc_jll", "Bzip2_jll", "HDF5_jll", "JLLWrappers", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "TOML", "XML2_jll", "Zlib_jll", "Zstd_jll", "libzip_jll"] @@ -432,7 +432,7 @@ weakdeps = ["Adapt"] [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.23+2" +version = "0.3.23+4" [[deps.OpenMPI_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] @@ -442,9 +442,9 @@ version = "4.1.6+0" [[deps.OpenSSL_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a028ee3cb5641cccc4c24e90c36b0a4f7707bdf5" +git-tree-sha1 = "7493f61f55a6cce7325f197443aa80d32554ba10" uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" -version = "3.0.14+0" +version = "3.0.15+1" [[deps.OrderedCollections]] git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" @@ -607,9 +607,9 @@ version = "0.5.7" [[deps.StructTypes]] deps = ["Dates", "UUIDs"] -git-tree-sha1 = "ca4bccb03acf9faaf4137a9abc1881ed1841aa70" +git-tree-sha1 = "159331b30e94d7b11379037feeb9b690950cace8" uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" -version = "1.10.0" +version = "1.11.0" [[deps.SuiteSparse_jll]] deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] @@ -664,9 +664,9 @@ version = "0.8.1" [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] -git-tree-sha1 = "d9717ce3518dc68a99e6b96300813760d887a01d" +git-tree-sha1 = "1165b0443d0eca63ac1e32b8c0eb69ed2f4f8127" uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" -version = "2.13.1+0" +version = "2.13.3+0" [[deps.XZ_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] @@ -693,9 +693,9 @@ version = "1.2.13+1" [[deps.Zstd_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e678132f07ddb5bfa46857f0d7620fb9be675d3b" +git-tree-sha1 = "555d1076590a6cc2fdee2ef1469451f872d8b41b" uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" -version = "1.5.6+0" +version = "1.5.6+1" [[deps.libaec_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] @@ -706,13 +706,13 @@ version = "1.1.2+0" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.8.0+1" +version = "5.11.0+0" [[deps.libsodium_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "848ab3d00fe39d6fbc2a8641048f8f272af1c51e" +git-tree-sha1 = "f76d682d87eefadd3f165d8d9fda436464213142" uuid = "a9144af2-ca23-56d9-984f-0d03f7b5ccf8" -version = "1.0.20+0" +version = "1.0.20+1" [[deps.libzip_jll]] deps = ["Artifacts", "Bzip2_jll", "GnuTLS_jll", "JLLWrappers", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] diff --git a/test/runtests.jl b/test/runtests.jl index 3dd096afe..357608a23 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -100,6 +100,6 @@ with_logger(NullLogger()) do include("subdomains.jl") include("run_flextopo.jl") - Aqua.test_all(Wflow; ambiguities = false) + Aqua.test_all(Wflow; ambiguities = false, persistent_tasks = false) end end From 969bd2990102535347fc862380957544ab771d74 Mon Sep 17 00:00:00 2001 From: aweerts Date: Tue, 8 Oct 2024 15:30:25 +0200 Subject: [PATCH 18/27] Update publications.md (#475) * Update publications.md * Wrap text * Fix typos and missing text reference --------- Co-authored-by: Willem van Verseveld --- docs/src/intro/publications.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/src/intro/publications.md b/docs/src/intro/publications.md index 15db4a328..d958f365f 100644 --- a/docs/src/intro/publications.md +++ b/docs/src/intro/publications.md @@ -111,13 +111,11 @@ M.F.P., 2016. Improved large-scale hydrological modelling through the assimilati streamflow and downscaled satellite soil moisture observations. Hydrol. Earth Syst. Sci., 20, 3059–3076. . -van Osnabrugge, B., Weerts, A.H., Uijlenhoet, R., 2017. genRE: A method to extend gridded -precipitation climatology data sets in near real-time for hydrological forecasting purposes. -Water Resources Research, 53. . - -van Osnabrugge, B., Uijlenhoet, R., Weerts, A., 2019. Contribution of potential evaporation -forecasts to 10-day streamflow forecast skill for the Rhine River, Hydrol. Earth Syst. Sci., -23, 1453–1467, . +Pranoto, B., Soekarno, H., Hartulistiyoso, E., Nur Aidi, M., Sutrisno, D., Pohan, D., +Radhika, Sutejo, B., Heru Kuncoro, A., Nahib, I., 2024. Integrating Flood Early Warning +System (FEWS) for Optimizing Small Hydropower Sites: A West Java Case Study. EVERGREEN Joint +Journal of Novel Carbon Resource Sciences & Green Asia Strategy, 11, 3, 2691-2699. + Rakovec, O., Weerts, A.H., Sumihar, J., Uijlenhoet, R., 2015. Operational aspects of asynchronous filtering for flood forecasting. Hydrol. Earth Syst. Sci., 19, 2911–2924, @@ -168,6 +166,14 @@ van der Laan, E., P. Hazenberg, A.H. Weerts, 2024. Simulation of long-term stora of headwater reservoirs across the globe using public cloud computing infrastructure. Science of The Total Environment, 172678, . +van Osnabrugge, B., Weerts, A.H., Uijlenhoet, R., 2017. genRE: A method to extend gridded +precipitation climatology data sets in near real-time for hydrological forecasting purposes. +Water Resources Research, 53. . + +van Osnabrugge, B., Uijlenhoet, R., Weerts, A., 2019. Contribution of potential evaporation +forecasts to 10-day streamflow forecast skill for the Rhine River, Hydrol. Earth Syst. Sci., +23, 1453–1467, . + van der Vat, M., Boderie, P., Bons, K.A., Hegnauer, M., Hendriksen, G., van Oorschot, M., Ottow, B., Roelofsen, F., Sankhua, R.N., Sinha, S.K., Warren, A., Young, W., 2019. Participatory Modelling of Surface and Groundwater to Support Strategic Planning in the From 6da42c00a2150ffc9ca57e63abc8b09f580d75ff Mon Sep 17 00:00:00 2001 From: aweerts Date: Tue, 8 Oct 2024 15:41:38 +0200 Subject: [PATCH 19/27] Update publications.md (#476) --- docs/src/intro/publications.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/src/intro/publications.md b/docs/src/intro/publications.md index d958f365f..d592daa38 100644 --- a/docs/src/intro/publications.md +++ b/docs/src/intro/publications.md @@ -289,3 +289,6 @@ Plastics Series;. World Bank, Washington, DC. © World Bank, World Meteorological Organization (WMO), 2023. State of Global Water Resources report 2022, WMO-No. 1333, . + +World Meteorological Organization (WMO), 2024. State of Global Water Resources report 2023, +WMO-No. 1362, . From d8e74819cdb5da7f9c8526f17fbf0b246b3925cb Mon Sep 17 00:00:00 2001 From: Peter Nelemans <103419103+PeterNelemans@users.noreply.github.com> Date: Tue, 8 Oct 2024 16:07:34 +0200 Subject: [PATCH 20/27] Correction glacier documentation (#478) * Correction glacier documentation Changed the sentence: > The definition of glacier boundaries and initial volume is defined in three parameters. to: > The definition of glacier boundaries and initial volume is defined by two parameters. * Some edits --------- Co-authored-by: Willem van Verseveld --- docs/src/model_docs/shared_concepts.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/model_docs/shared_concepts.md b/docs/src/model_docs/shared_concepts.md index ebda6144b..5596e1f78 100644 --- a/docs/src/model_docs/shared_concepts.md +++ b/docs/src/model_docs/shared_concepts.md @@ -53,10 +53,10 @@ snow modelling is not optional. Glacier modelling is very close to snow modellin considers two main processes: glacier build-up from snow turning into firn/ice (using the HBV-light model) and glacier melt (using a temperature degree-day model). -The definition of glacier boundaries and initial volume is defined in three parameters. -`glacierfrac` is a parameter that gives the fraction of each grid cell covered by a glacier -as a number between zero and one. `glacierstore` is a state parameter that gives the amount -of water (in mm w.e.) within the glaciers at each gridcell. Because the glacier store +The definition of glacier boundaries and initial volume is defined by two parameters. The +parameter `glacierfrac` gives the fraction of each grid cell covered by a glacier as a +number between zero and one. The state parameter `glacierstore` gives the amount of water +(in mm w.e.) within the glaciers at each grid cell. Because the glacier store (`glacierstore`) cannot be initialized by running the model for a couple of years, a default initial state should be supplied by adding this parameter to the input static file. The required glacier data can be prepared from available glacier datasets. From 16b6b3a78e96ce2ce8719d7aebce1d8f20eb0c27 Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Thu, 10 Oct 2024 15:11:00 +0200 Subject: [PATCH 21/27] Remove git-tree-sha1 from metadata (#480) * Remove git-tree-sha1 from metadata It is not part of the Wflow entry in the Manifest.toml file and as a result the build fails. * Fix SSL error on Windows during build task * Add commit and tag info --- build/create_binaries/add_metadata.jl | 19 ++++++++++++++----- pixi.toml | 4 ++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/build/create_binaries/add_metadata.jl b/build/create_binaries/add_metadata.jl index 0bf14242f..489379e1c 100644 --- a/build/create_binaries/add_metadata.jl +++ b/build/create_binaries/add_metadata.jl @@ -40,14 +40,22 @@ function add_metadata(project_dir, license_file, output_dir, git_repo) error("Manifest.toml is in the old format, run Pkg.upgrade_manifest()") end julia_version = manifest["julia_version"] - wflow_entry = only(manifest["deps"]["Wflow"]) - tree = wflow_entry["git-tree-sha1"] version = TOML.parsefile(normpath(git_repo, "Project.toml"))["version"] repo = GitRepo(git_repo) branch = LibGit2.head(repo) - # commit = LibGit2.peel(LibGit2.GitCommit, branch) + commit = LibGit2.peel(LibGit2.GitCommit, branch) short_name = LibGit2.shortname(branch) - # short_commit = string(LibGit2.GitShortHash(LibGit2.GitHash(commit), 10)) + short_commit = string(LibGit2.GitShortHash(LibGit2.GitHash(commit), 10)) + + # get the release from the current tag, like `git describe --tags` + # if it is a commit after a tag, it will be -g + options = LibGit2.DescribeOptions(; describe_strategy = LibGit2.Consts.DESCRIBE_TAGS) + result = LibGit2.GitDescribeResult(repo; options) + suffix = "-dirty" + foptions = + LibGit2.DescribeFormatOptions(; dirty_suffix = Base.unsafe_convert(Cstring, suffix)) + GC.@preserve suffix tag = LibGit2.format(result; options = foptions)[2:end] # skip v prefix + url = "https://github.com/Deltares/Wflow.jl/tree" version_info = """ @@ -56,8 +64,9 @@ function add_metadata(project_dir, license_file, output_dir, git_repo) This build uses the Wflow.jl version mentioned below. ```toml + release = "$tag" version = "$version" - git-tree-sha1 = "$tree" + commit = "$url/$short_commit" branch = "$url/$short_name" julia_version = "$julia_version" ```""" diff --git a/pixi.toml b/pixi.toml index 274e87b6e..86725ee2e 100644 --- a/pixi.toml +++ b/pixi.toml @@ -13,7 +13,7 @@ platforms = ["win-64", "linux-64"] install-julia = "juliaup add 1.10.5 && juliaup override unset && juliaup override set 1.10.5" # Julia update-registry-julia = "julia --eval='using Pkg; Registry.update()'" -instantiate-julia = "julia --project --eval='using Pkg; Pkg.instantiate()'" +instantiate-julia = {cmd = "julia --project --eval='using Pkg; Pkg.instantiate()'",env = { JULIA_SSL_CA_ROOTS_PATH = "" } } initialize-julia = { depends_on = [ "update-registry-julia", "instantiate-julia", @@ -28,7 +28,7 @@ download-test-data = { cmd = "julia --project download_test_data.jl", cwd = "bui build-wflow-cli = { cmd = "julia --project create_app.jl", cwd = "build/create_binaries", depends_on = [ "download-test-data", "instantiate-wflow-cli", -] } +], env = { JULIA_SSL_CA_ROOTS_PATH = "" } } # Test test-wflow-cli = { cmd = "julia --project --eval 'using Pkg; Pkg.test()'", cwd = "build/wflow_cli", depends_on = [ "download-test-data", From 788eaad5848e4e02d5357a7c17df363fcfd7d7b7 Mon Sep 17 00:00:00 2001 From: Martijn Visser Date: Thu, 10 Oct 2024 16:35:23 +0200 Subject: [PATCH 22/27] Update compat requirements for stdlibs (#482) This requirement is dropped. --- Project.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Project.toml b/Project.toml index ac49bd9a5..bbbf9ec42 100644 --- a/Project.toml +++ b/Project.toml @@ -29,9 +29,9 @@ UnPack = "3a884ed6-31ef-47d7-9d2a-63182c4928ed" Aqua = "0.8" BasicModelInterface = "0.1" CFTime = "0.1" -Dates = "<0.0.1,1" -DelimitedFiles = "<0.0.1,1" -Downloads = "<0.0.1,1" +Dates = "1" +DelimitedFiles = "1" +Downloads = "1" FieldMetadata = "0.3" Glob = "1.3" Graphs = "1.4" @@ -44,12 +44,12 @@ Polyester = "0.7.10" Polynomials = "3,4" ProgressLogging = "0.1" QuadGK = "2" -Random = "<0.0.1,1" +Random = "1" StaticArrays = "0.12,1.0" -Statistics = "<0.0.1,1" -TOML = "<0.0.1,1" +Statistics = "1" +TOML = "1" TerminalLoggers = "0.1.5" -Test = "<0.0.1,1" +Test = "1" UnPack = "1" julia = "1.6" From e8b7603178807a9c55378b095b5d40e5967d9f0d Mon Sep 17 00:00:00 2001 From: Bart de Koning <74617371+SouthEndMusic@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:45:33 +0200 Subject: [PATCH 23/27] Quality of life improvements (#488) * Remove @unpack macro * Only import used names * Small fix * Back to Julia 1.10.5 * Random compat * Fully revert to Julia 1.10.5 * Env fix (?) --- Manifest.toml | 34 ++++++++++++++++---------------- Project.toml | 6 ++---- src/Wflow.jl | 40 ++++++++++++++++++-------------------- src/bmi.jl | 33 ++++++++++++++----------------- src/flextopo_model.jl | 4 ++-- src/flow.jl | 41 +++++++++++++++++---------------------- src/hbv_model.jl | 8 +++----- src/horizontal_process.jl | 4 ++-- src/io.jl | 28 +++++++++++++------------- src/sbm_gwf_model.jl | 2 +- src/sbm_model.jl | 10 +++++----- src/sediment.jl | 2 +- src/sediment_model.jl | 4 ++-- src/utils.jl | 2 +- src/water_demand.jl | 2 +- test/io.jl | 11 +++++------ test/run_flextopo.jl | 4 ++-- test/run_hbv.jl | 2 +- test/run_sbm.jl | 10 +++++----- test/run_sbm_gwf.jl | 4 ++-- test/run_sediment.jl | 2 +- test/runtests.jl | 1 - test/subdomains.jl | 2 +- 23 files changed, 120 insertions(+), 136 deletions(-) diff --git a/Manifest.toml b/Manifest.toml index a72a3f92b..7b5174c1c 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -2,7 +2,7 @@ julia_version = "1.10.5" manifest_format = "2.0" -project_hash = "acf9296ffbaffd3f335bdb6005badef66b3e10fe" +project_hash = "986b47155507036c06218d23750fe8de28eb001a" [[deps.AbstractTrees]] git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177" @@ -11,9 +11,9 @@ version = "0.4.5" [[deps.Adapt]] deps = ["LinearAlgebra", "Requires"] -git-tree-sha1 = "6a55b747d1812e699320963ffde36f1ebdda4099" +git-tree-sha1 = "d80af0733c99ea80575f612813fa6aa71022d33a" uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" -version = "4.0.4" +version = "4.1.0" weakdeps = ["StaticArrays"] [deps.Adapt.extensions] @@ -78,15 +78,15 @@ version = "0.1.6" [[deps.Blosc_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Lz4_jll", "Zlib_jll", "Zstd_jll"] -git-tree-sha1 = "19b98ee7e3db3b4eff74c5c9c72bf32144e24f10" +git-tree-sha1 = "ef12cdd1c7fb7e1dfd6fa8fd60d4db6bc61d2f23" uuid = "0b7ba130-8d10-5ba8-a3d6-c5182647fed9" -version = "1.21.5+0" +version = "1.21.6+0" [[deps.Bzip2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "9e2a6b69137e6969bab0152632dcb3bc108c8bdd" +git-tree-sha1 = "8873e196c2eb87962a2048b3b8e08946535864a1" uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" -version = "1.0.8+1" +version = "1.0.8+2" [[deps.CFTime]] deps = ["Dates", "Printf"] @@ -239,9 +239,9 @@ uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" [[deps.JLLWrappers]] deps = ["Artifacts", "Preferences"] -git-tree-sha1 = "f389674c99bfcde17dc57454011aa44d5a260a40" +git-tree-sha1 = "be3dc50a92e5a386872a493a10050136d4703f9b" uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" -version = "1.6.0" +version = "1.6.1" [[deps.LRUCache]] git-tree-sha1 = "b3cc6698599b10e652832c2f23db3cab99d51b59" @@ -337,9 +337,9 @@ version = "1.10.0+1" [[deps.MPICH_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] -git-tree-sha1 = "19d4bd098928a3263693991500d05d74dbdc2004" +git-tree-sha1 = "7715e65c47ba3941c502bffb7f266a41a7f54423" uuid = "7cb0a576-ebde-5e09-9194-50597f1243b4" -version = "4.2.2+0" +version = "4.2.3+0" [[deps.MPIPreferences]] deps = ["Libdl", "Preferences"] @@ -349,9 +349,9 @@ version = "0.1.11" [[deps.MPItrampoline_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] -git-tree-sha1 = "fde81c9f9c94fe5fbeaed7b3f1330305cf9a327c" +git-tree-sha1 = "70e830dab5d0775183c99fc75e4c24c614ed7142" uuid = "f1f71cc9-e9ae-5b93-9b94-4fe0e1ad3748" -version = "5.5.0+0" +version = "5.5.1+0" [[deps.MacroTools]] deps = ["Markdown", "Random"] @@ -559,9 +559,9 @@ weakdeps = ["OffsetArrays", "StaticArrays"] [[deps.StaticArrays]] deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] -git-tree-sha1 = "eeafab08ae20c62c44c8399ccb9354a04b80db50" +git-tree-sha1 = "777657803913ffc7e8cc20f0fd04b634f871af8f" uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.9.7" +version = "1.9.8" [deps.StaticArrays.extensions] StaticArraysChainRulesCoreExt = "ChainRulesCore" @@ -668,9 +668,9 @@ version = "5.11.0+0" [[deps.libzip_jll]] deps = ["Artifacts", "Bzip2_jll", "GnuTLS_jll", "JLLWrappers", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] -git-tree-sha1 = "3282b7d16ae7ac3e57ec2f3fa8fafb564d8f9f7f" +git-tree-sha1 = "668ac0297e6bd8f4d53dfdcd3ace71f2e00f4a35" uuid = "337d8026-41b4-5cde-a456-74a10e5b31d1" -version = "1.10.1+0" +version = "1.11.1+0" [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] diff --git a/Project.toml b/Project.toml index bbbf9ec42..4bd017e4e 100644 --- a/Project.toml +++ b/Project.toml @@ -18,12 +18,10 @@ NCDatasets = "85f8d34a-cbdd-5861-8df4-14fed0d494ab" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" Polyester = "f517fe37-dbe3-4b94-8317-1923a5111588" ProgressLogging = "33c8b6b6-d38a-422a-b730-caa89a2f386c" -Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" TerminalLoggers = "5d786b92-1e48-4d6f-9151-6b4477ca9bed" -UnPack = "3a884ed6-31ef-47d7-9d2a-63182c4928ed" [compat] Aqua = "0.8" @@ -50,7 +48,6 @@ Statistics = "1" TOML = "1" TerminalLoggers = "0.1.5" Test = "1" -UnPack = "1" julia = "1.6" [extras] @@ -58,7 +55,8 @@ Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" Polynomials = "f27b6e38-b328-58d1-80ce-0feddd5e7a45" QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Aqua", "Downloads", "Polynomials", "QuadGK", "Test"] +test = ["Aqua", "Downloads", "Polynomials", "QuadGK", "Test", "Random"] diff --git a/src/Wflow.jl b/src/Wflow.jl index f4a8bd8f5..f4f898e0d 100644 --- a/src/Wflow.jl +++ b/src/Wflow.jl @@ -1,26 +1,25 @@ module Wflow -using Dates -using TOML -using Graphs -using NCDatasets -using StaticArrays -using Statistics -using UnPack -using Random -using BasicModelInterface -using FieldMetadata -using Parameters -using DelimitedFiles -using ProgressLogging +import BasicModelInterface as BMI + +using Base.Threads: nthreads +using CFTime: CFTime, monthday, dayofyear +using Dates: Dates, Second, Minute, Hour, Day, Month, year, TimeType, DatePeriod, TimePeriod, Date, DateTime, now, isleapyear, datetime2unix +using DelimitedFiles: readdlm +using FieldMetadata: @metadata +using Glob: glob +using Graphs: Graphs, Graph, DiGraph, add_edge!, is_cyclic, inneighbors, outneighbors, edges, topological_sort_by_dfs, src, dst, vertices, nv, ne, induced_subgraph, add_vertex! +using IfElse: IfElse using LoggingExtras +using LoopVectorization: @tturbo +using NCDatasets: NCDatasets, NCDataset, dimnames, dimsize, nomissing, defDim, defVar +using Parameters: @with_kw +using Polyester: @batch +using ProgressLogging: @progress +using StaticArrays: SVector, pushfirst, setindex +using Statistics: mean, median, quantile! using TerminalLoggers -using CFTime -using Base.Threads -using Glob -using Polyester -using LoopVectorization -using IfElse +using TOML: TOML @metadata get_units "mm dt-1" String # metadata for BMI grid @@ -28,7 +27,6 @@ using IfElse @metadata grid_type "unstructured" String @metadata grid_location "node" String -const BMI = BasicModelInterface const Float = Float64 const CFDataset = Union{NCDataset,NCDatasets.MFDataset} const CFVariable_MF = Union{NCDatasets.CFVariable,NCDatasets.MFCFVariable} @@ -213,7 +211,7 @@ function run_timestep(model::Model; update_func = update, write_model_output = t end function run(model::Model; close_files = true) - @unpack network, config, writer, clock = model + (; config, writer, clock) = model model_type = config.model.type::String diff --git a/src/bmi.jl b/src/bmi.jl index a2a7b9899..68093f03a 100644 --- a/src/bmi.jl +++ b/src/bmi.jl @@ -48,7 +48,6 @@ Update the model for a single timestep. - `run = nothing`: to update a model partially. """ function BMI.update(model::Model; run = nothing) - @unpack clock, network, config = model if isnothing(run) model = run_timestep(model) elseif run == "sbm_until_recharge" @@ -64,7 +63,6 @@ function BMI.update(model::Model; run = nothing) end function BMI.update_until(model::Model, time::Float64) - @unpack clock, network, config = model t = BMI.get_current_time(model) _div, _rem = divrem(time - t, model.clock.dt.value) steps = Int(_div) @@ -85,7 +83,7 @@ end "Write state output to netCDF and close files." function BMI.finalize(model::Model) - @unpack config, writer, clock = model + (; config, writer) = model # it is possible that the state dataset has been closed by `save_state` if !isnothing(writer.state_dataset) && isopen(writer.state_dataset) write_netcdf_timestep(model, writer.state_dataset, writer.state_parameters) @@ -95,8 +93,7 @@ function BMI.finalize(model::Model) end function BMI.get_component_name(model::Model) - @unpack config = model - return config.model.type + return model.config.model.type end function BMI.get_input_item_count(model::Model) @@ -115,7 +112,7 @@ This `API` sections contains a list of `Model` components for which variables ca exchanged. """ function BMI.get_input_var_names(model::Model) - @unpack config = model + (; config) = model if haskey(config, "API") var_names = Vector{String}() for c in config.API.components @@ -211,7 +208,7 @@ function BMI.get_var_location(model::Model, name::String) end function BMI.get_current_time(model::Model) - @unpack config = model + (; config) = model calendar = get(config, "calendar", "standard")::String starttime = cftime(config.starttime, calendar) return 0.001 * Dates.value(model.clock.time - starttime) @@ -222,7 +219,7 @@ function BMI.get_start_time(model::Model) end function BMI.get_end_time(model::Model) - @unpack config = model + (; config) = model calendar = get(config, "calendar", "standard")::String starttime = cftime(config.starttime, calendar) endtime = cftime(config.endtime, calendar) @@ -243,7 +240,7 @@ function BMI.get_value(model::Model, name::String, dest::Vector{T}) where {T<:Ab end function BMI.get_value_ptr(model::Model, name::String) - @unpack network = model + (; network) = model s = split(name, "[") key = symbols(first(s)) if exchange(param(model, key[1:end-1]), key[end]) == 1 @@ -322,9 +319,9 @@ function BMI.get_grid_rank(model::Model, grid::Int) end function BMI.get_grid_x(model::Model, grid::Int, x::Vector{T}) where {T<:AbstractFloat} - @unpack reader, config = model - @unpack dataset = reader - sel = active_indices(model.network, grids[grid]) + (; reader, network) = model + (; dataset) = reader + sel = active_indices(network, grids[grid]) inds = [sel[i][1] for i in eachindex(sel)] x_nc = read_x_axis(dataset) x .= x_nc[inds] @@ -332,9 +329,9 @@ function BMI.get_grid_x(model::Model, grid::Int, x::Vector{T}) where {T<:Abstrac end function BMI.get_grid_y(model::Model, grid::Int, y::Vector{T}) where {T<:AbstractFloat} - @unpack reader, config = model - @unpack dataset = reader - sel = active_indices(model.network, grids[grid]) + (; reader, network) = model + (; dataset) = reader + sel = active_indices(network, grids[grid]) inds = [sel[i][2] for i in eachindex(sel)] y_nc = read_y_axis(dataset) y .= y_nc[inds] @@ -350,7 +347,7 @@ function BMI.get_grid_size(model::Model, grid::Int) end function BMI.get_grid_edge_count(model::Model, grid::Int) - @unpack network = model + (; network) = model if grid == 3 return ne(network.river.graph) elseif grid == 4 @@ -365,7 +362,7 @@ function BMI.get_grid_edge_count(model::Model, grid::Int) end function BMI.get_grid_edge_nodes(model::Model, grid::Int, edge_nodes::Vector{Int}) - @unpack network = model + (; network) = model n = length(edge_nodes) m = div(n, 2) # inactive nodes (boundary/ghost points) are set at -999 @@ -402,7 +399,7 @@ function load_state(model::Model) end function save_state(model::Model) - @unpack config, writer, clock = model + (; config, writer) = model if haskey(config, "state") && haskey(config.state, "path_output") @info "Write output states to netCDF file `$(model.writer.state_nc_path)`." end diff --git a/src/flextopo_model.jl b/src/flextopo_model.jl index 039759940..8ea773d63 100644 --- a/src/flextopo_model.jl +++ b/src/flextopo_model.jl @@ -665,7 +665,7 @@ function initialize_flextopo_model(config::Config) end function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:FlextopoModel} - @unpack lateral, vertical, network, clock, config = model + (; lateral, vertical, network, config) = model inds_riv = network.index_river @@ -716,7 +716,7 @@ function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:FlextopoModel} end function set_states(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:FlextopoModel} - @unpack lateral, config = model + (; lateral, config) = model reinit = get(config.model, "reinit", true)::Bool # read and set states in model object if reinit=true if reinit == false diff --git a/src/flow.jl b/src/flow.jl index c778b9f6c..f7578cc03 100644 --- a/src/flow.jl +++ b/src/flow.jl @@ -189,7 +189,7 @@ end function update(sf::SurfaceFlowLand, network, frac_toriver) - @unpack graph, subdomain_order, topo_subdomain, indices_subdomain, upstream_nodes = + (; subdomain_order, topo_subdomain, indices_subdomain, upstream_nodes) = network ns = length(subdomain_order) @@ -256,7 +256,7 @@ end function update(sf::SurfaceFlowRiver, network, doy) - @unpack graph, subdomain_order, topo_subdomain, indices_subdomain, upstream_nodes = + (; graph, subdomain_order, topo_subdomain, indices_subdomain, upstream_nodes) = network ns = length(subdomain_order) @@ -429,7 +429,7 @@ end end function update(ssf::LateralSSF, network, frac_toriver, ksat_profile) - @unpack subdomain_order, topo_subdomain, indices_subdomain, upstream_nodes, area = + (; subdomain_order, topo_subdomain, indices_subdomain, upstream_nodes, area) = network @@ -749,7 +749,7 @@ end function shallowwater_river_update(sw::ShallowWaterRiver, network, dt, doy, update_h) - @unpack nodes_at_link, links_at_node = network + (; nodes_at_link, links_at_node) = network sw.q0 .= sw.q if !isnothing(sw.floodplain) @@ -952,8 +952,6 @@ function shallowwater_river_update(sw::ShallowWaterRiver, network, dt, doy, upda end function update(sw::ShallowWaterRiver{T}, network, doy; update_h = true) where {T} - @unpack nodes_at_link, links_at_node = network - if !isnothing(sw.reservoir) sw.reservoir.inflow .= 0.0 sw.reservoir.totaloutflow .= 0.0 @@ -1192,9 +1190,6 @@ function update( doy; update_h = false, ) where {T} - - @unpack nodes_at_link, links_at_node = network.river - if !isnothing(swr.reservoir) swr.reservoir.inflow .= 0.0 swr.reservoir.totaloutflow .= 0.0 @@ -1236,7 +1231,7 @@ function shallowwater_update( indices = network.land.staggered_indices inds_riv = network.land.index_river - @unpack nodes_at_link, links_at_node = network.river + (; links_at_node) = network.river sw.qx0 .= sw.qx sw.qy0 .= sw.qy @@ -1637,7 +1632,7 @@ function set_river_inwater( model::Model{N,L,V,R,W,T}, ssf_toriver, ) where {N,L,V,R,W,T<:Union{SbmModel,SbmGwfModel}} - @unpack lateral, vertical, network, config = model + (; lateral, vertical, network, config) = model inds = network.index_river do_water_demand = haskey(config.model, "water_demand") if do_water_demand @@ -1667,7 +1662,7 @@ end Set `inwater` of the lateral river component (based on overland flow). """ function set_river_inwater(model, ssf_toriver) - @unpack lateral, network = model + (; lateral, network) = model inds = network.index_river lateral.river.inwater .= lateral.land.to_river[inds] end @@ -1678,7 +1673,7 @@ end Set `inwater` of the lateral land component for the `SbmGwfModel` type. """ function set_land_inwater(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmGwfModel} - @unpack lateral, vertical, network, config = model + (; lateral, vertical, network, config) = model do_water_demand = haskey(config.model, "water_demand") do_drains = get(config.model, "drains", false)::Bool drainflux = zeros(vertical.n) @@ -1703,7 +1698,7 @@ end Set `inwater` of the lateral land component for the `SbmModel` type. """ function set_land_inwater(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmModel} - @unpack lateral, vertical, network, config = model + (; lateral, vertical, network, config) = model do_water_demand = haskey(config.model, "water_demand") if do_water_demand @. lateral.land.inwater = @@ -1722,7 +1717,7 @@ end Set `inwater` of the lateral land component, based on `runoff` of the `vertical` concept. """ function set_land_inwater(model) - @unpack lateral, vertical, network = model + (; lateral, vertical, network) = model @. lateral.land.inwater = (vertical.runoff * network.land.area * 0.001) / lateral.land.dt end @@ -1746,8 +1741,8 @@ Set inflow from the subsurface and land components to a water body (reservoir or function set_inflow_waterbody( model::Model{N,L,V,R,W,T}, ) where {N,L<:NamedTuple{<:Any,<:Tuple{Any,SurfaceFlow,SurfaceFlow}},V,R,W,T} - @unpack lateral, network = model - @unpack subsurface, land, river = lateral + (; lateral, network) = model + (; subsurface, land, river) = lateral inds = network.index_river if !isnothing(lateral.river.reservoir) || !isnothing(lateral.river.lake) @@ -1772,8 +1767,8 @@ Set inflow from the subsurface and land components to a water body (reservoir or function set_inflow_waterbody( model::Model{N,L,V,R,W,T}, ) where {N,L<:NamedTuple{<:Any,<:Tuple{Any,SurfaceFlow,ShallowWaterRiver}},V,R,W,T} - @unpack lateral, network = model - @unpack subsurface, land, river = lateral + (; lateral, network) = model + (; subsurface, land, river) = lateral inds = network.index_river if !isnothing(lateral.river.reservoir) || !isnothing(lateral.river.lake) @@ -1801,8 +1796,8 @@ Set inflow from the subsurface and land components to a water body (reservoir or function set_inflow_waterbody( model::Model{N,L,V,R,W,T}, ) where {N,L<:NamedTuple{<:Any,<:Tuple{Any,ShallowWaterLand,ShallowWaterRiver}},V,R,W,T} - @unpack lateral, network = model - @unpack subsurface, land, river = lateral + (; lateral, network) = model + (; subsurface, land) = lateral inds = network.index_river if !isnothing(lateral.river.reservoir) || !isnothing(lateral.river.lake) @@ -1820,7 +1815,7 @@ Run surface routing (land and river). Kinematic wave for overland flow and kinem local inertial model for river flow. """ function surface_routing(model; ssf_toriver = 0.0) - @unpack lateral, network, clock = model + (; lateral, network, clock) = model # run kinematic wave for overland flow set_land_inwater(model) @@ -1846,7 +1841,7 @@ function surface_routing( ssf_toriver = 0.0, ) where {N,L<:NamedTuple{<:Any,<:Tuple{Any,ShallowWaterLand,ShallowWaterRiver}},V,R,W,T} - @unpack lateral, vertical, network, clock = model + (; lateral, vertical, network, clock) = model @. lateral.land.runoff = ( (vertical.net_runoff / 1000.0) * network.land.area / vertical.dt + diff --git a/src/hbv_model.jl b/src/hbv_model.jl index 988dc4249..7c63bc252 100644 --- a/src/hbv_model.jl +++ b/src/hbv_model.jl @@ -385,9 +385,7 @@ function initialize_hbv_model(config::Config) end function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:HbvModel} - @unpack lateral, vertical, network, clock, config = model - - inds_riv = network.index_river + (; lateral, vertical, network, config) = model # vertical hbv concept is updated until snow state, after that (optional) # snow transport is possible @@ -412,7 +410,7 @@ function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:HbvModel} end function set_states(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:HbvModel} - @unpack lateral, config = model + (; lateral, config) = model reinit = get(config.model, "reinit", true)::Bool do_lakes = get(config.model, "lakes", false)::Bool @@ -422,7 +420,7 @@ function set_states(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:HbvModel} @info "Set initial conditions from state file `$instate_path`." set_states(instate_path, model; type = Float) # update kinematic wave volume for river and land domain - @unpack lateral = model + (; lateral) = model # makes sure land cells with zero flow width are set to zero q and h for i in eachindex(lateral.land.width) if lateral.land.width[i] <= 0.0 diff --git a/src/horizontal_process.jl b/src/horizontal_process.jl index 2d10333ce..5977a667c 100644 --- a/src/horizontal_process.jl +++ b/src/horizontal_process.jl @@ -274,7 +274,7 @@ such as that returned by `Graphs.topological_sort_by_dfs`. Returns the material state after transport. """ function accucapacitystate!(material, network, capacity) - @unpack graph, order = network + (; graph, order) = network for v in order downstream_nodes = outneighbors(graph, v) n = length(downstream_nodes) @@ -313,7 +313,7 @@ interface, and the order is a valid topological ordering such as that returned b Returns the flux (material leaving each cell), and material (left after transport). """ function accucapacityflux!(flux, material, network, capacity) - @unpack graph, order = network + (; graph, order) = network for v in order downstream_nodes = outneighbors(graph, v) n = length(downstream_nodes) diff --git a/src/io.jl b/src/io.jl index 16d89baed..3b7c63499 100644 --- a/src/io.jl +++ b/src/io.jl @@ -195,8 +195,8 @@ end mover_params = (symbols"vertical.precipitation", symbols"vertical.potential_evaporation") function load_fixed_forcing(model) - @unpack reader, network, config = model - @unpack forcing_parameters = reader + (; reader, network, config) = model + (; forcing_parameters) = reader do_reservoirs = get(config.model, "reservoirs", false)::Bool do_lakes = get(config.model, "lakes", false)::Bool @@ -241,8 +241,8 @@ end "Get dynamic netCDF input for the given time" function update_forcing!(model) - @unpack vertical, clock, reader, network, config = model - @unpack dataset, dataset_times, forcing_parameters = reader + (; clock, reader, network, config) = model + (; dataset, dataset_times, forcing_parameters) = reader do_reservoirs = get(config.model, "reservoirs", false)::Bool do_lakes = get(config.model, "lakes", false)::Bool @@ -331,8 +331,8 @@ end "Get cyclic netCDF input for the given time" function update_cyclic!(model) - @unpack vertical, clock, reader, network, config = model - @unpack cyclic_dataset, cyclic_times, cyclic_parameters = reader + (; clock, reader, network) = model + (; cyclic_dataset, cyclic_times, cyclic_parameters) = reader # pick up the data that is valid for the past model time step month_day = monthday(clock.time - clock.dt) @@ -1058,7 +1058,7 @@ end "Write a new timestep with scalar data to a netCDF file" function write_netcdf_timestep(model, dataset) - @unpack writer, clock, config = model + (; writer, clock, config) = model time_index = add_time(dataset, clock.time) for (nt, nc) in zip(writer.nc_scalar, config.netcdf.variable) @@ -1090,13 +1090,13 @@ end "Write a new timestep with grid data to a netCDF file" function write_netcdf_timestep(model, dataset, parameters) - @unpack vertical, clock, reader, network = model + (; clock, network) = model time_index = add_time(dataset, clock.time) buffer = zeros(Union{Float,Missing}, size(model.network.land.reverse_indices)) for (key, val) in parameters - @unpack par, vector = val + (; par, vector) = val sel = active_indices(network, par) # write the active cells vector to the 2d buffer matrix elemtype = eltype(vector) @@ -1128,8 +1128,8 @@ write_netcdf_timestep(model, dataset::Nothing) = model "Write model output" function write_output(model) - @unpack vertical, clock, reader, network, writer = model - @unpack dataset, dataset_scalar, parameters = writer + (; writer) = model + (; dataset, dataset_scalar, parameters) = writer write_csv_row(model) write_netcdf_timestep(model, dataset, parameters) @@ -1183,7 +1183,7 @@ end "Close input and output datasets that are opened on model initialization" function close_files(model; delete_output::Bool = false) - @unpack reader, writer, config = model + (; reader, writer, config) = model close(reader.dataset) if haskey(config.input, "cyclic") @@ -1307,7 +1307,7 @@ function reducer(col, rev_inds, x_nc, y_nc, config, dataset, fileformat) end function write_csv_row(model) - @unpack writer, clock, config = model + (; writer, clock, config) = model isnothing(writer.csv_path) && return nothing io = writer.csv_io print(io, string(clock.time)) @@ -1630,7 +1630,7 @@ end "Get `index` for dimension name `layer` or `classes` based on `model`" function get_index_dimension(var, model)::Int - @unpack vertical = model + (; vertical) = model if haskey(var, "layer") inds = collect(1:vertical.maxlayers) index = inds[var["layer"]] diff --git a/src/sbm_gwf_model.jl b/src/sbm_gwf_model.jl index 2dfe4a2ae..2e4913c83 100644 --- a/src/sbm_gwf_model.jl +++ b/src/sbm_gwf_model.jl @@ -526,7 +526,7 @@ end "update the sbm_gwf model for a single timestep" function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmGwfModel} - @unpack lateral, vertical, network, clock, config = model + (; lateral, vertical, network, config) = model do_water_demand = haskey(config.model, "water_demand") inds_riv = network.index_river diff --git a/src/sbm_model.jl b/src/sbm_model.jl index ec1032894..0ad9054f0 100644 --- a/src/sbm_model.jl +++ b/src/sbm_model.jl @@ -434,7 +434,7 @@ end "update SBM model for a single timestep" function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmModel} - @unpack lateral, vertical, network, clock, config = model + (; lateral, vertical, network, config) = model do_water_demand = haskey(config.model, "water_demand") ksat_profile = get(config.input.vertical, "ksat_profile", "exponential")::String @@ -465,7 +465,7 @@ Update SBM model until recharge for a single timestep. This function is also acc through BMI, to couple the SBM model to an external groundwater model. """ function update_until_recharge(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmModel} - @unpack lateral, vertical, network, clock, config = model + (; lateral, vertical, network, config) = model do_water_demand = haskey(config.model, "water_demand") @@ -511,7 +511,7 @@ accessible through BMI, to couple the SBM model to an external groundwater model function update_after_subsurfaceflow( model::Model{N,L,V,R,W,T}, ) where {N,L,V,R,W,T<:SbmModel} - @unpack lateral, vertical, network, clock, config = model + (; lateral, vertical) = model # update vertical sbm concept (runoff, ustorelayerdepth and satwaterdepth) update_after_subsurfaceflow( @@ -532,7 +532,7 @@ Update of the total water storage at the end of each timestep per model cell. This is done here at model level. """ function update_total_water_storage(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmModel} - @unpack lateral, vertical, network, clock, config = model + (; lateral, vertical, network) = model # Update the total water storage based on vertical states # TODO Maybe look at routing in the near future @@ -549,7 +549,7 @@ end function set_states( model::Model{N,L,V,R,W,T}, ) where {N,L,V,R,W,T<:Union{SbmModel,SbmGwfModel}} - @unpack lateral, vertical, network, config = model + (; lateral, vertical, network, config) = model reinit = get(config.model, "reinit", true)::Bool routing_options = ("kinematic-wave", "local-inertial") diff --git a/src/sediment.jl b/src/sediment.jl index dd708a32a..13a919114 100644 --- a/src/sediment.jl +++ b/src/sediment.jl @@ -1133,7 +1133,7 @@ function initialize_riversed(nc, config, riverwidth, riverlength, inds_riv) end function update(rs::RiverSediment, network, config) - @unpack graph, order = network + (; graph, order) = network tcmethod = get(config.model, "rivtransportmethod", "bagnold")::String # River sediment loads are separated into different particle class. diff --git a/src/sediment_model.jl b/src/sediment_model.jl index e2229a1bd..2c5d5ebd6 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -155,7 +155,7 @@ function initialize_sediment_model(config::Config) end function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SedimentModel} - @unpack lateral, vertical, network, clock, config = model + (; lateral, vertical, network, config) = model update_until_ols(vertical, config) update_until_oltransport(vertical, config) @@ -194,7 +194,7 @@ end function set_states(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SedimentModel} # read and set states in model object if reinit=false - @unpack config = model + (; config) = model reinit = get(config.model, "reinit", true)::Bool if reinit == false instate_path = input_path(config, config.state.path_input) diff --git a/src/utils.jl b/src/utils.jl index 1f557b1cb..b81248c9e 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -137,7 +137,7 @@ and set states in `model` object. Active cells are selected with the correspondi - `type = nothing`: type to convert data to after reading. By default no conversion is done. """ function set_states(instate_path, model; type = nothing, dimname = nothing) - @unpack network, config = model + (; network, config) = model # Check if required states are covered state_ncnames = check_states(config) diff --git a/src/water_demand.jl b/src/water_demand.jl index 32e364e0c..432db1b69 100644 --- a/src/water_demand.jl +++ b/src/water_demand.jl @@ -536,7 +536,7 @@ function update_water_allocation( model::Model{N,L,V,R,W,T}, ) where {N,L,V,R,W,T<:Union{SbmModel,SbmGwfModel}} - @unpack network, lateral, vertical = model + (; network, lateral, vertical) = model river = lateral.river index_river = network.land.index_river_wb diff --git a/test/io.jl b/test/io.jl index f692efed3..25e142de3 100644 --- a/test/io.jl +++ b/test/io.jl @@ -3,7 +3,6 @@ using Dates using TOML using CFTime using Random -using UnPack using LoggingExtras tomlpath = joinpath(@__DIR__, "sbm_config.toml") @@ -196,7 +195,7 @@ model = Wflow.initialize_sbm_model(config) Wflow.advance!(model.clock) Wflow.load_dynamic_input!(model) -@unpack vertical, clock, reader, writer = model +(; vertical, clock, reader, writer) = model @testset "output and state names" begin ncdims = ("lon", "lat", "layer", "time") @@ -242,8 +241,8 @@ end end @testset "network" begin - @unpack network = model - @unpack indices, reverse_indices = model.network.land + (; network) = model + (; indices, reverse_indices) = model.network.land # test if the reverse index reverses the index linear_index = 100 cartesian_index = indices[linear_index] @@ -252,7 +251,7 @@ end end @testset "initial parameter values" begin - @unpack vertical = model + (; vertical) = model @test vertical.cfmax[1] ≈ 3.7565300464630127 @test vertical.soilthickness[1] ≈ 2000.0 @test vertical.precipitation[49951] ≈ 2.2100000381469727 @@ -280,7 +279,7 @@ Wflow.advance!(model.clock) Wflow.load_dynamic_input!(model) @testset "changed parameter values" begin - @unpack vertical = model + (; vertical) = model @test vertical.cfmax[1] == 2.0 @test vertical.soilthickness[1] ≈ 2000.0 * 3.0 + 100.0 @test vertical.precipitation[49951] ≈ 1.5 * 2.2100000381469727 diff --git a/test/run_flextopo.jl b/test/run_flextopo.jl index cfb4bbffb..974fc085c 100644 --- a/test/run_flextopo.jl +++ b/test/run_flextopo.jl @@ -3,7 +3,7 @@ tomlpath = joinpath(@__DIR__, "flextopo_config.toml") config = Wflow.Config(tomlpath) model = Wflow.initialize_flextopo_model(config) -@unpack network = model +(; network) = model model = Wflow.run_timestep(model) @@ -82,7 +82,7 @@ config["model"]["select_fast"] = ["fast_no_storage", "fast_storage", "fast_stora config["model"]["select_slow"] = ["common_slow_storage"] model = Wflow.initialize_flextopo_model(config) -@unpack network = model +(; network) = model model = Wflow.run_timestep(model) diff --git a/test/run_hbv.jl b/test/run_hbv.jl index 12d6eb169..b0706f316 100644 --- a/test/run_hbv.jl +++ b/test/run_hbv.jl @@ -3,7 +3,7 @@ tomlpath = joinpath(@__DIR__, "hbv_config.toml") config = Wflow.Config(tomlpath) model = Wflow.initialize_hbv_model(config) -@unpack network = model +(; network) = model model = Wflow.run_timestep(model) diff --git a/test/run_sbm.jl b/test/run_sbm.jl index d50ccb752..fe81c53ec 100644 --- a/test/run_sbm.jl +++ b/test/run_sbm.jl @@ -4,7 +4,7 @@ tomlpath = joinpath(@__DIR__, "sbm_config.toml") config = Wflow.Config(tomlpath) model = Wflow.initialize_sbm_model(config) -@unpack network = model +(; network) = model model = Wflow.run_timestep(model) @@ -446,7 +446,7 @@ Wflow.close_files(model, delete_output = false) @testset "exponential profile" begin model = Wflow.initialize_sbm_model(config) - @unpack vertical = model + (; vertical) = model z = vertical.zi[i] kv_z = Wflow.hydraulic_conductivity_at_depth(vertical, z, i, 2, "exponential") @test kv_z ≈ vertical.kvfrac[i][2] * vertical.kv_0[i] * exp(-vertical.f[i] * z) @@ -465,7 +465,7 @@ Wflow.close_files(model, delete_output = false) @testset "exponential constant profile" begin config.input.vertical.ksat_profile = "exponential_constant" model = Wflow.initialize_sbm_model(config) - @unpack vertical = model + (; vertical) = model z = vertical.zi[i] kv_z = Wflow.hydraulic_conductivity_at_depth(vertical, z, i, 2, "exponential_constant") @@ -500,7 +500,7 @@ Wflow.close_files(model, delete_output = false) @testset "layered profile" begin config.input.vertical.ksat_profile = "layered" model = Wflow.initialize_sbm_model(config) - @unpack vertical = model + (; vertical) = model z = vertical.zi[i] @test Wflow.hydraulic_conductivity_at_depth(vertical, z, i, 2, "layered") ≈ vertical.kv[100][2] @@ -513,7 +513,7 @@ Wflow.close_files(model, delete_output = false) @testset "layered exponential profile" begin config.input.vertical.ksat_profile = "layered_exponential" model = Wflow.initialize_sbm_model(config) - @unpack vertical = model + (; vertical) = model z = vertical.zi[i] @test Wflow.hydraulic_conductivity_at_depth( vertical, diff --git a/test/run_sbm_gwf.jl b/test/run_sbm_gwf.jl index b984fed2b..3af207643 100644 --- a/test/run_sbm_gwf.jl +++ b/test/run_sbm_gwf.jl @@ -3,7 +3,7 @@ tomlpath = joinpath(@__DIR__, "sbm_gwf_config.toml") config = Wflow.Config(tomlpath) model = Wflow.initialize_sbm_gwf_model(config) -@unpack network = model +(; network) = model model = Wflow.run_timestep(model) @@ -144,7 +144,7 @@ config = Wflow.Config(tomlpath) config.model.reinit = false model = Wflow.initialize_sbm_gwf_model(config) -@unpack network = model +(; network) = model model = Wflow.run_timestep(model) model = Wflow.run_timestep(model) diff --git a/test/run_sediment.jl b/test/run_sediment.jl index 7d009bddc..f8b323996 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -4,7 +4,7 @@ tomlpath = joinpath(@__DIR__, "sediment_config.toml") config = Wflow.Config(tomlpath) model = Wflow.initialize_sediment_model(config) -@unpack network = model +(; network) = model model = Wflow.run_timestep(model) diff --git a/test/runtests.jl b/test/runtests.jl index 357608a23..67947e377 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,7 +6,6 @@ using NCDatasets using StaticArrays using Statistics using Test -using UnPack using Wflow using Base.MathConstants: eulergamma using Base.Threads diff --git a/test/subdomains.jl b/test/subdomains.jl index bb5517640..12b5170b3 100644 --- a/test/subdomains.jl +++ b/test/subdomains.jl @@ -3,7 +3,7 @@ config = Wflow.Config(tomlpath) model = Wflow.initialize_sbm_model(config) -@unpack network = model +(; network) = model min_sto_river = get(config.model, "min_streamorder_river", 6) min_sto_land = get(config.model, "min_streamorder_land", 5) From 48af51366100d1913c275737ece69c4e12f4f93a Mon Sep 17 00:00:00 2001 From: aweerts Date: Wed, 30 Oct 2024 10:32:59 +0100 Subject: [PATCH 24/27] Update params_lateral.md (#491) --- docs/src/model_docs/params_lateral.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/model_docs/params_lateral.md b/docs/src/model_docs/params_lateral.md index 6456500e8..b2321b674 100644 --- a/docs/src/model_docs/params_lateral.md +++ b/docs/src/model_docs/params_lateral.md @@ -104,8 +104,8 @@ locs = "wflow_reservoirlocs" | `dt` | model time step | s | - | | `volume` | volume | m``^3`` | - | | `inflow` | total inflow into reservoir | m``^3`` | - | -| `outflow` | outflow into reservoir | m``^3`` s``^{-1}`` | - | -| `totaloutflow` | total outflow into reservoir | m``^3`` | - | +| `outflow` | outflow of reservoir | m``^3`` s``^{-1}`` | - | +| `totaloutflow` | total outflow of reservoir | m``^3`` | - | | `percfull` | fraction full (of max storage) | - | - | | `precipitation` | average precipitation for reservoir area | mm Δt⁻¹ | - | | `evaporation` | average potential evaporation for reservoir area | mm Δt⁻¹ | - | From c9e5b8720c914f54a4181b696039fd9b2774c030 Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Mon, 4 Nov 2024 11:12:44 +0100 Subject: [PATCH 25/27] Refactor `SBM` concept (#487) * Remove vertical concepts `HBV` and `FLEXTopo` (#433) * Remove vertical concept `FLEXTopo` * Remove vertical concept `HBV` * Update download test data for build * Cleanup docs Removed `HBV` and `FLEXTopo` concepts. * Removed code related to `FLEXTopo` Use of extra dim `classes`. * Remove and change river and land `inwater` functions As a result of removing `HBV` and `FLEXTopo` concepts. * Update changelog * Fix typo in docs Co-authored-by: JoostBuitink <44062204+JoostBuitink@users.noreply.github.com> --------- Co-authored-by: JoostBuitink <44062204+JoostBuitink@users.noreply.github.com> * Cleanup metadata macros (#434) * Cleanup metadata structs Remove `exchange`, `grid_type` and `grid_location` from metadata structs. `grid_type` is not required, it is already implemented as part of BMI. `exhange` and `grid_location` are now implemented without the use of the FieldMetadata package. * Update changelog * Fix `exchange` function for `ShallowWaterLand` * Add tests * Move `exchange` and `grid_location` functions To file bmi.jl as these functions are quite specific to BMI functionality. * Add `v1` branch to CI * Refactor/style guide (#437) * Add configuration file for Julia Formatter * Add format on save in vscode settings file * Remove .vscode from gitignore * Format all Julia files * Sync wflow's style guide with ribasim's style guide * Update formatting * Update .vscode/settings.json * Remove outdated comments * Add workaround for VS Code bugs * WIP: refactor `SBM` interception * WIP: add `AtmosphericForcing` to store meteo input * WIP: remove unused type parameter `A` from `SBM` * WIP: add `RutterInterceptionModel` * WIP: refactor `SBM` snow * Stop using local JULIAUP_DEPOT_PATH (#438) (#440) * Stop using local JULIAUP_DEPOT_PATH * Avoid error when override is already set Co-authored-by: Martijn Visser * WIP: refactor `SBM` glacier * WIP: introduce types `NoSnowModel` and `NoGlacierModel` * WIP: `model` in `update` functions and add wrapper methods In `update` functions use `model` for interception, snow and glacier models. Add wrapper methods for snow runoff and glacier melt. * WIP: start refactor soil model of `SBM` (initialization) * Update of `SBM` soil model (until recharge) * Update run of refactored sbm concept (sbm_model) This is with kinematic wave routing. Renamed the model for `SBM` from `soil` to `bucket` as it is more related to this type of model (Simple Bucket Model) than a soil model that for example solves the Richards equation for the unsaturated zone and upper part of the saturated zone. * Delete `soil` folder * Add `SBM` functions to seperate file (bucket_process.jl) Also removed the vertical_process.jl file. * Add water demand functionality * Rename `vegetation_parameters` to `vegetation_parameter_set` * Fix test Wflow ZMQ Server * Refactor update of `LandHydrologySBM` Add water demand and allocation, and snow transport computations. * Further refactor of update `LandHydrologySBM` Add separate functions to update the boundary conditions of the snow and `SBM` models. * Rename `bucket` to `soil` and add `SurfaceRunoff` model In addition: - add a parameter set for LandParameters (to share accross model components). - make use of type NamedTuple (for external models) in the update of models and boundary conditions, these external models provide input to the models and boundary conditions. - move shared parameters (vegetation and land) to file Parameters.jl. * Include `atmospheric_forcing` as a separate argument In update of models and boundaries of `LandHydrologySBM` * Remove land_parameter_set It only contains two parameters that are not shared that much across different models of `LandHydrologySBM`. * Rename outer constructors and couple of structs Use the struct name for outer constructor functions. * Add parameter `soil_fraction` to `SbmSoilParameters` * Move function for `soil_fraction` to soil.jl * Rename model type `SurfaceRunoff` to `OpenWaterRunoff` * Refactor water demand For more efficient and cleaner code: 1) Support dispatching on types that represent non-existent Demand (e.g. NoDemand) or Allocation (e.g. NoAllocationLand) models is supported. 2) Add more wrapper functions to access variables of water demand and allocation structs. * Move runoff,jl to separate folder * Refactor update `SbmSoilModel` Add functions for different processes that are part of the `SbmSoilModel` to improve code readability. * Use types for hydraulic conductivity profiles And reduce allocations. * Rename `surface_water_flux` to `water_flux_surface` * Small updates * Boundary conditions `SbmSoilModel` Change computation of `water_flux_surface` and `potential_soilevaporation` to represent fluxes for actual soil boundary by moving paddy evaporation and infiltration to the `update_boundary_conditions!` function. * FIx some non-concrete types * Delete log.txt file * Refactor water demand Split structs into variables and parameters and moved demand variables of `AllocationLand` to `Demand`. * Add docstrings to snow model * Add docstrings glacier model * Add docstrings canopy and rename `interception_flux` variable * Add docstrings open water runoff model * Add docstrings SBM soil model * Add docstrings water demand And group structs and associated functions. * Add comment about metadata and BMI (Wflow.jl) * Add docstrings to vegetation parameters * Add docstrings atmospheric forcing * Cleanup metadata `get_units` and `grid_loc` Only specify for variables that are exposed by BMI. Additionally added docstrings for `LandHydrologySBM` and simplified initialization of `LandHydrologySBM`. * Update docstrings and comments * Fix ZMQ Server tests * Ksat_profile tests from PR #474 So we can close PR #474 as the fix is already part if this branch. * Update changelog * Update docstrings * Add variable `snow_melt` to `SnowVariables` * Update changelog * Format Wflow.jl * Suggestions from code review for water_demand Co-authored-by: Bart de Koning <74617371+SouthEndMusic@users.noreply.github.com> * Address remaining review comments water_demand * Remaining suggestions from code review Co-authored-by: Bart de Koning <74617371+SouthEndMusic@users.noreply.github.com> * Address review comments Simplify external constructors. * Address review comment Rewrite without explicit loop. * Address review comment and bug fix -Use consistently `!` for functions that are mutating, and return always `nothing` for these functions. - Fix missing `canonicalize` from `Dates` * Fix typo Co-authored-by: JoostBuitink <44062204+JoostBuitink@users.noreply.github.com> * Address remaining comments 2nd reviewer * Fix wflow server tests * Revert back to using `isnothing` As of Julia 1.7 there is no difference and `isnothing` is also used in other code sections. --------- Co-authored-by: JoostBuitink <44062204+JoostBuitink@users.noreply.github.com> Co-authored-by: Carlos Fernando Baptista Co-authored-by: Martijn Visser Co-authored-by: Bart de Koning <74617371+SouthEndMusic@users.noreply.github.com> --- .JuliaFormatter.toml | 12 + .github/workflows/CI.yml | 1 + .github/workflows/CIWflowServer.yml | 1 + .gitignore | 3 - .vscode/settings.json | 9 + build/create_binaries/download_test_data.jl | 7 +- build/wflow_cli/test/runtests.jl | 18 +- docs/make.jl | 2 - docs/src/changelog.md | 26 + docs/src/images/flextopo_julia_1class.png | Bin 63360 -> 0 bytes docs/src/images/flextopo_julia_3class.png | Bin 118098 -> 0 bytes docs/src/images/hbv-soilmoist.png | Bin 8785 -> 0 bytes docs/src/images/hbv-upper.png | Bin 4945 -> 0 bytes docs/src/images/hbv96.png | Bin 142910 -> 0 bytes docs/src/model_docs/model_configurations.md | 130 +- docs/src/model_docs/params_vertical.md | 157 -- docs/src/model_docs/shared_concepts.md | 8 +- docs/src/model_docs/structures.md | 2 +- docs/src/model_docs/vertical/flextopo.md | 357 ----- docs/src/model_docs/vertical/hbv.md | 195 --- docs/src/user_guide/additional_options.md | 13 +- docs/src/user_guide/intro.md | 14 +- docs/src/user_guide/model-setup.md | 11 - docs/src/user_guide/sample_data.md | 38 - docs/src/user_guide/step1_requirements.md | 16 +- docs/src/user_guide/step2_settings_file.md | 26 +- server/src/WflowServer.jl | 8 +- server/src/bmi_service.jl | 2 +- server/src/server.jl | 14 +- server/test/client.jl | 61 +- server/test/runtests.jl | 12 +- server/test/sbm_config.toml | 100 +- src/Wflow.jl | 106 +- src/bmi.jl | 133 +- src/demand/water_demand.jl | 934 +++++++++++ src/flextopo.jl | 882 ----------- src/flextopo_model.jl | 739 --------- src/flow.jl | 699 ++++----- src/forcing.jl | 23 + src/glacier/glacier.jl | 150 ++ src/glacier/glacier_process.jl | 56 + src/groundwater/aquifer.jl | 85 +- src/groundwater/boundary_conditions.jl | 23 +- src/groundwater/connectivity.jl | 5 +- src/hbv.jl | 239 --- src/hbv_model.jl | 446 ------ src/horizontal_process.jl | 126 +- src/io.jl | 202 ++- src/logging.jl | 4 +- src/parameters.jl | 104 ++ src/reservoir_lake.jl | 40 +- src/sbm.jl | 1543 ++----------------- src/sbm_gwf_model.jl | 105 +- src/sbm_model.jl | 217 ++- src/sediment.jl | 92 +- src/sediment_model.jl | 27 +- src/snow/snow.jl | 178 +++ src/snow/snow_process.jl | 89 ++ src/soil/soil.jl | 1302 ++++++++++++++++ src/soil/soil_process.jl | 294 ++++ src/states.jl | 146 +- src/subdomains.jl | 23 +- src/surfacewater/runoff.jl | 145 ++ src/utils.jl | 411 +++-- src/vegetation/canopy.jl | 153 ++ src/vegetation/rainfall_interception.jl | 95 ++ src/vertical_process.jl | 439 ------ src/water_demand.jl | 626 -------- test/bmi.jl | 80 +- test/flextopo_config.toml | 523 ------- test/groundwater.jl | 93 +- test/hbv_config.toml | 137 -- test/horizontal_process.jl | 24 +- test/io.jl | 99 +- test/reservoir_lake.jl | 110 +- test/run.jl | 9 +- test/run_flextopo.jl | 116 -- test/run_hbv.jl | 66 - test/run_sbm.jl | 254 ++- test/run_sbm_gwf.jl | 63 +- test/run_sbm_gwf_piave.jl | 74 +- test/run_sbm_piave.jl | 80 +- test/run_sediment.jl | 20 +- test/runtests.jl | 14 +- test/sbm_config.toml | 100 +- test/sbm_gw.toml | 74 +- test/sbm_gwf_config.toml | 35 +- test/sbm_gwf_piave_demand_config.toml | 123 +- test/sbm_piave_config.toml | 96 +- test/sbm_piave_demand_config.toml | 127 +- test/sbm_simple.toml | 56 +- test/sbm_swf_config.toml | 73 +- test/subdomains.jl | 2 +- test/testing_utils.jl | 41 +- test/vertical_process.jl | 27 +- 95 files changed, 6106 insertions(+), 8534 deletions(-) create mode 100644 .JuliaFormatter.toml create mode 100644 .vscode/settings.json delete mode 100644 docs/src/images/flextopo_julia_1class.png delete mode 100644 docs/src/images/flextopo_julia_3class.png delete mode 100644 docs/src/images/hbv-soilmoist.png delete mode 100644 docs/src/images/hbv-upper.png delete mode 100644 docs/src/images/hbv96.png delete mode 100644 docs/src/model_docs/vertical/flextopo.md delete mode 100644 docs/src/model_docs/vertical/hbv.md create mode 100644 src/demand/water_demand.jl delete mode 100644 src/flextopo.jl delete mode 100644 src/flextopo_model.jl create mode 100644 src/forcing.jl create mode 100644 src/glacier/glacier.jl create mode 100644 src/glacier/glacier_process.jl delete mode 100644 src/hbv.jl delete mode 100644 src/hbv_model.jl create mode 100644 src/parameters.jl create mode 100644 src/snow/snow.jl create mode 100644 src/snow/snow_process.jl create mode 100644 src/soil/soil.jl create mode 100644 src/soil/soil_process.jl create mode 100644 src/surfacewater/runoff.jl create mode 100644 src/vegetation/canopy.jl create mode 100644 src/vegetation/rainfall_interception.jl delete mode 100644 src/vertical_process.jl delete mode 100644 src/water_demand.jl delete mode 100644 test/flextopo_config.toml delete mode 100644 test/hbv_config.toml delete mode 100644 test/run_flextopo.jl delete mode 100644 test/run_hbv.jl diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml new file mode 100644 index 000000000..4b8f03a0c --- /dev/null +++ b/.JuliaFormatter.toml @@ -0,0 +1,12 @@ +# Options for the JuliaFormatter auto syntax formatting tool. +# https://domluna.github.io/JuliaFormatter.jl/stable/ +# https://docs.sciml.ai/SciMLStyle/stable/ + +# Based on the default style we do pick these non-default options from SciML style: +whitespace_ops_in_indices = true +remove_extra_newlines = true +always_for_in = true +whitespace_typedefs = true + +# And add other options we like: +separate_kwargs_with_semicolon = true diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 868c5ee46..60504c78b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - v1 tags: '*' jobs: test: diff --git a/.github/workflows/CIWflowServer.yml b/.github/workflows/CIWflowServer.yml index a051bcf52..126361940 100644 --- a/.github/workflows/CIWflowServer.yml +++ b/.github/workflows/CIWflowServer.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - v1 tags: '*' jobs: test: diff --git a/.gitignore b/.gitignore index 604242d59..aff6d881c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,6 @@ # General .DS_Store -# VS Code stuff -.vscode - # Packaging stuff *.jl.*.cov *.jl.cov diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..c24c4ee94 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "[julia]": { + "editor.formatOnSave": true, + }, + "julia.lint.disabledDirs": [ + ".pixi" + ], + "julia.lint.run": true, +} diff --git a/build/create_binaries/download_test_data.jl b/build/create_binaries/download_test_data.jl index 3ff5f5437..339e19b91 100644 --- a/build/create_binaries/download_test_data.jl +++ b/build/create_binaries/download_test_data.jl @@ -19,12 +19,8 @@ end staticmaps_rhine_path = testdata(v"0.1", "staticmaps.nc", "staticmaps-rhine.nc") staticmaps_moselle_path = - testdata(v"0.2.8", "staticmaps-moselle.nc", "staticmaps-moselle.nc") -staticmaps_lahn_path = testdata(v"0.2.1", "staticmaps-lahn.nc", "staticmaps-lahn.nc") -staticmaps_meuse_path = - testdata(v"0.2.8", "staticmaps_flex_meuse.nc", "staticmaps_flex_meuse.nc") + testdata(v"0.2.9", "staticmaps-moselle.nc", "staticmaps-moselle.nc") forcing_moselle_path = testdata(v"0.2.6", "forcing-moselle.nc", "forcing-moselle.nc") -forcing_lahn_path = testdata(v"0.2", "forcing-lahn.nc", "forcing-lahn.nc") forcing_moselle_sed_path = testdata(v"0.2.3", "forcing-moselle-sed.nc", "forcing-moselle-sed.nc") staticmaps_moselle_sed_path = @@ -42,7 +38,6 @@ forcing_sbm_gw_path = testdata( "forcing-sbm-groundwater-part2.nc", "forcing-sbm-groundwater-part2.nc", ) -forcing_meuse_path = testdata(v"0.2.8", "forcing_meuse.nc", "forcing_meuse.nc") staticmaps_sbm_gw_path = testdata(v"0.2.3", "staticmaps-sbm-groundwater.nc", "staticmaps-sbm-groundwater.nc") instates_sbm_gw_path = diff --git a/build/wflow_cli/test/runtests.jl b/build/wflow_cli/test/runtests.jl index b68f71f6d..aada6ffb5 100644 --- a/build/wflow_cli/test/runtests.jl +++ b/build/wflow_cli/test/runtests.jl @@ -2,7 +2,8 @@ using Test using Wflow extension = Sys.iswindows() ? ".exe" : "" -wflow_exe = normpath(@__DIR__, "../../create_binaries/wflow_bundle/bin/wflow_cli" * extension) +wflow_exe = + normpath(@__DIR__, "../../create_binaries/wflow_bundle/bin/wflow_cli" * extension) # this assumes that the Wflow tests have already been run, so the data has been downloaded testdir = abspath(dirname(pathof(Wflow)), "..", "test") @@ -14,7 +15,7 @@ outputdir = joinpath(datadir, "output") @testset "no_config" begin # Clean output directory - rm(outputdir; force=true, recursive=true) + rm(outputdir; force = true, recursive = true) @test_throws ProcessFailedException run(`$wflow_exe`) # Check if no files are being created @test !(isdir(outputdir)) @@ -22,7 +23,7 @@ end @testset "wflow_sbm" begin # Clean directory - rm(outputdir; force=true, recursive=true) + rm(outputdir; force = true, recursive = true) # Run cli with the toml toml = normpath(testdir, "sbm_config.toml") run(`$wflow_exe $toml`) @@ -43,7 +44,7 @@ end @testset "wflow_sbm-gwf" begin # Clean directory - rm(outputdir; force=true, recursive=true) + rm(outputdir; force = true, recursive = true) # Run cli with the toml toml = normpath(testdir, "sbm_gwf_config.toml") run(`$wflow_exe $toml`) @@ -63,7 +64,7 @@ end @testset "wflow_sediment" begin # Clean directory - rm(outputdir; force=true, recursive=true) + rm(outputdir; force = true, recursive = true) # Run cli with the toml toml = normpath(testdir, "sediment_config.toml") run(`$wflow_exe $toml`) @@ -82,11 +83,12 @@ end end @testset "wflow_sbm_timing" begin - toml = normpath(testdir, "sbm_config.toml") - time_sbm_1thread = @elapsed run(Cmd(`$wflow_exe $toml`, env=("JULIA_NUM_THREADS" => "1",))) + time_sbm_1thread = + @elapsed run(Cmd(`$wflow_exe $toml`; env = ("JULIA_NUM_THREADS" => "1",))) - time_sbm_4thread = @elapsed run(Cmd(`$wflow_exe $toml`, env=("JULIA_NUM_THREADS" => "4", ))) + time_sbm_4thread = + @elapsed run(Cmd(`$wflow_exe $toml`; env = ("JULIA_NUM_THREADS" => "4",))) # Test if run with more threads is indeed faster @test time_sbm_4thread < time_sbm_1thread diff --git a/docs/make.jl b/docs/make.jl index a710b5fb3..22dd23a5d 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -23,8 +23,6 @@ pages = [ "model_docs/model_configurations.md", "Vertical concepts" => [ "model_docs/vertical/sbm.md", - "model_docs/vertical/hbv.md", - "model_docs/vertical/flextopo.md", "model_docs/vertical/sediment.md", "model_docs/shared_concepts.md", ], diff --git a/docs/src/changelog.md b/docs/src/changelog.md index de9f4cffb..72e5ae536 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Fixed +- Initialization of `LateralSSF` variables `ssf` and `ssfmax` with vertical hydraulic + conductivity profile `exponential_constant`. Removed parameter `khfrac` from the + computation, as it is already part of parameter `kh_0`. + +### Changed +- Removed vertical concepts `HBV` and `FLEXTopo`. +- Removed metadata macros `exchange` and `grid_type`. The macro `grid_type` is not required + because this functionality is already part of `BMI`. The macro `exchange` is replaced by a + function used by `BMI`. Remaining metadata macros `get_units` and `grid_loc` are only used + by `BMI`. +- Refactor the vertical `SBM` concept: divide the long struct `SBM` into different model + components for interception, snow, glacier, (open water) runoff, soil, water demand and + allocation stored in the struct `LandHydrologySBM`. Additionally, the atmospheric forcing + and a shared vegetation parameterset are stored as separate fields in struct + `LandHydrologySBM` (with soil model `SbmSoilModel`). The model component structs have + model `variables`, `parameters` and `boundary_conditions` (if applicable), including + associated functions for initializing and updating these model components. The original + long update function of the `SBM` soil part has been split into separate functions. + +### Added +- Support direct output of snow and glacier melt, and add computation of snow water + equivalent (SWE). + ## v0.8.1 - 2024-08-27 ### Fixed diff --git a/docs/src/images/flextopo_julia_1class.png b/docs/src/images/flextopo_julia_1class.png deleted file mode 100644 index cb662a324738943d168f9714967765783b566001..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63360 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 za0�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 diff --git a/docs/src/images/hbv-soilmoist.png b/docs/src/images/hbv-soilmoist.png deleted file mode 100644 index 1e6e3ed7d7b53a2a483c098d8c5421f533db10cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8785 zcmbVycT`hNyKfK$6ai7HH0jctbOD8c(nSJD2_Qv!k=_Cb(tGcq^dh16-aCj8dT#;= z(i0$oT;A_J^}F|+d+z;X)?PDfX4c+&Ju|=ZJYnzD-Vi-_`Tzg`5GlR|z6Sts-rhD% zf;+b((-wDL-ac@hWfegL1O$ug>T9>%`;KpQodE#i-oF}75*INI0PqZ;2z(9l$UInf zfA!=y7XL{7@#AhXj@;aLWGD({x^ImQH+So_^y{LV9YKrD);0}VHn39If|3oLQJpKS zjcYu-hV$M%>Ts2L9Kk=?s|?TOgYPgkz4Yw3c`&rS-o);?db$MuBTj7^3GnB0GSvY9 zIOzk3IvMZM_{$N9074$w0*D#s0JO4|IAULoaUBV|@YWvvpC$vCh zSi?%hAjgDZG0~$&KladF={axA5Gxr!&AJDKnrABA9W*5w1jta{P)e4A26od8}x)sx4>+za#te0?2U*YTL4cB*$%8|R%NvMV{wM^Q%C zNrsm&{@*cctnr{2RiI!}();iAnkgUnlIfnrvpnCf?sfn@C-;mwBXMh|tmHEExskcu z=4E`z0XN}`n(A>H&BX<6H`Y-!vqxAy1%kadGGlQEX}8mY*bNrQh9PJo7pU1EQEw3Q zU0xI|S~1Fup^cBfhcr)*d^tR~Jeh{p#rk|)WU$EVaq|)R-J(gVBLbOgNSi3?7)L_q zEr+Y%8FzxCbIAE?Z8~Dqq#qOVdP)dfmgIieNvb=Q(G@FLg-e^>krUqQ(+cKFUn4KW zYL)7qq{%NM%x1vJMv9buXI!hU4icDIM;fq9bX|U zu;7ND6&Gcw{rt*pU+PGGd_7-$gj=J}QQmA98O<|o`CBl@9xI1(l^?!5WcK)k&KWUd zg)n^ccz;l(2(5N=tSlGuK=wx4)6+?SCL+~%yjnpYHxc{AoG&@CF+ow9b{6=kl|V@^ z5^DG?c2gA|++r344*M0ZtEj8)a7;+X`f>?<&fBD}DM`&?aHR@Qyy`d?oj>I=4F{dC z-k4m5C)qU6tw=-FRz7lRwSC-#8|b>fcs)IUA-~c~S)c%Fm+wd~yEt(bEoj-k#H^h7 z!txF=4j69iim#c?cLjZS`BM-Y1uf@(Kkg^&`3YUolFI=@;hZRn%mD?1`Zn7>r4^Ki z?*(lXY9{FxlTC2T+cTz&Yy*K#i|N`a?N^TA#te5U8xv4tBPQ47xWT0EL4m9SFdqsV z)OeCq%%f_qk}}$UUTFwbVq$vVzAy8=>)bUYmq{(i`*Ire^E7>Yq0LoQKmf5HcpE#) z_ZG39+b?Ubos};k{sSzuhzI@gSXxPpagqAYL+khQuMyNWyj)rP;`aq+P3k-knYE&J z?6s4$RGH%zVOA&MYzVK`?7>{5zv37cTzArN&KGbV+R-T}MWzSj` zUN$7F-ppxUWk=i{v-@|ZOroY z43E))i&tk+KYxIws25mB*bCIiDYF1JHUOc7MpQe} zy{MDh*dfvpq`t2>D<^fQu>L)zK6OI>L%qP~TkbyG*Gnf1x!Vtz-cKWhn#Qt>?Cb1w zRMkK}Qvyt2lMlJsZ{Ie0tn8GR;3|+=y|Br}E_8f#x-RB^Q^^>c9cdQ1vpRobri_>_S~m zhz@%OGy<7(aja@l{RgvM6(YgLf}lHCpYuV?c!%kTMwY5p7Yez3{S+q7SavOCORLvf zgN(UnD7-i-k#06O^X+UgdQJ&+nl(N8<}pHp;esQl@wO7`jGSO&gyOB+@A_>}xtKj}Crg zjP8d%AbVpi_nDta1@RcTC zX9Rrwur!q&@u0bse~&K!L5T%>=n-sRiC zKfxrYsp03$J71InaXmbT9YF?cTv+YS#<6QA;gH|)pi>Hje7qwOiHInmY7>t_x3XtDsheblc;H;f}HC z3*z`bDlQ}7CA?pm+%_{5PxE-AWy;58lPVmy!LX7+WKYh1`@V?6rm@psy{6c_0ld?b z+bQ=NNaGK-vKed<#<&%Rtu!gN^&|79%FgD1;i4tx1KEM~f;>>yxPBXkU)fm|zzNhH z*!P@I){{YD;8hVbR6?^~(gv}m83q=?VHAMu9? zgRIxM{SbGF$)nMR%FLtX)$+NoyD~=F|r^o$@fXj~s+J!+}TT$eF zyX4QGU+)?Z=xOp=Q<c;NsJQQz!m;_BZxddQr!fN+)scQAcDjBq26)fb3f=_7 z&5*JHx}c!o5O!3*h;LrG+)a>dz>Y?JwHMtRLX&iwvchXKVUw%IF6nXIDn-2@5eBOWH7WaMo0;c1Q0=f%&~b zi#lZe2Acf5cUuI*hJO2Y!2hfciOV=Ic(Ye*O&`v88zws+oRZS)N7`gGx(~JH98lPi z9KEZqEyHIJ*+GZOW63(45I*)>{?VsGKh`+h1$_wVb z-Y<=8OvZ2`1#L^fVOjWHP$2j92{L2++N+V1LFQv)hiO-H?M+UM&9`Bq#VW3nLM^ke zt=F~NHosA&R<9H3s%1v&IxNVQ+G^3hHCP*KxkL!uk3CVUxsfcjEczP7dH?=vzry-c z2V3(1{YZ-#kesELDTRbsv+)#a^MRH3)?)Fx0!hx!E5R;kAv9wP-3+ zx~lay5ehV^(0Fd7Pp#3{aldGATZJx99q()dai5%BAriT9NyDr}vCZ);@4o18oL;T$ z_FtC;Ex+|ba=a)OQ!$~m-RUC2 z2F$k?FpV=h92zrP6}5gR+HwET1r$NTo2gCwVH0Y$H?)`EcG_wFqai5iIy0sXkLX}c z#wFApZfL3i(yYwK8j^4RBBDs^YOqCjd?7)y8o$r7Z7dd4$xVBIMBL0ffghg}^C{gPBP$qxSc~08vDhndMG@Fv!s7nl!s49>V#qwB zA39j`?B7*KVp{LO`oB!_2q>VZo%x2ovMe!t%3f$dfbpI5aNxsM1jHv`y)&68bjiB# zYPzbKiFuG^YzZ82FA}O7WbDdHh3iuZcbm5}37_jmq32&?EL_TY#~Ab?{*%~Ps@%C| zk8wY|KT82_K0BpPwpxGSX(}(^b?<^LZifdfET|q`Y_i_Ml_b!g`lqX%dMv4Qwxdb+ z4+S}7&$#=RuqzbcIGn1~>Hn$hs3VZ$tJfzHf6c5KJr4ySt+HElK`>5mgnjs|o0G}+M&HuHz@(-J1qFlsFQT&49;<|J;gpBuME%70W2 zQ$sNgq(KkFJyBb~ZlZ_XXf}z`u=W`SIvVcdX=`@QXpvK}n@bPXgf)^#;UIFvn`@-C z+U32ej22Lz~^Sq$Ei%YH$!uTn;;&-A=K4>e%RY>H;MFjp6R0Qv?bMp+oeAvQAB9I zyU}UBEb?Yoq1!x3uD`((umOx<@$VB!iNcr<^GeGuLv(Z_VuHnbW#p&_#C#sxh6op_;L+D~spe$Gf}it*HT4OwYYX$(_$ds?p5k zhv3#34Bg_i!N*^eShi3{>=rg4-a*V=O22&FYBM^FpD?gxXHHY@u*zbKF?8R~AZ|Br z#km_mw4?gA$yq(mqfTCxoOFmAbSWe^!n5Vnlo@(9M9MLf&1^AdWvvK24SUY+VIf=J z)fpN}<|bU0V&LV=$QsMRO^=oPi`^I8kNlgto3Dtx0++?*?Z}$&YETD8L2je0SyF#y zrC8!Z$|&S9rG~bv*6L!NjM5t*OW|Dxf=Th_#r`a@$0f{6g1z{3tr_~w*iRoz__ucH zbHGoC`sgb)C1B2vO`v@{S=}c2rISnLh$%(pIb-cq?Yt2g7akVudEcDF6n@2rB{;b_ zY24pj8wgY(J8$w*V5wMf1=Ykh*-5H>Xo5`TihoH%*4tkzh_s*f9g&F8OJYtj;vsgK zJc`izhPN?_YGGk2e6h0_+0f9pd%O1J$G_83xAeX14OCPj)W;^_wO0e!SOK=(drXA0 zuQerE>{lFF6XIfBq^6mTt+{NNO#UYQ%oJV*zrwYTy28SJ{_SW(e1(Ck7dI97Vx+ot zwEWh;YD=CdT%uJ!xE>Gjs07v8$dFX1iGV3{qSc$5KZ5Myg|n)dUnrZY&`{Q1EhBk_ z4QVb9jzc8+sYW1<8!Zj`;0=1H}10gQ$hUwPO{o(ZW@wa zY732~P9w>IpCA$~IZsL?eSO8a%?=IJ2}@<{EE*Neg1<7KUR|6qoUTmYXEj+@uw8ik|FDEcndYcM0-H+mM??Yx14oV({C}OEoODyXWRQ`n zPf1CsDg!(t$K0g>oP2(4>oN&T zj5r{Q_vyFi2_$YBp%Q?QE# z@KofPC159?3_Z6H%aCF?J^#VPowgJ7lDS;P+ipI8(t%|Er`3Ah(r_6mptaH>vcf#M zeATGo^Q)w#MYzs-C$&EZM_+VJ^Ubr4ha)LQ#JFPSD?HzqsxI$cyqJw#gZB zHa|bXKQH6^-{-u+=X6JwfAD3}Y^->KKHr2s-#d&dG1h6Id&z|HHqIwZ8Z2bJGG}dW6 z)zHuoS7)`=-M)v+h%X1FmLH1cVPT_7ogOb+L}vs(aqTbT^e0W-T0Z~Ucpc%cl~dz; zRz_j6Kkq;pTx{chgmP%&xj5u0dMrT#O><8$l0$?^tp}72eS2*<&dEGW_01{GEQTjv z#^Yiw$4~PvlP7F81o>Grezdz(JEe=BbdeYcKc)C3pG{yl4J}l%`m*=43z2G8sr9D~ zEs=HUaghk~llsXCKifNiGBE|!94^|?AKXa22nl_TMo#7Ff0@1 zuP%n!05|c`vifdrT95C01%8{2nBXDt7h>~lj(AIXj`ns~IXSntOgbJM_FE@Ahe@@J zXO+SVBWxyu>E56(!?LnCm=V1zLdW8JXkr69#Gx*cMQdl|AeCtW7^EE>MA$|;3+f)( z+1}P_KV(l)aeht=nD}o^=?(U|+bHOV-th+=J+TE0hWzh(lQK2cy)8HOGYa#aF2l`G zxd-uhho~!=d-)o7RVCwO-NFd+_{h($tr_Aa+>*~)!#fI>@q$BeZ~#9mKNX|xT5OFCqmLBarAsGp-@;D_}0NQxes6`x$~ zW=gU%88pA(MPYu(?#nF7V0V%;;Dol+ukJju4ZHpS*HIKzHlKK9a+uOT?Lh?DMZ zOgOBz{vKRk^)oo>ITn+OOHH8iHJSVYYj6q+ed~QVZ5N`(Q!0J=Ax-4Z;n-<#lpzC@ znW^U*93c_}AAHneI^06TePX9(msU{1c%EFLrL2l4la& zLUb1Lc$3(pO}gKNJaOH_((8(^_+8zg*IQ(sN?WT^{Rw3Q6V!urr;$e0CSUu;(-O47 z<1Nequ^tx*dy8c4*IwsHxc(->-Ns=jc4?uhR81!@uqYuQ)P-amTxSX2&h*1RNi!d- zzX_x%1QSh|Y~BwwPuzwbjzPw_o1ulOEL3}CrLMWgZ^IHRbRT``$*>wG0*tRmu}4en}F*19n1- zX!?DaX0|(hQ+6}Nm`vSj-PduzPUNWdIBeePsa9aGR--(9uxAs&)bc@`0zvfZnc zL=~6Q?;l(B_}*AnjC6kDDDu;=G}sEXVV}I>7K&WrpX1QO(J~e7{F7A~hNfSJo!f4eG2O7c+IYHp2N; z$rfG~oMJ&rdk$7P2{e?x{H9pcZ$FcM@dm{gB<5;uQq+O^{l#F_9?JQco*uK;npIK6 zTwp*nY`N069RHRQf&rx`Z1>=w>^O{PN*1;y1oYOz2l7g54;I@ch?|t?19)C96!)~K zE7;sd)}YFac%8kjDu-q%OoZ54JTBG6`ua86edX{fJkyCuZQ0r0g5lU9{8jqTCjxao z;9ncnInrU}@5+Si<(u8lD$C#ZNL((hlvUI^So5)8=M{sRqQa~5Cg`NxJFHuR=TQv7 znbBoaRn}+ApfA8K<5-yGo^D6F5_+wF%p)t+75>4tW)fn}IKu{Dh(DuxTyZz+@-tfU zJ7fV?H4-)9X#vBxvkiS3nLdBopy@~7Y!P?y(|ril1}_aw6;YMDN(B~u;USmxd0)Sv zonWm2yWShCyS*PF!=WNbr>4TIvN0dLGb>vzW~Q=WTuC#Yv{TQWe0P0NjdUXNm5a*+ zIoW&z^AAWf7YNx?=$4HftDLmBy7Y2IGW^s_qVeHwbz@M+Kf71xpF@M^;pw?ve|P7h z=)>m(8AkuZgxh}$mH(rZ-G8|QcO)3c{{eHt(C`&S|J+D%*d*gzCyAk^9`UV{$Q}?F z7zMs}|Me~Uo?(nBmmT7U%(my!aQu}tOCbmh74yV12;T+Iw&|O;O#BI!>XX|m7-aUl zbdJ5tr*$wTAp;7(?D`a`=wo*pZ*YX;pSF4GLfLF11Zp2^2KHx@Ltnn>E104n;3w-H z9-q_l)pcwp^p`7mP;X^7_cIDNv1uS#Z;;f~KbM~jf9;V&mjXWVs{#_Y6#@h6&7+qD zLa!KG&*uQIGVjrVZ%L;vYb2EQCIYn9AK{l~UOoI4$XcAAlc+6gw72=l_~!Plsq0VfJFm{j;I*o#{cq{LlYvV0pFgT{kyaAuldwWQu=#5 zPdBET94clF$OO~sRwq?VX9ubHg&#Wx735?JUt-ptu!qdX+Vsql{Ba+h6BZ;so9iTp zAo`S#bCnpGnMS;rWoV{enIkxl?eHqHrAL-%1~Uv|)|=v63+|!LrXFW7F7SHG0O( z!8G-T+o9x#ddqUtjF6{W;&l@(NqEv=_BkTw;#nbjuZ%n*seullAy}GOXt^4m^nTRx0hZ%*9G9i z8O<<7c0b~9+Xnj~;ijYFDhFJ!m)fr=*@@lqr?XI#&2>~Vsx0~imZsxe?uMYE!(&Q< zJ>^Gbn}(hM7l-mU&XSMc(}-`hlFW3=3L==E&%OfHI`YI7eAdV5!h?pUX^>tmoEsgW z`X)m~{hrQyZcKCd5laa6N{ch~EjVxe5Le|1vy>*aa>#mq_OL1+J6z@F0M>k&T2i|1 zvR6YA<4(U0p17iAR-k}EuA2N>4xRRmbG#|1q~5r>B`37MUGERM{kHH}N89B%k1wHS z^<(`N;7JW?S1t|p;Zo?nx9cPg>22}A?q9V4Eyy=}p`pErYq*TfOcR!&p`rJgn3+EP z9L}QAXJ8Qhw_Mus#Ri}2DYY^_+>P6?b!XJ(OAGEkM@ee8aKG;5Eniqu>2Ip&kVt?2 zh;&cWM9{_!ep4F01#5}6rVwnf8@SKa;Gp+{oquYjc~Hoj=|APGs$g;s{rw6rRT?3d zMwZ(4)`%?p`E+F?-pKtIULQKzaa(QpahW*_9qTmo{X-p(9$w_OwA6BtmMlv?q`lek zUoo9EP^3IDob10>?%U9&95t)p>y7yiZM5j^<)H(qN%fCsqHj_eH)jne$ed-h(cW`n zwH_7+41}%L^=YmzhGJ=|=g{PE_LfRFI_XPEIc}&UbB}ojAf_t6T>5-wEb`&gjSm}w zSA(jA#I;v0*3Mi^8Yy1aAt-f}o@hLpVIZ`>A2#QgtMPyENL_$L;3O*57=vajCiwkQOkD6a;D${Gj!FC%p&_W%F@ diff --git a/docs/src/images/hbv-upper.png b/docs/src/images/hbv-upper.png deleted file mode 100644 index 4aae2eb1237829314a8d3f4c7f802bbdadc472f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4945 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 F{t0+`{DJ@g diff --git a/docs/src/model_docs/model_configurations.md b/docs/src/model_docs/model_configurations.md index 814ddbfd2..074082107 100644 --- a/docs/src/model_docs/model_configurations.md +++ b/docs/src/model_docs/model_configurations.md @@ -131,128 +131,6 @@ lateral.land => struct ShallowWaterLand{T} The local inertial approach is described in more detail in the section [Local inertial model](@ref local_inertial). -## [wflow\_hbv](@id config_hbv) -The Hydrologiska Byrans Vattenbalansavdelning (HBV) model was introduced back in 1972 by the -Swedish Meteological and Hydrological Institute (SMHI). The HBV model is mainly used for -runoff simulation and hydrological forecasting. The model is particularly useful for -catchments where snow fall and snow melt are dominant factors, but application of the model -is by no means restricted to these type of catchments. - -The model is based on the HBV-96 model. However, the hydrological routing represented in HBV -by a triangular function controlled by the MAXBAS parameter has been removed. Instead, the -kinematic wave function is used to route the water downstream. All runoff that is generated -in a cell in one of the HBV reservoirs is added to the kinematic wave reservoir at the end -of a timestep. There is no connection between the different HBV cells within the model. - -A catchment is divided into a number of grid cells. For each of the cells individually, -daily runoff is computed through application of the HBV-96 of the HBV model. The use of the -grid cells offers the possibility to turn the HBV modelling concept, which is originally -lumped, into a distributed model. - -![wflow_hbv model](../images/hbv96.png) - -The figure above shows a schematic view of hydrological response simulation with the -HBV-modelling concept. The land-phase of the hydrological cycle is represented by three -different components: a snow routine, a soil routine and a runoff response routine. Each -component is discussed in more detail below. - -The vertical HBV concept is described in section [HBV vertical concept](@ref vert_hbv). The -routing for river and overland flow is described in the section [Kinematic wave](@ref -kin_wave). - -Below the mapping for wflow\_hbv (type `hbv`) to the vertical HBV concept (instance of -`struct HBV`) and the different lateral concepts. For an explanation about the type -parameters between curly braces after the `struct` name see the section on model parameters. - -```julia -vertical => struct HBV{T} -lateral.subsurface => struct LateralSSF{T} -lateral.land => struct SurfaceFlow{T,R,L} -lateral.river => struct SurfaceFlow{T,R,L} -lateral.river.lake => struct NaturalLake{T} # optional -lateral.river.reservoir => struct SimpleReservoir{T} # optional -``` - -## [wflow\_flextopo](@id config_flextopo) -The FLEXTopo model is a process-based model, which consists of different parallel classes -connected through their groundwater storage. These classes are usually delineated from -topographical data to represent the variability in hydrological processes across -user-defined Hydrological Response Units (HRU). The main assumption underlying the concept, -which was first introduced by Savenije (2010), is that different parts of the landscape -fulfill different tasks in runoff generation and, hence, can be represented by different -model structures. The strength of the concept is that the definition of classes and -associated model structures is modular and flexible and not fixed to a predefined model -structure. The flexible approach allows to develop process-based models for different -topographic, climatic, geologic and land use conditions, making use of the available data -and expert knowledge. - -The kinematic wave function is used to route the water downstream. In a similar way as for -HBV, all runoff that is generated in a cell in one of the FLEXTopo storages is added to the -kinematic wave reservoir at the end of a timestep. There is no connection between the -different vertical FLEXTopo cells within the model. The FLEXTopo model is implemented in a -fully distributed way in the wflow Julia framework. - -In wflow\_flextopo, the user is free to determine the number of classes and which model -components to include or exclude for each class, this is done in the TOML file. Currently, -for each storage, it is possible to bypass the storage and pass on the fluxes to the next -model component. Interested users can contribute to the code by adding other -conceptualizations for each storage components. - -```julia -[model] -type = "flextopo" -classes = ["h", "p", "w"] #user can set the number and name of each class. - -# for each component which is class specific, the user can select which conceptualization -# to apply for each class as defined above in classes = ["h", "p", "w"] -select_snow = ["common_snow_hbv"] -# available options are ["common_snow_hbv", "common_snow_no_storage"] -select_interception = ["interception_overflow", "interception_overflow", "interception_overflow"] -# available options are ["interception_overflow", "interception_no_storage"] -select_hortonponding = ["hortonponding_no_storage", "hortonponding_no_storage", "hortonponding_no_storage"] -# available options are ["hortonponding", "hortonponding_no_storage"] -select_hortonrunoff = ["hortonrunoff_no_storage", "hortonrunoff_no_storage", "hortonrunoff_no_storage"] -# available options are ["hortonrunoff", "hortonrunoff_no_storage"] -select_rootzone = ["rootzone_storage", "rootzone_storage", "rootzone_storage"] -# available options are ["rootzone_storage", "rootzone_no_storage"] -select_fast = ["fast_storage", "fast_storage", "fast_storage"] -# available options are ["fast_storage", "fast_no_storage"] -select_slow = ["common_slow_storage"] -# available options are ["common_slow_storage", "slow_no_storage"] -``` - -A schematic representation of the most complete model structure including all storage -components, as currently implemented in the code, is shown in the Figure below. When setting -up the model with multiple classes, model structures can be adapted by bypassing storages or -turning parameter values on or off (e.g.: percolation or capillary rise, non-linear versus -linear outflow of the fast runoff etc.), an example of a three class model is shown in -[FLEXTopo vertical concept](@ref vert_flextopo). - -![flextopo_julia_1class.png](../images/flextopo_julia_1class.png) -*Schematic representation of the FLEXTopo model for a single class model including all -storages and fluxes. Main parameters are denoted in red.* - -In the staticmaps, the user needs to provide maps of the fraction of each class within each -cell, as shown below with `hrufrac`. For each model parameter which is class specific, an -extra dimension `classes` is required in the staticmaps netcdf. For an example model, see -[FLEXTopo example model](@ref wflow_flextopo_data). - -```julia -[input.vertical] -hrufrac = "hrufrac_lu" -``` - -Parameter multiplication of model parameters which are defined for several classes is -possible through the TOML file: - -```julia -[input.vertical.kf] -netcdf.variable.name = "kf" -scale = [1.0, 3.0, 4.0] -offset = [0.0, 0.0, 0.0] -class = ["h", "p", "w"] -``` - ## [wflow\_sediment](@id config_sediment) The processes and fate of many particles and pollutants impacting water quality at the catchment level are intricately linked to the processes governing sediment dynamics. Both @@ -292,8 +170,7 @@ required dynamic inputs to run wflow\_sediment are: - River water level in the kinematic wave, - Rainfall interception by the vegetation. -These inputs can be obtained from other wflow models such as wflow\_sbm, wflow\_hbv or from -other sources. +These inputs can be obtained from wflow\_sbm or from other sources. Model outputs can be saved for both the inland and the instream part of the model. Some examples are listed below. @@ -331,7 +208,4 @@ Bedconc = "Bedconc" ## References + Köhler, L., Mulligan, M., Schellekens, J., Schmid, S., Tobón, C., 2006, Hydrological impacts of converting tropical montane cloud forest to pasture, with initial reference to - northern Costa Rica. Final Technical Report DFID‐FRP Project No. R799. - -+ Savenije, H. H. G. (2010). HESS opinions “topography driven conceptual modelling (FLEX-Topo).” - Hydrology and Earth System Sciences, 14(12), 2681–2692. https://doi.org/10.5194/hess-14-2681-2010 + northern Costa Rica. Final Technical Report DFID‐FRP Project No. R799. \ No newline at end of file diff --git a/docs/src/model_docs/params_vertical.md b/docs/src/model_docs/params_vertical.md index 12e52bcd6..0202719cd 100644 --- a/docs/src/model_docs/params_vertical.md +++ b/docs/src/model_docs/params_vertical.md @@ -148,163 +148,6 @@ profile `kv` is used and `z_layered` is required as input. | `industry` | optional industry water demand of type `NonIrrigationDemand` | - | - | | `allocation` | optional water allocation of type `AllocationLand` | - | - | -## [HBV](@id params_hbv) -The Table below shows the parameters (fields) of struct `HBV`, including a description of -these parameters, the unit, and default value if applicable. The parameters in bold -represent model parameters that can be set through static and forcing input data (netCDF), -and can be listed in the TOML configuration file under `[input.vertical]`, to map the -internal model parameter to the external netCDF variable. - -| parameter | description | unit | default | -|:---------------| --------------- | ---------------------- | ----- | -| **`cfmax`** | degree-day factor | mm ᵒC``^{-1}`` Δt``^{-1}`` | 3.75653 mm ᵒC``^{-1}`` day``^{-1}`` | -| **`tt`** | threshold temperature for snowfall| ᵒC | -1.41934 | -| **`tti`** | threshold temperature interval length | ᵒC | 1.0 | -| **`ttm`** | threshold temperature for snowmelt | ᵒC | -1.41934 | -| **`whc`** | water holding capacity as fraction of current snow pack | - | 0.1 | -| **`g_tt`** | threshold temperature for snowfall above glacier | ᵒC| 0.0 | -| **`g_cfmax`** | Degree-day factor for glacier | mm ᵒC``^{-1}`` Δt``^{-1}``| 3.0 mm ᵒC``^{-1}`` day``^{-1}`` | -| **`g_sifrac`** | fraction of the snowpack on top of the glacier converted into ice | Δt``^{-1}``| 0.001 day``^{-1}`` | -| **`glacierfrac`** | fraction covered by a glacier | - | 0.0 | -| **`glacierstore`** | water within the glacier | mm | 5500.0 | -| **`fc`** | field capacity | mm | 260.0 | -| **`betaseepage`** | exponent in soil runoff generation equation | - | 1.8 | -| **`lp`** | fraction of field capacity below which actual evaporation=potential evaporation | - | 0.53 | -| **`k4`** | recession constant baseflow | Δt``^-1`` | 0.02307 day``^{-1}`` | -| **`kquickflow`** | recession constant upper reservoir | Δt``^-1`` | 0.09880 day``^{-1}`` | -| **`suz`** | Level over which `k0` is used | mm | 100.0 | -| **`k0`** | recession constant upper reservoir | Δt``^-1`` | 0.30 day``^{-1}`` | -| **`khq`** | recession rate at flow `hq`| Δt``^-1`` | 0.09880 day``^{-1}`` | -| **`hq`** | high flow rate hq for which recession rate of upper reservoir is known | mm Δt``^-1`` | 3.27 mm day``^{-1}`` | -| **`alphanl`** | measure of non-linearity of upper reservoir | - | 1.1 | -| **`perc`** | percolation from upper to lower zone | mm Δt``^-1`` | 0.4 mm day``^{-1}`` | -| **`cfr`** | refreezing efficiency constant in refreezing of freewater in snow | - | 0.05 | -| **`pcorr`** | correction factor for precipitation | - | 1.0 | -| **`rfcf`** | correction factor for rainfall | - | 1.0 | -| **`sfcf`** | correction factor for snowfall | - | 1.0 | -| **`cflux`** | maximum capillary rise from runoff response routine to soil moisture routine | mm Δt``^-1`` | 2.0 mm day``^{-1}`` | -| **`icf`** | maximum interception storage (in forested and non-forested areas) | mm | 2.0 | -| **`cevpf`** | correction factor for potential evaporation | - | 1.0 | -| **`epf`** | exponent of correction factor for evaporation on days with precipitation | mm``^{-1}`` | 1.0 | -| **`ecorr`** | evaporation correction | - | 1.0 | -| **`precipitation`** | precipitation | mm Δt``^-1`` | - | -| **`temperature`** | temperature | ᵒC | - | -| **`potential_evaporation`** | potential evapotranspiration | mm Δt``^-1`` | - | -| `potsoilevap` | potential soil evaporation | mm Δt``^-1`` | - | -| `soilevap` | soil evaporation | mm Δt``^-1`` | - | -| `intevap` | evaporation from interception storage | mm Δt``^-1`` | - | -| `actevap` | actual evapotranspiration (intevap + soilevap) | mm Δt``^-1`` | - | -| `interceptionstorage` | actual interception storage | mm | - | -| `snowwater` | available free water in snow | mm | - | -| `snow` | snow pack | mm | - | -| `rainfallplusmelt` | snow melt + precipitation as rainfall | mm Δt``^-1`` | - | -| `soilmoisture` | actual soil moisture | mm | - | -| `directrunoff` | direct runoff to upper zone | mm Δt``^-1`` | - | -| `hbv_seepage` | recharge to upper zone | mm Δt``^-1`` | - | -| `in_upperzone` | water inflow into upper zone | mm Δt``^-1`` | - | -| `upperzonestorage` | water content of the upper zone | mm | - | -| `quickflow` | specific runoff (quickflow part) | mm Δt``^-1`` | - | -| `real_quickflow` | specific runoff (quickflow), if K upper zone is precalculated | mm Δt``^-1`` | - | -| `percolation` | actual percolation to the lower zone | mm Δt``^-1`` | - | -| `capflux` | capillary rise | mm Δt``^-1`` | - | -| `lowerzonestorage` | water content of the lower zone | mm | - | -| `baseflow` | specific runoff (baseflow part) per cell | mm Δt``^-1`` | - | -| `runoff` | total specific runoff per cell | mm Δt``^-1`` | - | - - -## [FLEXtopo](@id params_flextopo) -The Table below shows the parameters (fields) of struct `FLEXTOPO`, including a description -of these parameters, the unit, and default value if applicable. The parameters in bold -represent model parameters that can be set through static and forcing input data (netCDF), -and can be listed in the TOML configuration file under `[input.vertical]`, to map the -internal model parameter to the external netCDF variable. - -| parameter | description | unit | default | -|:---------------| --------------- | ---------------------- | ----- | -| **`cfmax`** | degree-day factor | mm ᵒC``^{-1}`` Δt``^{-1}`` | 3.75653 mm ᵒC``^{-1}`` day``^{-1}`` | -| **`tt`** | threshold temperature for snowfall| ᵒC | -1.41934 | -| **`tti`** | threshold temperature interval length | ᵒC | 1.0 | -| **`ttm`** | threshold temperature for snowmelt | ᵒC | -1.41934 | -| **`whc`** | water holding capacity as fraction of current snow pack | - | 0.1 | -| **`cfr`** | refreezing efficiency constant in refreezing of freewater in snow | - | 0.05 | -| **`g_tt`** | threshold temperature for snowfall above glacier | ᵒC| 0.0 | -| **`g_cfmax`** | Degree-day factor for glacier | mm ᵒC``^{-1}`` Δt``^{-1}``| 3.0 mm ᵒC``^{-1}`` day``^{-1}`` | -| **`g_sifrac`** | fraction of the snowpack on top of the glacier converted into ice | Δt``^{-1}`` | 0.001 day``^{-1}`` | -| **`glacierfrac`** | fraction covered by a glacier | - | 0.0 | -| **`glacierstore`** | water within the glacier | mm | 5500.0 | -| **`ecorr`** | evaporation correction | - | 1.0 | -| **`pcorr`** | correction factor for precipitation | - | 1.0 | -| **`rfcf`** | correction factor for rainfall | - | 1.0 | -| **`sfcf`** | correction factor for snowfall | - | 1.0 | -| **`imax`** | maximum interception storage (``I_\mathrm{max}``) | mm | 3.0 | -| **`shmax`** | maximum horton ponding storage capacity (``S_\mathrm{Hmax}``) | mm | 30.0 | -| **`srmax`** | maximum root zone storage capacity (``S_\mathrm{Rmax}``) | mm | 260.0 | -| **`beta`** | exponent in soil runoff generation equation | - | 0.3 | -| **`lp`** | fraction of root zone capacity below which actual evaporation=potential evaporation (``L_\mathrm{P}``) | - | 0.3 | -| **`ks`** | recession constant slow groundwater storage (``K_\mathrm{S}``) | Δt``^-1`` | 0.006 day``^{-1}`` | -| **`kf`** | recession constant fast storage (``K_\mathrm{F}``) | Δt``^-1`` | 0.1 day``^{-1}`` | -| **`khf`** | recession constant horton runoff storage (``K_\mathrm{Hf}``) | Δt``^-1`` | 0.5 day``^{-1}`` | -| **`alfa`** | measure of non-linearity of upper reservoir (``\alpha``) | - | 1.0 | -| **`perc`** | maximum percolation flux from root zone to slow storage (``Q_\mathrm{perc,max}``) | mm Δt``^-1``| 0.30 mm day``^{-1}`` | -| **`cap`** | maximum capillary rise from slow storage to root zone (``Q_\mathrm{cap,max}``)| mm Δt``^-1`` | 0.20 mm day``^{-1}`` | -| **`ds`** | splitter parameter determining fraction of root zone outflow to slow storage (``d_\mathrm{s}``) | - | 0.2 | -| **`shmin`** | minimum storage capacity in horton ponding (relative to ``S_\mathrm{Hmax}``) (``S_\mathrm{Hmin}``) | [-] | 0.2 | -| **`facc0`** | maximum modelled accumulated frost resulting in `shmin` (``F_\mathrm{acc,fr0}``) | [ᵒC Δt] | -3.0 | -| **`facc1`** | minimum modelled accumulated frost resulting in `shmin` (``F_\mathrm{acc,fr1}``) | [ᵒC Δt] | 0.0 | -| **`fdec`** | exponent for the decline of infiltration capacity (``F_\mathrm{dec}``) | [-] | 0.2 | -| **`fmax`** | maximum infiltration capacity from horton ponding (``F_\mathrm{max}``) | [mm Δt``^-1``] | 2.0 | -| **`kmf`** | melt coefficient of frozen topsoil (``K_\mathrm{mf}``) | [-] | 1.0 | -| **`hrufrac`** | fraction of class within cell (``F_\mathrm{hrufrac}``)| - | 1/length(classes) | -| **`precipitation`** | precipitation | mm Δt``^-1`` | - | -| **`temperature`** | temperature | ᵒC | - | -| **`potential_evaporation`** | potential evapotranspiration | mm Δt``^-1`` | - | -| `precipcorr` | corrected precipitation | mm Δt``^-1`` | - | -| `epotcorr` | corrected potential evaporation | mm Δt``^-1`` | - | -| `snow` | snow water (``S_\mathrm{W}``) | mm | - | -| `snowwater` | available free water in snow | mm | - | -| `interceptionstorage` | interception storage (``S_\mathrm{I}``) | mm | - | -| `interceptionstorage_m` | average interception storage over classes (``S_\mathrm{I}``) | mm | - | -| `hortonpondingstorage` | horton ponding storage (``S_\mathrm{Hp}``) | mm | - | -| `hortonpondingstorage_m` | average horton ponding storage over classes (``S_\mathrm{Hp}``) | mm | - | -| `hortonrunoffstorage` | horton runoff storage (``S_\mathrm{Hf}``) | mm | - | -| `hortonrunoffstorage_m` | average horton runoff storage over classes (``S_\mathrm{Hf}``) | mm | - | -| `rootzonestorage` | root zone storage (``S_\mathrm{R}``) | mm | - | -| `rootzonestorage_m` | average root zone storage over classes (``S_\mathrm{R}``) | mm | - | -| `faststorage` | fast storage (``S_\mathrm{F}``) | mm | - | -| `faststorage_m` | average fast storage over classes (``S_\mathrm{F}``) | mm | - | -| `slowstorage` | slow storage (``S_\mathrm{S}``) | mm | - | -| `potsoilevap` | potential soil evaporation (``E_\mathrm{P}``) | mm Δt``^-1`` | - | -| `soilevap` | soil evaporation | mm Δt``^-1`` | - | -| `intevap` | evaporation from interception storage (``E_\mathrm{I}``) | mm Δt``^-1`` | - | -| `intevap_m` | average evaporation from interception storage over classes (``E_\mathrm{I}``) | mm Δt``^-1`` | - | -| `hortonevap` | evaporation from horton ponding storage (``E_\mathrm{H}``) | mm Δt``^-1`` | - | -| `hortonevap_m` | average evaporation from horton ponding storage over classes (``E_\mathrm{H}``) | mm Δt``^-1`` | - | -| `rootevap` | evaporation from root zone storage (``E_\mathrm{R}``) | mm Δt``^-1`` | - | -| `rootevap_m` | average evaporation from root zone storage over classes (``E_\mathrm{R}``) | mm Δt``^-1`` | - | -| `actevap` | actual evapotranspiration (intevap + hortonevap + rootevap) (``E_\mathrm{A}``) | mm Δt``^-1`` | - | -| `actevap_m` | average actual evapotranspiration (intevap + hortonevap + rootevap) over classes (``E_\mathrm{A}``) | mm Δt``^-1`` | - | -| `precipeffective` | Effective precipitation (``P_\mathrm{E}``) | mm Δt``^-1`` | - | -| `rainfallplusmelt` | snow melt + precipitation as rainfall (``P_\mathrm{M} + P_\mathrm{R}``) | mm Δt``^-1`` | - | -| `snowmelt` | snowfall | mm Δt``^-1`` | - | -| `snowfall` | snowfall | mm Δt``^-1`` | - | -| `facc` | modeled accumulated frost | ᵒC Δt | - | -| `qhortonpond` | Flux from the hortonian ponding storage to the hortonian runoff storage (``Q_\mathrm{H}``) | mm Δt``^-1`` | - | -| `qhortonrootzone` | Flux from the hortonian ponding storage to the root zone storage (``Q_\mathrm{HR}``) | mm Δt``^-1`` | - | -| `qhortonrun` | Flux from the hortonian runoff storage (``Q_\mathrm{Hf}``) | mm Δt``^-1`` | - | -| `qrootzone` | Flux from the root zone storage (``Q_\mathrm{R}``) | mm Δt``^-1`` | - | -| `qrootzonefast` | Pref. recharge to fast storage (``Q_\mathrm{RF}``) | mm Δt``^-1`` | - | -| `qrootzoneslow_m` | Pref. recharge to slow storage sum classes (``Q_\mathrm{RS}``) | mm Δt``^-1`` | - | -| `qcapillary` | Capillary flux from the slow to the root-zone storage (``Q_\mathrm{cap}``) | mm Δt``^-1`` | - | -| `qcapillary_m` | Capillary flux from the slow to the root-zone storage sum classes (``Q_\mathrm{cap}``) | mm Δt``^-1`` | - | -| `qpercolation` | Percolation flux from the root-zone to the slow storage (``Q_\mathrm{perc}``) | mm Δt``^-1`` | - | -| `qpercolation_m` | Percolation flux from the root-zone to the slow storage sum classes (``Q_\mathrm{perc}``) | mm Δt``^-1`` | - | -| `qfast` | runoff from fast storage (``Q_\mathrm{F}``) | mm Δt``^-1`` | - | -| `qfast_tot` | sum of fast runoff (from fast and horton runoff storages) over classes | mm Δt``^-1`` | - | -| `qslow` | runoff from slow storage (``Q_\mathrm{S}``) | mm Δt``^-1`` | - | -| `runoff` | total specific runoff per cell (qslow + qfast_tot) (``Q``) | mm Δt``^-1`` | - | -| `wb_tot` | total water balance | mm Δt``^-1`` | - | - - ## [Sediment](@id params_sediment) The Table below shows external parameters that can be set through static input data diff --git a/docs/src/model_docs/shared_concepts.md b/docs/src/model_docs/shared_concepts.md index 5596e1f78..2d8e514c7 100644 --- a/docs/src/model_docs/shared_concepts.md +++ b/docs/src/model_docs/shared_concepts.md @@ -48,10 +48,10 @@ exceeds `whc`, either through snow melt or incoming rainfall, the surplus water ### Glacier modelling -Glacier processes can be modelled if the snow model is enabled. For the vertical HBV concept -snow modelling is not optional. Glacier modelling is very close to snow modelling and -considers two main processes: glacier build-up from snow turning into firn/ice (using the -HBV-light model) and glacier melt (using a temperature degree-day model). +Glacier processes can be modelled if the snow model is enabled. Glacier modelling is very +similar to snow modelling and considers two main processes: glacier build-up from snow turning +into firn/ice (using the HBV-light model) and glacier melt (using a temperature degree-day +model). The definition of glacier boundaries and initial volume is defined by two parameters. The parameter `glacierfrac` gives the fraction of each grid cell covered by a glacier as a diff --git a/docs/src/model_docs/structures.md b/docs/src/model_docs/structures.md index 78ca20a85..5f25e6f3f 100644 --- a/docs/src/model_docs/structures.md +++ b/docs/src/model_docs/structures.md @@ -21,7 +21,7 @@ end The `lateral` field of the `struct Model` can contain different lateral concepts. For each wflow model these different lateral concepts are mapped through the use of a `NamedTuple`. The `vertical` field of the `struct Model` always contains one vertical concept, for example -the SBM or HBV vertical concept. +the SBM vertical concept. Below an example how lateral concepts are mapped for the SBM model through a `NamedTuple`: diff --git a/docs/src/model_docs/vertical/flextopo.md b/docs/src/model_docs/vertical/flextopo.md deleted file mode 100644 index 114b54920..000000000 --- a/docs/src/model_docs/vertical/flextopo.md +++ /dev/null @@ -1,357 +0,0 @@ -# [FLEXTopo](@id vert_flextopo) - -## Introduction -This section describes the different vertical processes available as part of the vertical -FLEXTopo concept. This concept is part of the wflow\_flextopo model. The FLEXTopo model is a -process-based model, which consists of different parallel classes connected through their -groundwater storage. These classes are usually delineated from topographical data to -represent the variability in hydrological processes across user-defined Hydrological -Response Units (HRU). The main assumption underlying the concept, which was first introduced -by Savenije (2010), is that different parts of the landscape fulfill different tasks in -runoff generation and, hence, can be represented by different model structures. Commonly -used classes include hillslopes, plateau and wetlands. Hillslopes are steep areas in a -catchment and are generally forested. The dominant runoff process in hillslopes is assumed -to be characterized by subsurface flow. Plateaus are defined as relatively flat and are -relatively high above the stream, with deep groundwater levels. Depending on the specific -conditions, the dominant runoff processes are groundwater recharge, quick subsurface flow -and hortonian overland flow, which is especially important in agricultural areas. Saturation -overland flow and capillary rise are the dominant processes on the riparian wetland class, -where groundwater levels are shallow and assumed to rise quickly during an event. The -strength of the concept is that the definition of classes and associated model structures is -modular and flexible and not constrained by a predefined fixed model structure. The flexible -approach allows to develop process-based models for different topographic, climatic, -geologic and land use conditions, making use of the available data and expert knowledge. The -FLEXTopo modeling approach has been applied in a lumped and distributed way in various -applications across the globe (Gao et al., 2014; Euser et al., 2015; Hanus et al., 2021; -Hrachowitz et al. 2021; Hulsman et al. 2021; Bouaziz et al., 2022). - -The wflow\_flextopo model is set-up in a modular way, implying that the user is free to -determine the number of classes and which processes and/or parameters to include or exclude -for each class. For each cell in the model domain, the percentage of each class in the cell -is provided by the user in the staticmaps. The most complete model structure implemented in -wflow\_flextopo is shown in the figure below. However, it is also possible to bypass each -bucket or to deactivate processes through parameter values (see [FLEXTopo -configuration](@ref config_flextopo)). It is also possible for users to contribute to the -code by adding different conceptualizations for the different storages. When defining -several classes, the model structures for each class are implemented in parallel, except for -the common glacier, snow and groundwater processes, which are not class specific. An example -of a three classes model is also shown in the Figure below. - -![flextopo_julia_1class.png](../../images/flextopo_julia_1class.png) -*Schematic representation of the FLEXTopo model for a single class model including all -storages and fluxes. Main parameters are denoted in red.* - -![flextopo_julia_3class.png](../../images/flextopo_julia_3class.png) -*Example of a three class model with different model structure configurations per class* - -The descriptions below of each of the FLEXTopo model components are given for the most -complete model structure with the symbols as shown in the schematic representation of the -one class model in the Figure above. By bypassing storages and/or setting parameters to -specific values, the model structure can be adapted to a user-defined model structure. - -## Snow -The snow model is described in [Snow and glaciers](@ref snow_and_glac). - -## Glaciers -Glacier processes are described in [Snow and glaciers](@ref snow_and_glac). Glacier -modelling is enabled by specifying the following in the TOML file: - -```toml -[model] -glacier = true -``` - -## Correction factors for forcing data -The ``e_\mathrm{corr}`` and ``p_\mathrm{corr}`` model parameters can be used to adjust the -potential evaporation and precipitation, respectively. - -## Interception -After the snow module, rainfall ``P_\mathrm{R}`` [mm t``^{-1}``] and snowmelt -``P_\mathrm{M}`` [mm t``^{-1}``] enter the interception storage ``S_\mathrm{I}`` \[mm\]. The -maximum interception storage is defined by the ``I_\mathrm{max}`` \[mm\] parameter for each -class. Interception evaporation ``E_\mathrm{I}`` [mm t``^{-1}``] occurs at potential rate -``E_\mathrm{P}`` [mm t``^{-1}``] as long as there is enough water in the interception -storage. Effective precipitation ``P_\mathrm{E}`` [mm t``^{-1}``] flows out of the -interception store when the storage capacity is exceeded. Interception evaporation is -subtracted from potential evaporation to ensure total evaporation does not exceed potential -evaporation. - -The following equations apply: - -```math - \mathrm{d}S_\mathrm{I}/\mathrm{d}t = (P_\mathrm{R} + P_\mathrm{M}) - E_\mathrm{I} - P_\mathrm{E} -``` - -```math - P_\mathrm{E} = \mathrm{max}(0, (S_\mathrm{I} - I_\mathrm{max})/\mathrm{d}t) -``` - -```math - E_\mathrm{I} = \mathrm{min}(E_\mathrm{P}, S_\mathrm{I}/\mathrm{d}t) -``` - -## Hortonion ponding and runoff -Hortonian overland flow processes are represented by a combination of two storages: a horton -ponding storage ``S_\mathrm{Hp}`` [mm] and a runoff generating storage ``S_\mathrm{Hf}`` -[mm]. This conceptualization was introduced by de Boer-Euser (2017) and included in the -plateau class to represent hortonian overland flow (infiltration excess overland flow) in -agricultural fields. When the storage capacity of the ponding storage is exceeded, runoff -``Q_\mathrm{H}`` [mm t``^{-1}``] is generated that enters the horton runoff storage. The -horton runoff generating storage is included to slightly smooth the precipitation signal. -However, the response time of the runoff generation storage ``K_\mathrm{Hf}`` [t``^{-1}``] -is very short to generate fast runoff ``Q_\mathrm{Hf}`` [mm t``^{-1}``]. - -Effective precipitation ``P_\mathrm{E}`` [mm t``^{-1}``] from the interception module enters -the horton ponding storage, which has a maximum storage capacity ``S_\mathrm{Hmax}`` [mm]. -When the inflow exceeds the storage capacity, direct runoff is generated -``Q_\mathrm{H,direct}`` [mm t``^{-1}``] and net infiltration in the horton ponding storage -is denoted as ``Q_\mathrm{H,in,net}`` [mm t``^{-1}``]. - -Evaporation from the horton ponding storage ``E_\mathrm{H}`` [mm t``^{-1}``] is based on a -simple formulation to express water stress. The equation describes how actual evaporation is -linearly reduced when the relative horton ponding storage ``\overline{S_\mathrm{Hp}}`` [-] -is below a certain threshold ``L_\mathrm{P}`` [-] parameter. - -A beta function with parameter ``\beta`` [-] is used to split the net infiltrating water to -storage and to runoff ``Q_\mathrm{H}`` [mm t``^{-1}``], to which is added the direct runoff -``Q_\mathrm{H,direct}`` [mm t``^{-1}``]. - -The shape of the beta function for various values of ``\beta`` [-] is shown below: - -```@setup plot - using Printf - using CairoMakie -``` - -```@example plot - let # hide - fig = Figure(resolution = (800, 400)) # hide - ax = Axis(fig[1, 1], xlabel = "S/Smax [-]", ylabel = "Fraction of runoff [-]") # hide - x = 0:0.01:1 # hide - betas = [0.3, 1.0, 3.0] # hide - for beta in betas # hide - lines!(ax, x, (1 .- (1 .- x).^beta), label = @sprintf("beta = %.1f", beta)) # hide - end # hide - Legend(fig[1, 2], ax, "beta") # hide - fig # hide - end # hide -``` - -Additionally, water infiltrates from the horton ponding storage to the root zone storage -(``Q_\mathrm{HR}`` [mm t``^{-1}``]), based on a formulation using a maximum infiltration -capacity ``F_\mathrm{max}`` [mm t``^{-1}``] and a decay coefficient ``F_\mathrm{dec}`` [-]. - -The maximum storage capacity of the horton ponding storage is reduced when soils are likely -to be frozen ``S_\mathrm{Hmax,frost}`` [mm], i.e. during periods when the temperature is -below zero for several consecutive days. - -The following equations apply for the Horton ponding storage ``S_\mathrm{Hp}`` [mm]: - -```math - \mathrm{d}S_\mathrm{Hp}/\mathrm{d}t = P_\mathrm{E} - E_\mathrm{H} - Q_\mathrm{H} - Q_\mathrm{HR} -``` - -The constitutive equations are: - -```math - Q_\mathrm{H,direct}=\mathrm{max}((S_\mathrm{Hp}+P_\mathrm{E}−S_\mathrm{Hmax});0.0) -``` - -```math - Q_\mathrm{H,in,net} = P_\mathrm{E} − Q_\mathrm{H,direct} -``` - -```math - \overline{S_\mathrm{Hp}} = S_\mathrm{Hp}/S_\mathrm{H,max} -``` - -```math - E_\mathrm{H} = \mathrm{min} ( (E_\mathrm{P} - E_\mathrm{I}) \cdot \mathrm{min}(\overline{S_\mathrm{Hp}}/L_\mathrm{P},1), S_\mathrm{Hp}/\mathrm{d}t ) -``` - -```math - Q_\mathrm{H} = Q_\mathrm{H,in,net} \cdot (1-(1-\overline{S_\mathrm{Hp}})^\beta) -``` - -```math - Q_\mathrm{HR} = F_\mathrm{max} \cdot \mathrm{exp}(-F_\mathrm{dec} \cdot \overline{S_\mathrm{Hp}}) -``` - -The reduction of the storage capacity of the horton ponding storage during frozen soil -conditions is calculated following the equations provided by de Boer-Euser (2017): - -```math - S_\mathrm{Hmax,frost} = F_\mathrm{T} \cdot S_\mathrm{Hmax} -``` - -with: - -```math -F_\mathrm{T} = - \begin{cases} - S_\mathrm{Hmin} & \text{if $F_\mathrm{acc,fr} < F_\mathrm{acc,fr0}$}\\ - \frac{F_\mathrm{acc}}{F_\mathrm{acc,fr1} - F_\mathrm{acc,fr0}} - \frac{F_\mathrm{acc,fr0}}{F_\mathrm{acc,fr1} - F_\mathrm{acc,fr0}} & \text{if $ F_\mathrm{acc,fr0} \le F_\mathrm{acc,fr} \le F_\mathrm{acc,fr1}$}\\ - 1 & \text{if $F_\mathrm{acc,fr} > F_\mathrm{acc,fr,1}$} - \end{cases} -``` - -where ``S_\mathrm{Hmin}`` [-], ``F_\mathrm{acc,fr0}`` [degree t], ``F_\mathrm{acc,fr1}`` -[degree t] and ``K_\mathrm{mf}`` [-] are all model parameters to describe: a coefficient to -reduce ``S_\mathrm{Hmax}`` to a minimum storage capacity, the minimum and maximum modelled -accumulated frost and a melt coefficient for the frozen topsoil, respectively. - -The following equations apply for the Horton fast runoff storage ``S_\mathrm{Hf}`` [mm]: - -```math - \mathrm{d}S_\mathrm{Hf}/\mathrm{d}t = Q_\mathrm{H} - Q_\mathrm{Hf} -``` - -```math - Q_\mathrm{Hf} = K_\mathrm{Hf}^{-1} \cdot S_\mathrm{Hf} -``` - -## Root zone soil moisture -The incoming water from the interception and hortonian routines ``Q_\mathrm{HR}`` [mm -t``^{-1}``] enters the root zone storage ``S_\mathrm{R}`` [mm]. The root zone storage has a -maximum capacity ``S_\mathrm{Rmax}`` [mm], which represents the volume of water in the -unsaturated root zone, which is available to the roots of vegetation for transpiration. -Abundant water which exceeds the capacity of the root zone storage cannot infiltrate and -becomes directly available for runoff ``Q_\mathrm{R,direct}`` [mm t``^{-1}``]. The net -infiltration in the root zone storage is denoted as ``Q_\mathrm{R,in,net}`` [mm t``^{-1}``]. - -A simple formulation to express water stress is used to calculate evaporation -``E_\mathrm{R}`` [mm t``^{-1}``] from the root zone storage. The equation describes how -actual evaporation is linearly reduced when the relative root zone storage -``\overline{S_\mathrm{R}}`` [-] is below a certain threshold ``L_\mathrm{P}`` [-] parameter. - -Next, a beta function with parameter ``\beta`` [-] describes the partitioning of incoming -water to the root zone storage and to runoff ``Q_\mathrm{R}`` [mm t``^{-1}``]. The water -that leaves the root zone storage is partitioned into the fast storage and through -preferential recharge to the slow storage, based on a splitter parameter ``d_\mathrm{s}`` -[-]. - -Water may also leave the root zone storage through percolation to the slow groundwater -``Q_\mathrm{perc}`` [mm t``^{-1}``] or enter the root zone storage from the slow groundwater -through capillary rise ``Q_\mathrm{cap}`` [mm t``^{-1}``], based on a maximum percolation -parameter ``Q_\mathrm{perc,max}`` [mm t``^{-1}``] and a maximum capillary rise flux -parameter ``Q_\mathrm{cap,max}`` [mm t``^{-1}``]. - - -The water balance equation for the root zone storage is: - -```math - \mathrm{d}S_\mathrm{R}/\mathrm{d}t = Q_\mathrm{HR} - E_\mathrm{R} - Q_\mathrm{R} - Q_\mathrm{perc} + Q_\mathrm{cap} -``` - -The constitutive equations are: - -```math - Q_\mathrm{R,direct}=\mathrm{max}((S_\mathrm{R}+Q_\mathrm{HR}−S_\mathrm{Rmax});0.0) -``` - -```math - Q_\mathrm{R,in,net} = Q_\mathrm{HR} − Q_\mathrm{R,direct} -``` - -```math - \overline{S_\mathrm{R}} = S_\mathrm{R}/S_\mathrm{R,max} -``` - -```math - E_\mathrm{R} = \mathrm{min} ( (E_\mathrm{P} - E_\mathrm{I} - E_\mathrm{H}) \cdot \mathrm{min}(\overline{S_\mathrm{R}}/L_\mathrm{P},1), S_\mathrm{R}/\mathrm{d}t ) -``` - -```math - Q_\mathrm{R} = Q_\mathrm{R,in,net} \cdot (1-(1-\overline{S_\mathrm{R}})^\beta) -``` - -```math - Q_\mathrm{perc} = Q_\mathrm{perc,max} \cdot \overline{S_\mathrm{R}} -``` - -```math - Q_\mathrm{cap} = Q_\mathrm{cap,max} \cdot (1 - \overline{S_\mathrm{R}}) -``` - -## Fast storage and runoff -The outflow from the root zone storage ``Q_\mathrm{R}`` [mm t``^{-1}``] is split with the -splitter parameter ``d_\mathrm{s}`` [-] into inflow in the fast storage ``Q_\mathrm{RF}`` -[mm t``^{-1}``] and inflow in the slow storage ``Q_\mathrm{RS}`` [mm t``^{-1}``] to -represent preferential recharge. The fast runoff storage ``S_\mathrm{F}`` [mm] generates -fast runoff ``Q_\mathrm{F}`` [mm t``^{-1}``] through a simple non-linear equation with a -recession constant ``K_\mathrm{F}`` [t``^{-1}``] and an exponent ``\alpha`` [-]. - -The following equations apply: - -```math - \mathrm{d}S_\mathrm{F}/\mathrm{d}t = Q_\mathrm{RF} - Q_\mathrm{F} -``` - -```math - Q_\mathrm{RF} = Q_\mathrm{R} \cdot (1-d_\mathrm{s}) -``` - -```math - Q_\mathrm{F} = K_\mathrm{F}^{-1} \cdot S_\mathrm{F}^{\alpha} -``` - - -## Common slow groundwater storage and runoff -The slow groundwater storage ``S_\mathrm{S}`` [mm] is a shared storage for all the different -classes. It is filled through preferential recharge from the outflow of the root zone -storage ``Q_\mathrm{RS}`` [mm t``^{-1}``] and through percolation ``Q_\mathrm{perc}`` [mm -t``^{-1}``]. It empties through capillary rise ``Q_\mathrm{cap}`` [mm t``^{-1}``] and -through a linear outflow ``Q_\mathrm{S}`` [mm t``^{-1}``] with recession timescale -coefficient ``K_\mathrm{S}`` [t``^{-1}``]. - -Total streamflow ``Q_\mathrm{TOT}`` [mm t``^{-1}``] is the weighted sum of the horton fast -runoff and the fast runoff from the different classes based on the fraction of each class in -a cell ``F_\mathrm{hrufrac}`` [-] and the slow runoff, which is then routed downstream along -the river network through the kinematic wave. - -```math - \mathrm{d}S_\mathrm{S}/\mathrm{d}t = Q_\mathrm{RS} + Q_\mathrm{perc} - Q_\mathrm{S} - Q_\mathrm{cap} -``` - -```math - Q_\mathrm{RS} = Q_\mathrm{R} \cdot d_\mathrm{s} -``` - -```math - Q_\mathrm{S} = K_\mathrm{S}^{-1} \cdot S_\mathrm{S} -``` - -```math -Q_\mathrm{TOT} = Q_\mathrm{S} + \sum_{class=1}^{n} (Q_\mathrm{F,class} + Q_\mathrm{Hf,class}) \cdot F_\mathrm{hrufrac,class} -``` - -## References -+ de Boer-Euser, T. (2017). Added value of distribution in rainfall-runoff models for the Meuse basin - PhD thesis, Delft University of Technology. https://doi.org/10.4233/uuid:89a78ae9-7ffb-4260-b25d-698854210fa8 - -+ Bouaziz, L. J. E., Aalbers, E. E., Weerts, A. H., Hegnauer, M., Buiteveld, H., Lammersen, R., Stam, J., Sprokkereef, E., Savenije, H. H. G., and Hrachowitz, M. (2022) - Ecosystem adaptation to climate change: the sensitivity of hydrological predictions to time-dynamic model parameters, - Hydrol. Earth Syst. Sci., 26, 1295–1318, https://doi.org/10.5194/hess-26-1295-2022 - -+ Euser, T., Hrachowitz, M., Winsemius, H. C., & Savenije, H. H. G. (2015). - The effect of forcing and landscape distribution on performance and consistency of model structures. - Hydrological Processes, 29(17), 3727–3743. https://doi.org/10.1002/hyp.10445 - -+ Gao, H., Hrachowitz, M., Fenicia, F., Gharari, S., & Savenije, H. H. G. (2014). - Testing the realism of a topography-driven model (FLEX-Topo) in the nested catchments of the Upper Heihe, China. - Hydrology and Earth System Sciences, 18(5), 1895–1915. https://doi.org/10.5194/hess-18-1895-2014 - -+ Hanus, S., Hrachowitz, M., Zekollari, H., Schoups, G., Vizcaino, M., and Kaitna, R. (2021) - Future changes in annual, seasonal and monthly runoff signatures in contrasting Alpine catchments in Austria, - Hydrol. Earth Syst. Sci., 25, 3429–3453, https://doi.org/10.5194/hess-25-3429-2021 - -+ Hrachowitz, M., Stockinger, M., Coenders-Gerrits, M., van der Ent, R., Bogena, H., Lücke, A., and Stumpp, C. (2021) - Reduction of vegetation-accessible water storage capacity after deforestation affects catchment travel time distributions - and increases young water fractions in a headwater catchment, Hydrol. Earth Syst. Sci., 25, 4887–4915, https://doi.org/10.5194/hess-25-4887-2021 - -+ Hulsman, P., Savenije, H. H. G., & Hrachowitz, M. (2021). Learning from satellite observations: - Increased understanding of catchment processes through stepwise model improvement. - Hydrology and Earth System Sciences, 25(2), 957–982. https://doi.org/10.5194/hess-25-957-2021 - -+ Savenije, H. H. G. (2010). HESS opinions “topography driven conceptual modelling (FLEX-Topo).” - Hydrology and Earth System Sciences, 14(12), 2681–2692. https://doi.org/10.5194/hess-14-2681-2010 diff --git a/docs/src/model_docs/vertical/hbv.md b/docs/src/model_docs/vertical/hbv.md deleted file mode 100644 index a0c2ae92f..000000000 --- a/docs/src/model_docs/vertical/hbv.md +++ /dev/null @@ -1,195 +0,0 @@ -# [HBV](@id vert_hbv) - -## Introduction -This section describes the different vertical processes available as part of the vertical -HBV concept. This concept is part of the wflow\_hbv model. - -## Snow -The snow model is described in [Snow and glaciers](@ref snow_and_glac). - -## Glaciers -Glacier processes are described in [Snow and glaciers](@ref snow_and_glac). Glacier modelling is enabled -by specifying the following in the TOML file: - -```toml -[model] -glacier = true -``` -## Potential Evaporation -The `cevpf` model parameter is used to adjust the potential evaporation based on land use. -In the original HBV version `cevpfo` is used, a factor for forest land use only. - -## Interception -For interception storage a single `icf` parameter is used according to the land use. In this -implementation interception evaporation is subtracted to ensure total evaporation does not -exceed potential evaporation. From this storage evaporation equal to the potential -evaporation rate will occur as long as water is available, even if it is stored as snow. All -water enters this store first, there is no concept of free throughfall (e.g. through gaps in -the canopy). In the model a running water budget is kept of the interception store: - -+ The available storage (`icf`- actual storage) is filled with the water coming from the - snow routine (``Q_{in}``) -+ Any surplus water now becomes the new ``Q_{in}`` -+ Interception evaporation is determined as the minimum of the current interception storage - and the potential evaporation - -## The soil routine -The incoming water from the snow and interception routines, ``Q_{in}``, is available for -infiltration in the soil routine. The soil layer has a limited capacity, `fc`, to hold soil -water, which means if `fc` is exceeded the abundant water cannot infiltrate and, -consequently, becomes directly available for runoff. - -```math - Q_{dr}=max((SM+Q_{in}−fc);0.0) -``` - -where ``Q_{dr}`` is the abundant soil water (also referred to as direct runoff) and ``SM`` -is the soil moisture content. Consequently, the net amount of water that infiltrates into -the soil, ``I_{net}``, equals: - -```math -I_{net} = Q_{in} − Q_{dr} -``` - -Part of the infiltrating water, ``I_{net}``, will runoff through the soil layer (seepage). -This runoff volume, ``SP``, is related to the soil moisture content, ``SM``, through the -following power relation: - -```math -SP = \left(\frac{SM}{fc}\right)^\beta I_{net} -``` - -where ``\beta`` is an empirically based parameter. Application of this equation implies that -the amount of seepage water increases with increasing soil moisture content. The fraction of -the infiltrating water which does not runoff, ``I_{net}−SP``, is added to the available -amount of soil moisture, ``SM``. The ``\beta`` parameter affects the amount of supply to the -soil moisture reservoir that is transferred to the quick response reservoir. Values of -``\beta`` vary generally between 1 and 3. Larger values of ``\beta`` reduce runoff and -indicate a higher absorption capacity of the soil. - -![hbv-soilmoist.png](../../images/hbv-soilmoist.png) - -*Schematic view of the soil moisture routine* - -A percentage of the soil moisture will evaporate. This percentage is related to the -potential evaporation and the available amount of soil moisture: - -```math - E_a = \frac{SM}{T_m} E_p \, ; \, SM "ERROR", "error" => err) - ZMQ.send(s, JSON3.write(resp)) + resp = Dict{String, String}("status" => "ERROR", "error" => err) + return ZMQ.send(s, JSON3.write(resp)) end "Status response ZMQ server" function response(s::ZMQ.Socket) @info "Send status response" - resp = Dict{String,String}("status" => "OK") - ZMQ.send(s, JSON3.write(resp)) + resp = Dict{String, String}("status" => "OK") + return ZMQ.send(s, JSON3.write(resp)) end "Validate JSON request against mapped Struct" @@ -128,7 +128,7 @@ function main(ARGS::Vector{String}) ), ) end - start(port) + return start(port) end """ diff --git a/server/test/client.jl b/server/test/client.jl index 36e6b5dc5..1688af26f 100644 --- a/server/test/client.jl +++ b/server/test/client.jl @@ -32,11 +32,11 @@ end @testset "model information functions" begin @test request((fn = "get_component_name",)) == Dict("component_name" => "sbm") - @test request((fn = "get_input_item_count",)) == Dict("input_item_count" => 207) - @test request((fn = "get_output_item_count",)) == Dict("output_item_count" => 207) + @test request((fn = "get_input_item_count",)) == Dict("input_item_count" => 203) + @test request((fn = "get_output_item_count",)) == Dict("output_item_count" => 203) to_check = [ - "vertical.nlayers", - "vertical.theta_r", + "vertical.soil.parameters.nlayers", + "vertical.soil.parameters.theta_r", "lateral.river.q", "lateral.river.reservoir.outflow", ] @@ -52,22 +52,26 @@ vwc_1_size = 0 @test request((fn = "get_var_itemsize", name = "lateral.subsurface.ssf")) == Dict("var_itemsize" => sizeof(Wflow.Float)) @test request((fn = "get_var_type", name = "vertical.n"))["status"] == "ERROR" - @test request((fn = "get_var_units", name = "vertical.theta_s")) == Dict("var_units" => "-") + @test request((fn = "get_var_units", name = "vertical.soil.parameters.theta_s")) == + Dict("var_units" => "-") @test request((fn = "get_var_location", name = "lateral.river.q")) == Dict("var_location" => "node") - zi_nbytes = request((fn = "get_var_nbytes", name = "vertical.zi"))["var_nbytes"] + zi_nbytes = + request((fn = "get_var_nbytes", name = "vertical.soil.variables.zi"))["var_nbytes"] @test zi_nbytes == 400504 - zi_itemsize = request((fn = "get_var_itemsize", name = "vertical.zi"))["var_itemsize"] + zi_itemsize = + request((fn = "get_var_itemsize", name = "vertical.soil.variables.zi"))["var_itemsize"] zi_size = Int(zi_nbytes / zi_itemsize) - vwc_1_nbytes = request((fn = "get_var_nbytes", name = "vertical.vwc[1]"))["var_nbytes"] + vwc_1_nbytes = + request((fn = "get_var_nbytes", name = "vertical.soil.variables.vwc[1]"))["var_nbytes"] @test vwc_1_nbytes == 400504 vwc_1_itemsize = - request((fn = "get_var_itemsize", name = "vertical.vwc[1]"))["var_itemsize"] + request((fn = "get_var_itemsize", name = "vertical.soil.variables.vwc[1]"))["var_itemsize"] vwc_1_size = Int(vwc_1_nbytes / vwc_1_itemsize) @test request((fn = "get_var_grid", name = "lateral.river.h")) == Dict("var_grid" => 3) - msg = (fn = "get_value", name = "vertical.zi", dest = fill(0.0, zi_size)) + msg = (fn = "get_value", name = "vertical.soil.variables.zi", dest = fill(0.0, zi_size)) @test mean(request(msg)["value"]) ≈ 277.3620724821974 - msg = (fn = "get_value_ptr", name = "vertical.theta_s") + msg = (fn = "get_value_ptr", name = "vertical.soil.parameters.theta_s") @test mean(request(msg)["value_ptr"]) ≈ 0.4409211971535584 msg = ( fn = "get_value_at_indices", @@ -77,55 +81,68 @@ vwc_1_size = 0 ) @test request(msg)["value_at_indices"] ≈ [2.198747900215207f0, 2.6880427720508515f0, 3.4848783702629564f0] - msg = (fn = "set_value", name = "vertical.zi", src = fill(300.0, zi_size)) + msg = + (fn = "set_value", name = "vertical.soil.variables.zi", src = fill(300.0, zi_size)) @test request(msg) == Dict("status" => "OK") - msg = (fn = "get_value", name = "vertical.zi", dest = fill(0.0, zi_size)) + msg = (fn = "get_value", name = "vertical.soil.variables.zi", dest = fill(0.0, zi_size)) @test mean(request(msg)["value"]) == 300.0 msg = ( fn = "set_value_at_indices", - name = "vertical.zi", + name = "vertical.soil.variables.zi", src = [250.0, 350.0], inds = [1, 2], ) @test request(msg) == Dict("status" => "OK") msg = ( fn = "get_value_at_indices", - name = "vertical.zi", + name = "vertical.soil.variables.zi", dest = [0.0, 0.0, 0.0], inds = [1, 2, 3], ) @test request(msg)["value_at_indices"] == [250.0, 350.0, 300.0] - msg = (fn = "get_value", name = "vertical.vwc[1]", dest = fill(0.0, vwc_1_size)) + msg = ( + fn = "get_value", + name = "vertical.soil.variables.vwc[1]", + dest = fill(0.0, vwc_1_size), + ) @test mean(request(msg)["value"]) ≈ 0.18600013563085036f0 msg = ( fn = "get_value_at_indices", - name = "vertical.vwc[1]", + name = "vertical.soil.variables.vwc[1]", dest = [0.0, 0.0, 0.0], inds = [1, 2, 3], ) @test request(msg)["value_at_indices"] ≈ [0.12089607119560242f0, 0.11968416924304527f0, 0.14602328618707333f0] - msg = (fn = "set_value", name = "vertical.vwc[1]", src = fill(0.3, vwc_1_size)) + msg = ( + fn = "set_value", + name = "vertical.soil.variables.vwc[1]", + src = fill(0.3, vwc_1_size), + ) @test request(msg) == Dict("status" => "OK") - msg = (fn = "get_value", name = "vertical.vwc[1]", dest = fill(0.0, vwc_1_size)) + msg = ( + fn = "get_value", + name = "vertical.soil.variables.vwc[1]", + dest = fill(0.0, vwc_1_size), + ) @test mean(request(msg)["value"]) ≈ 0.3f0 msg = ( fn = "get_value_at_indices", - name = "vertical.vwc[1]", + name = "vertical.soil.variables.vwc[1]", dest = [0.0, 0.0, 0.0], inds = [1, 2, 3], ) @test request(msg)["value_at_indices"] == [0.3, 0.3, 0.3] msg = ( fn = "set_value_at_indices", - name = "vertical.vwc[1]", + name = "vertical.soil.variables.vwc[1]", src = [0.1, 0.25], inds = [1, 2], ) @test request(msg) == Dict("status" => "OK") msg = ( fn = "get_value_at_indices", - name = "vertical.vwc[1]", + name = "vertical.soil.variables.vwc[1]", dest = [0.0, 0.0], inds = [1, 2], ) diff --git a/server/test/runtests.jl b/server/test/runtests.jl index 8d016a2b6..bb37c0a4f 100644 --- a/server/test/runtests.jl +++ b/server/test/runtests.jl @@ -1,12 +1,12 @@ -import ZMQ -import JSON3 -import StructTypes -import Wflow -import WflowServer +using ZMQ: ZMQ +using JSON3: JSON3 +using StructTypes: StructTypes +using Wflow: Wflow +using WflowServer: WflowServer import Statistics: mean import Logging: with_logger, NullLogger import Test: @testset, @test -import Downloads +using Downloads: Downloads # ensure test data is present testdir = @__DIR__ diff --git a/server/test/sbm_config.toml b/server/test/sbm_config.toml index 3ab5c92d0..39898a488 100644 --- a/server/test/sbm_config.toml +++ b/server/test/sbm_config.toml @@ -19,14 +19,18 @@ path_output = "outstates-moselle.nc" # if listed, the variable must be present in the NetCDF or error # if not listed, the variable can get a default value if it has one -[state.vertical] -canopystorage = "canopystorage" +[state.vertical.interception.variables] +canopy_storage = "canopystorage" + +[state.vertical.soil.variables] satwaterdepth = "satwaterdepth" -snow = "snow" -snowwater = "snowwater" tsoil = "tsoil" ustorelayerdepth = "ustorelayerdepth" +[state.vertical.snow.variables] +snow_storage = "snow" +snow_water = "snowwater" + [state.lateral.river] h = "h_river" h_av = "h_av_river" @@ -56,42 +60,52 @@ subcatchment = "wflow_subcatch" # specify the internal IDs of the parameters which vary over time # the external name mapping needs to be below together with the other mappings forcing = [ - "vertical.precipitation", - "vertical.temperature", - "vertical.potential_evaporation", + "vertical.atmospheric_forcing.precipitation", + "vertical.atmospheric_forcing.temperature", + "vertical.atmospheric_forcing.potential_evaporation", ] -cyclic = ["vertical.leaf_area_index"] +cyclic = ["vertical.vegetation_parameter_set.leaf_area_index"] + +[input.vertical.vegetation_parameter_set] +leaf_area_index = "LAI" +kext = "Kext" +storage_specific_leaf = "Sl" +storage_wood = "Swood" +rootingdepth = "RootingDepth" + +[input.vertical.runoff.parameters] +waterfrac = "WaterFrac" + +[input.vertical.interception.parameters] +e_r = "EoverR" -[input.vertical] +[input.vertical.atmospheric_forcing] +potential_evaporation = "pet" +precipitation = "precip" +temperature = "temp" + +[input.vertical.soil.parameters] c = "c" cf_soil = "cf_soil" -cfmax = "Cfmax" -e_r = "EoverR" f = "f" infiltcappath = "InfiltCapPath" infiltcapsoil = "InfiltCapSoil" -kext = "Kext" -leaf_area_index = "LAI" +water_holding_capacity = "WHC" +theta_r = "thetaR" +theta_s = "thetaS" maxleakage = "MaxLeakage" pathfrac = "PathFrac" -potential_evaporation = "pet" -precipitation = "precip" rootdistpar = "rootdistpar" -rootingdepth = "RootingDepth" soilthickness = "SoilThickness" -specific_leaf = "Sl" -storage_wood = "Swood" -temperature = "temp" + +[input.vertical.snow.parameters] tt = "TT" tti = "TTI" ttm = "TTM" -water_holding_capacity = "WHC" -waterfrac = "WaterFrac" -theta_r = "thetaR" -theta_s = "thetaS" +cfmax = "Cfmax" -[input.vertical.kv_0] +[input.vertical.soil.parameters.kv_0] netcdf.variable.name = "KsatVer" scale = 1.0 offset = 0.0 @@ -135,14 +149,18 @@ min_streamorder_land = 5 [output] path = "output_moselle.nc" -[output.vertical] -canopystorage = "canopystorage" +[output.vertical.interception.variables] +canopy_storage = "canopystorage" + +[output.vertical.soil.variables] satwaterdepth = "satwaterdepth" -snow = "snow" -snowwater = "snowwater" tsoil = "tsoil" ustorelayerdepth = "ustorelayerdepth" +[output.vertical.snow.variables] +snow_storage = "snow" +snow_water = "snowwater" + [output.lateral.river] h = "h_river" q = "q_river" @@ -170,14 +188,14 @@ coordinate.x = 6.255 coordinate.y = 50.012 name = "temp_coord" location = "temp_bycoord" -parameter = "vertical.temperature" +parameter = "vertical.atmospheric_forcing.temperature" [[netcdf.variable]] location = "temp_byindex" name = "temp_index" index.x = 100 index.y = 264 -parameter = "vertical.temperature" +parameter = "vertical.atmospheric_forcing.temperature" [csv] path = "output_moselle.csv" @@ -196,20 +214,20 @@ parameter = "lateral.river.reservoir.volume" coordinate.x = 6.255 coordinate.y = 50.012 header = "temp_bycoord" -parameter = "vertical.temperature" +parameter = "vertical.atmospheric_forcing.temperature" [[csv.column]] coordinate.x = 6.255 coordinate.y = 50.012 header = "vwc_layer2_bycoord" -parameter = "vertical.vwc" +parameter = "vertical.soil.variables.vwc" layer = 2 [[csv.column]] header = "temp_byindex" index.x = 100 index.y = 264 -parameter = "vertical.temperature" +parameter = "vertical.atmospheric_forcing.temperature" [[csv.column]] header = "Q" @@ -219,12 +237,24 @@ parameter = "lateral.river.q" [[csv.column]] header = "recharge" map = "subcatchment" -parameter = "vertical.recharge" +parameter = "vertical.soil.variables.recharge" reducer = "mean" [API] components = [ - "vertical", + "vertical.atmospheric_forcing", + "vertical.vegetation_parameter_set", + "vertical.runoff.boundary_conditions", + "vertical.runoff.variables", + "vertical.runoff.parameters", + "vertical.soil.boundary_conditions", + "vertical.soil.variables", + "vertical.soil.parameters", + "vertical.interception.variables", + "vertical.interception.parameters", + "vertical.snow.boundary_conditions", + "vertical.snow.variables", + "vertical.snow.parameters", "lateral.subsurface", "lateral.land", "lateral.river", diff --git a/src/Wflow.jl b/src/Wflow.jl index f4f898e0d..233a96fe0 100644 --- a/src/Wflow.jl +++ b/src/Wflow.jl @@ -4,11 +4,43 @@ import BasicModelInterface as BMI using Base.Threads: nthreads using CFTime: CFTime, monthday, dayofyear -using Dates: Dates, Second, Minute, Hour, Day, Month, year, TimeType, DatePeriod, TimePeriod, Date, DateTime, now, isleapyear, datetime2unix +using Dates: + Dates, + Second, + Minute, + Hour, + Day, + Month, + year, + TimeType, + DatePeriod, + TimePeriod, + Date, + DateTime, + now, + isleapyear, + datetime2unix, + canonicalize using DelimitedFiles: readdlm using FieldMetadata: @metadata using Glob: glob -using Graphs: Graphs, Graph, DiGraph, add_edge!, is_cyclic, inneighbors, outneighbors, edges, topological_sort_by_dfs, src, dst, vertices, nv, ne, induced_subgraph, add_vertex! +using Graphs: + Graphs, + Graph, + DiGraph, + add_edge!, + is_cyclic, + inneighbors, + outneighbors, + edges, + topological_sort_by_dfs, + src, + dst, + vertices, + nv, + ne, + induced_subgraph, + add_vertex! using IfElse: IfElse using LoggingExtras using LoopVectorization: @tturbo @@ -21,15 +53,13 @@ using Statistics: mean, median, quantile! using TerminalLoggers using TOML: TOML +# metadata is used in combination with BMI functions `get_var_units` and `get_var_location` @metadata get_units "mm dt-1" String -# metadata for BMI grid -@metadata exchange 1 Integer -@metadata grid_type "unstructured" String -@metadata grid_location "node" String +@metadata grid_loc "node" String # BMI grid location const Float = Float64 -const CFDataset = Union{NCDataset,NCDatasets.MFDataset} -const CFVariable_MF = Union{NCDatasets.CFVariable,NCDatasets.MFCFVariable} +const CFDataset = Union{NCDataset, NCDatasets.MFDataset} +const CFVariable_MF = Union{NCDatasets.CFVariable, NCDatasets.MFCFVariable} const version = VersionNumber(TOML.parsefile(joinpath(@__DIR__, "..", "Project.toml"))["version"]) @@ -45,7 +75,7 @@ function Clock(config) calendar = get(config, "calendar", "standard")::String starttime = cftime(config.starttime, calendar) dt = Second(config.timestepsecs) - Clock(starttime, 0, dt) + return Clock(starttime, 0, dt) end function Clock(config, reader) @@ -79,7 +109,7 @@ function Clock(config, reader) end starttime = cftime(config.starttime, calendar) - Clock(starttime, 0, dt) + return Clock(starttime, 0, dt) end include("io.jl") @@ -90,7 +120,7 @@ include("io.jl") Composite type that represents all different aspects of a Wflow Model, such as the network, parameters, clock, configuration and input and output. """ -struct Model{N,L,V,R,W,T} +struct Model{N, L, V, R, W, T} config::Config # all configuration options network::N # connectivity information, directed graph lateral::L # lateral model that holds lateral state, moves along network @@ -104,26 +134,30 @@ end # different model types (used for dispatch) struct SbmModel end # "sbm" type / sbm_model.jl struct SbmGwfModel end # "sbm_gwf" type / sbm_gwf_model.jl -struct HbvModel end # "hbv" type / hbv_model.jl -struct FlextopoModel end # "flextopo" type / flextopo_model.jl struct SedimentModel end # "sediment" type / sediment_model.jl # prevent a large printout of model components and arrays Base.show(io::IO, m::Model) = print(io, "model of type ", typeof(m)) -include("horizontal_process.jl") +include("forcing.jl") +include("parameters.jl") include("flow.jl") -include("hbv.jl") -include("water_demand.jl") +include("horizontal_process.jl") +include("vegetation/rainfall_interception.jl") +include("vegetation/canopy.jl") +include("snow/snow_process.jl") +include("snow/snow.jl") +include("glacier/glacier_process.jl") +include("glacier/glacier.jl") +include("surfacewater/runoff.jl") +include("soil/soil.jl") +include("soil/soil_process.jl") include("sbm.jl") -include("flextopo.jl") +include("demand/water_demand.jl") include("sediment.jl") include("reservoir_lake.jl") -include("hbv_model.jl") include("sbm_model.jl") -include("flextopo_model.jl") include("sediment_model.jl") -include("vertical_process.jl") include("groundwater/connectivity.jl") include("groundwater/aquifer.jl") include("groundwater/boundary_conditions.jl") @@ -137,7 +171,7 @@ include("states.jl") """ run(tomlpath::AbstractString; silent=false) run(config::Config) - run(model::Model) + run!(model::Model) run() Run an entire simulation starting either from a path to a TOML settings file, @@ -178,6 +212,7 @@ function run(tomlpath::AbstractString; silent = nothing) close(logfile) end end + return nothing end function run(config::Config) @@ -187,30 +222,27 @@ function run(config::Config) initialize_sbm_model(config) elseif modeltype == "sbm_gwf" initialize_sbm_gwf_model(config) - elseif modeltype == "hbv" - initialize_hbv_model(config) elseif modeltype == "sediment" initialize_sediment_model(config) - elseif modeltype == "flextopo" - initialize_flextopo_model(config) else error("unknown model type") end - load_fixed_forcing(model) - run(model) + load_fixed_forcing!(model) + run!(model) + return model end -function run_timestep(model::Model; update_func = update, write_model_output = true) +function run_timestep!(model::Model; update_func = update!, write_model_output = true) advance!(model.clock) load_dynamic_input!(model) - model = update_func(model) + update_func(model) if write_model_output write_output(model) end - return model + return nothing end -function run(model::Model; close_files = true) +function run!(model::Model; close_files = true) (; config, writer, clock) = model model_type = config.model.type::String @@ -225,13 +257,13 @@ function run(model::Model; close_files = true) starttime = clock.time dt = clock.dt endtime = cftime(config.endtime, calendar) - times = range(starttime + dt, endtime, step = dt) + times = range(starttime + dt, endtime; step = dt) @info "Run information" model_type starttime dt endtime nthreads() runstart_time = now() @progress for (i, time) in enumerate(times) @debug "Starting timestep." time i now() - model = run_timestep(model) + run_timestep!(model) end @info "Simulation duration: $(canonicalize(now() - runstart_time))" @@ -246,7 +278,7 @@ function run(model::Model; close_files = true) # option to support running function twice without re-initializing # and thus opening the netCDF files if close_files - Wflow.close_files(model, delete_output = false) + Wflow.close_files(model; delete_output = false) end # copy TOML to dir_output, to archive what settings were used @@ -255,10 +287,10 @@ function run(model::Model; close_files = true) dst = output_path(config, basename(src)) if src != dst @debug "Copying TOML file." src dst - cp(src, dst, force = true) + cp(src, dst; force = true) end end - return model + return nothing end function run() @@ -271,7 +303,7 @@ function run() if !isfile(toml_path) throw(ArgumentError("File not found: $(toml_path)\n" * usage)) end - run(toml_path) + return run(toml_path) end end # module diff --git a/src/bmi.jl b/src/bmi.jl index 68093f03a..5d543cac9 100644 --- a/src/bmi.jl +++ b/src/bmi.jl @@ -3,7 +3,7 @@ # Mapping of grid identifier to a key, to get the active indices of the model domain. # See also function active_indices(network, key::Tuple). -const grids = Dict{Int,Tuple{Symbol}}( +const grids = Dict{Int, Tuple{Symbol}}( 0 => (:reservoir,), 1 => (:lake,), 2 => (:drain,), @@ -27,16 +27,12 @@ function BMI.initialize(::Type{<:Model}, config_file) initialize_sbm_model(config) elseif modeltype == "sbm_gwf" initialize_sbm_gwf_model(config) - elseif modeltype == "hbv" - initialize_hbv_model(config) elseif modeltype == "sediment" initialize_sediment_model(config) - elseif modeltype == "flextopo" - initialize_flextopo_model(config) else error("unknown model type") end - load_fixed_forcing(model) + load_fixed_forcing!(model) return model end @@ -49,15 +45,15 @@ Update the model for a single timestep. """ function BMI.update(model::Model; run = nothing) if isnothing(run) - model = run_timestep(model) + run_timestep!(model) elseif run == "sbm_until_recharge" - model = run_timestep( - model, - update_func = update_until_recharge, + run_timestep!( + model; + update_func = update_until_recharge!, write_model_output = false, ) elseif run == "sbm_after_subsurfaceflow" - model = run_timestep(model, update_func = update_after_subsurfaceflow) + run_timestep!(model; update_func = update_after_subsurfaceflow!) end return model end @@ -75,8 +71,8 @@ function BMI.update_until(model::Model, time::Float64) ) error(error_message) end - for _ = 1:steps - model = run_timestep(model) + for _ in 1:steps + run_timestep!(model) end return model end @@ -89,7 +85,7 @@ function BMI.finalize(model::Model) write_netcdf_timestep(model, writer.state_dataset, writer.state_parameters) end reset_clock!(model.clock, config) - close_files(model, delete_output = false) + return close_files(model; delete_output = false) end function BMI.get_component_name(model::Model) @@ -97,11 +93,11 @@ function BMI.get_component_name(model::Model) end function BMI.get_input_item_count(model::Model) - length(BMI.get_input_var_names(model)) + return length(BMI.get_input_var_names(model)) end function BMI.get_output_item_count(model::Model) - length(BMI.get_output_var_names(model)) + return length(BMI.get_output_var_names(model)) end """ @@ -117,22 +113,25 @@ function BMI.get_input_var_names(model::Model) var_names = Vector{String}() for c in config.API.components type = typeof(param(model, c)) - inds = findall(x -> x != 0, exchange(type)) - field_names = fieldnames(type)[inds] + field_names = fieldnames(type) for name in field_names var = string(c, ".", name) - model_var = param(model, var) - if eltype(model_var) <: SVector - for i = 1:length(first(model_var)) - push!(var_names, string(var, "[", i, "]")) - end - elseif ndims(model_var) > 1 - for i = 1:length(first(model_var)) - push!(var_names, string(var, "[", i, "]")) + if exchange(param(model, var)) + model_var = param(model, var) + if eltype(model_var) <: SVector + for i in 1:length(first(model_var)) + push!(var_names, string(var, "[", i, "]")) + end + elseif ndims(model_var) > 1 + for i in 1:length(first(model_var)) + push!(var_names, string(var, "[", i, "]")) + end + else + push!(var_names, var) end else - push!(var_names, var) + @warn("$var is not listed as variable for BMI exchange") end end end @@ -146,15 +145,14 @@ end "Returns input variables from `BMI.get_input_var_names(model::Model)`, there is no distinction between input - and output variables." function BMI.get_output_var_names(model::Model) - BMI.get_input_var_names(model) + return BMI.get_input_var_names(model) end function BMI.get_var_grid(model::Model, name::String) s = split(name, "[") key = symbols(first(s)) - if exchange(param(model, key[1:end-1]), key[end]) == 1 - gridtype = grid_type(param(model, key)) - type = typeof(param(model, key[1:end-1])) + if exchange(param(model, key)) + type = typeof(param(model, key[1:(end - 1)])) return if :reservoir in key 0 elseif :lake in key @@ -177,13 +175,13 @@ end function BMI.get_var_type(model::Model, name::String) value = BMI.get_value_ptr(model, name) - repr(eltype(first(value))) + return repr(eltype(first(value))) end function BMI.get_var_units(model::Model, name::String) key = symbols(first(split(name, "["))) - if exchange(param(model, key[1:end-1]), key[end]) == 1 - get_units(param(model, key[1:end-1]), key[end]) + if exchange(param(model, key)) + get_units(param(model, key[1:(end - 1)]), key[end]) else error("$name not listed as variable for BMI exchange") end @@ -191,17 +189,17 @@ end function BMI.get_var_itemsize(model::Model, name::String) value = BMI.get_value_ptr(model, name) - sizeof(eltype(first(value))) + return sizeof(eltype(first(value))) end function BMI.get_var_nbytes(model::Model, name::String) - sizeof(BMI.get_value_ptr(model, name)) + return sizeof(BMI.get_value_ptr(model, name)) end function BMI.get_var_location(model::Model, name::String) key = symbols(first(split(name, "["))) - if exchange(param(model, key[1:end-1]), key[end]) == 1 - return grid_location(model, key) + if exchange(param(model, key)) + return grid_loc(param(model, key[1:(end - 1)]), key[end]) else error("$name not listed as variable for BMI exchange") end @@ -215,7 +213,7 @@ function BMI.get_current_time(model::Model) end function BMI.get_start_time(model::Model) - 0.0 + return 0.0 end function BMI.get_end_time(model::Model) @@ -227,14 +225,18 @@ function BMI.get_end_time(model::Model) end function BMI.get_time_units(model::Model) - "s" + return "s" end function BMI.get_time_step(model::Model) - Float64(model.config.timestepsecs) + return Float64(model.config.timestepsecs) end -function BMI.get_value(model::Model, name::String, dest::Vector{T}) where {T<:AbstractFloat} +function BMI.get_value( + model::Model, + name::String, + dest::Vector{T}, +) where {T <: AbstractFloat} dest .= copy(BMI.get_value_ptr(model, name)) return dest end @@ -243,7 +245,7 @@ function BMI.get_value_ptr(model::Model, name::String) (; network) = model s = split(name, "[") key = symbols(first(s)) - if exchange(param(model, key[1:end-1]), key[end]) == 1 + if exchange(param(model, key)) n = length(active_indices(network, key)) if occursin("[", name) ind = tryparse(Int, split(s[end], "]")[1]) @@ -270,7 +272,7 @@ function BMI.get_value_at_indices( name::String, dest::Vector{T}, inds::Vector{Int}, -) where {T<:AbstractFloat} +) where {T <: AbstractFloat} dest .= BMI.get_value_ptr(model, name)[inds] return dest end @@ -281,8 +283,12 @@ end Set a model variable `name` to the values in vector `src`, overwriting the current contents. The type and size of `src` must match the model's internal array. """ -function BMI.set_value(model::Model, name::String, src::Vector{T}) where {T<:AbstractFloat} - BMI.get_value_ptr(model, name) .= src +function BMI.set_value( + model::Model, + name::String, + src::Vector{T}, +) where {T <: AbstractFloat} + return BMI.get_value_ptr(model, name) .= src end """ @@ -296,8 +302,8 @@ function BMI.set_value_at_indices( name::String, inds::Vector{Int}, src::Vector{T}, -) where {T<:AbstractFloat} - BMI.get_value_ptr(model, name)[inds] .= src +) where {T <: AbstractFloat} + return BMI.get_value_ptr(model, name)[inds] .= src end function BMI.get_grid_type(model::Model, grid::Int) @@ -318,7 +324,7 @@ function BMI.get_grid_rank(model::Model, grid::Int) end end -function BMI.get_grid_x(model::Model, grid::Int, x::Vector{T}) where {T<:AbstractFloat} +function BMI.get_grid_x(model::Model, grid::Int, x::Vector{T}) where {T <: AbstractFloat} (; reader, network) = model (; dataset) = reader sel = active_indices(network, grids[grid]) @@ -328,7 +334,7 @@ function BMI.get_grid_x(model::Model, grid::Int, x::Vector{T}) where {T<:Abstrac return x end -function BMI.get_grid_y(model::Model, grid::Int, y::Vector{T}) where {T<:AbstractFloat} +function BMI.get_grid_y(model::Model, grid::Int, y::Vector{T}) where {T <: AbstractFloat} (; reader, network) = model (; dataset) = reader sel = active_indices(network, grids[grid]) @@ -368,21 +374,21 @@ function BMI.get_grid_edge_nodes(model::Model, grid::Int, edge_nodes::Vector{Int # inactive nodes (boundary/ghost points) are set at -999 if grid == 3 nodes_at_edge = adjacent_nodes_at_link(network.river.graph) - nodes_at_edge.dst[nodes_at_edge.dst.==m+1] .= -999 - edge_nodes[range(1, n, step = 2)] = nodes_at_edge.src - edge_nodes[range(2, n, step = 2)] = nodes_at_edge.dst + nodes_at_edge.dst[nodes_at_edge.dst .== m + 1] .= -999 + edge_nodes[range(1, n; step = 2)] = nodes_at_edge.src + edge_nodes[range(2, n; step = 2)] = nodes_at_edge.dst return edge_nodes elseif grid == 4 xu = network.land.staggered_indices.xu - edge_nodes[range(1, n, step = 2)] = 1:m - xu[xu.==m+1] .= -999 - edge_nodes[range(2, n, step = 2)] = xu + edge_nodes[range(1, n; step = 2)] = 1:m + xu[xu .== m + 1] .= -999 + edge_nodes[range(2, n; step = 2)] = xu return edge_nodes elseif grid == 5 yu = network.land.staggered_indices.yu - edge_nodes[range(1, n, step = 2)] = 1:m - yu[yu.==m+1] .= -999 - edge_nodes[range(2, n, step = 2)] = yu + edge_nodes[range(1, n; step = 2)] = 1:m + yu[yu .== m + 1] .= -999 + edge_nodes[range(2, n; step = 2)] = yu return edge_nodes elseif grid in 0:2 || grid == 6 @warn("edges are not provided for grid type $grid (variables are located at nodes)") @@ -394,7 +400,7 @@ end # Extension of BMI functions (state handling and start time), required for OpenDA coupling. # May also be useful for other external software packages. function load_state(model::Model) - model = set_states(model) + set_states!(model) return model end @@ -404,9 +410,12 @@ function save_state(model::Model) @info "Write output states to netCDF file `$(model.writer.state_nc_path)`." end write_netcdf_timestep(model, writer.state_dataset, writer.state_parameters) - close(writer.state_dataset) + return close(writer.state_dataset) end function get_start_unix_time(model::Model) - datetime2unix(DateTime(model.config.starttime)) + return datetime2unix(DateTime(model.config.starttime)) end + +exchange(t::Vector) = true +exchange(t) = false \ No newline at end of file diff --git a/src/demand/water_demand.jl b/src/demand/water_demand.jl new file mode 100644 index 000000000..ed3e7c77c --- /dev/null +++ b/src/demand/water_demand.jl @@ -0,0 +1,934 @@ +abstract type AbstractIrrigationModel{T} end +abstract type AbstractAllocationModel{T} end +abstract type AbstractDemandModel end + +struct NoIrrigationPaddy{T} <: AbstractIrrigationModel{T} end +struct NoIrrigationNonPaddy{T} <: AbstractIrrigationModel{T} end +struct NoNonIrrigationDemand <: AbstractDemandModel end +struct NoAllocationLand{T} <: AbstractAllocationModel{T} end + +"Struct to store non-irrigation water demand variables" +@get_units @grid_loc @with_kw struct NonIrrigationDemandVariables{T} + returnflow::Vector{T} # return flow [mm Δt⁻¹] + returnflow_fraction::Vector{T} | "-" # return flow fraction [-] +end + +"Struct to store prescribed water demand variables" +@get_units @grid_loc @with_kw struct PrescibedDemand{T} + demand_gross::Vector{T} # gross water demand [mm Δt⁻¹] + demand_net::Vector{T} # net water demand [mm Δt⁻¹] +end + +"Non-irrigation water demand model" +@with_kw struct NonIrrigationDemand{T} <: AbstractDemandModel + demand::PrescibedDemand{T} + variables::NonIrrigationDemandVariables{T} +end + +# wrapper methods +get_demand_gross(model::NonIrrigationDemand) = model.demand.demand_gross +get_demand_gross(model::NoNonIrrigationDemand) = 0.0 + +"Initialize non-irrigation water demand model for a water use `sector`" +function NonIrrigationDemand(nc, config, inds, dt, sector) + demand_gross = + ncread( + nc, + config, + "vertical.demand.$(sector).demand_gross"; + sel = inds, + defaults = 0.0, + type = Float, + ) .* (dt / basetimestep) + demand_net = + ncread( + nc, + config, + "vertical.demand.$(sector).demand_net"; + sel = inds, + defaults = 0.0, + type = Float, + ) .* (dt / basetimestep) + n = length(inds) + returnflow_f = return_flow_fraction.(demand_gross, demand_net) + + demand = PrescibedDemand{Float}(; demand_gross, demand_net) + vars = NonIrrigationDemandVariables{Float}(; + returnflow_fraction = returnflow_f, + returnflow = fill(Float(0), n), + ) + non_irrigation_demand = NonIrrigationDemand{Float}(; demand, variables = vars) + + return non_irrigation_demand +end + +"Struct to store non-paddy irrigation model variables" +@get_units @grid_loc @with_kw struct NonPaddyVariables{T} + demand_gross::Vector{T} # irrigation gross demand [mm Δt⁻¹] +end + +"Struct to store non-paddy irrigation model parameters" +@get_units @grid_loc @with_kw struct NonPaddyParameters{T} + irrigation_efficiency::Vector{T} | "-" # irrigation efficiency [-] + maximum_irrigation_rate::Vector{T} # maximum irrigation rate [mm Δt⁻¹] + irrigation_areas::Vector{Bool} | "-" # irrigation areas [-] + irrigation_trigger::Vector{Bool} | "-" # irrigation on or off [-]end +end + +"Non-paddy (other crops than flooded rice) irrigation model" +@with_kw struct NonPaddy{T} <: AbstractIrrigationModel{T} + parameters::NonPaddyParameters{T} + variables::NonPaddyVariables{T} +end + +"Initialize non-paddy irrigation model" +function NonPaddy(nc, config, inds, dt) + efficiency = ncread( + nc, + config, + "vertical.demand.nonpaddy.parameters.irrigation_efficiency"; + sel = inds, + defaults = 1.0, + type = Float, + ) + areas = ncread( + nc, + config, + "vertical.demand.nonpaddy.parameters.irrigation_areas"; + sel = inds, + defaults = 1, + optional = false, + type = Int, + ) + irrigation_trigger = ncread( + nc, + config, + "vertical.demand.nonpaddy.parameters.irrigation_trigger"; + sel = inds, + defaults = 1, + optional = false, + type = Bool, + ) + max_irri_rate = + ncread( + nc, + config, + "vertical.demand.nonpaddy.parameters.maximum_irrigation_rate"; + sel = inds, + defaults = 25.0, + type = Float, + ) .* (dt / basetimestep) + + params = NonPaddyParameters{Float}(; + maximum_irrigation_rate = max_irri_rate, + irrigation_efficiency = efficiency, + irrigation_areas = areas, + irrigation_trigger, + ) + vars = NonPaddyVariables{Float}(; demand_gross = fill(mv, length(inds))) + + nonpaddy = NonPaddy{Float}(; variables = vars, parameters = params) + + return nonpaddy +end + +# wrapper methods +get_demand_gross(model::NonPaddy) = model.variables.demand_gross +get_demand_gross(model::NoIrrigationNonPaddy) = 0.0 + +""" + update_demand_gross!(nonpaddy::NonPaddy, soil::SbmSoilModel) + +Update gross water demand `demand_gross` of the non-paddy irrigation model for a single +timestep. + +The gross water demand is based on irrigation that is applied when the `irrigation_trigger` +is `true` (`on`) and when water depletion exceeds the readily available water in the root +zone of the SBM soil model. Irrigation brings the root zone back to field capacity, limited +by the infiltration capacity, taking into account limited irrigation efficiency and limited +by a maximum irrigation rate. +""" +function update_demand_gross!(nonpaddy::NonPaddy, soil::SbmSoilModel) + (; hb, theta_s, theta_r, c, sumlayers, act_thickl, pathfrac, infiltcapsoil) = + soil.parameters + (; h3, n_unsatlayers, zi, ustorelayerdepth, f_infiltration_reduction) = soil.variables + (; + irrigation_areas, + irrigation_trigger, + maximum_irrigation_rate, + irrigation_efficiency, + ) = nonpaddy.parameters + rootingdepth = get_rootingdepth(soil) + + for i in eachindex(irrigation_areas) + if irrigation_areas[i] && irrigation_trigger[i] + usl = set_layerthickness(zi[i], sumlayers[i], act_thickl[i]) + irri_dem_gross = 0.0 + for k in 1:n_unsatlayers[i] + # compute water demand only for root zone through root fraction per layer + rootfrac = min(1.0, (max(0.0, rootingdepth[i] - sumlayers[i][k]) / usl[k])) + # vwc_f and vwc_h3 can be precalculated. + vwc_fc = vwc_brooks_corey(-100.0, hb[i], theta_s[i], theta_r[i], c[i][k]) + vwc_h3 = vwc_brooks_corey(h3[i], hb[i], theta_s[i], theta_r[i], c[i][k]) + depletion = + (vwc_fc * usl[k]) - (ustorelayerdepth[i][k] + theta_r[i] * usl[k]) + depletion *= rootfrac + raw = (vwc_fc - vwc_h3) * usl[k] # readily available water + raw *= rootfrac + + # check if maximum irrigation rate has been applied at the previous time step. + max_irri_rate_applied = + nonpaddy.variables.demand_gross[i] == maximum_irrigation_rate[i] + if depletion >= raw # start irrigation + irri_dem_gross += depletion + # add depletion to irrigation gross demand when the maximum irrigation rate has been + # applied at the previous time step (to get volumetric water content at field capacity) + elseif depletion > 0.0 && max_irri_rate_applied # continue irrigation + irri_dem_gross += depletion + end + end + # limit irrigation demand to infiltration capacity + infiltration_capacity = + f_infiltration_reduction[i] * (1.0 - pathfrac[i]) * infiltcapsoil[i] + irri_dem_gross = min(irri_dem_gross, infiltration_capacity) + irri_dem_gross /= irrigation_efficiency[i] + # limit irrigation demand to the maximum irrigation rate + irri_dem_gross = min(irri_dem_gross, maximum_irrigation_rate[i]) + else + irri_dem_gross = 0.0 + end + nonpaddy.variables.demand_gross[i] = irri_dem_gross + end + return nothing +end +update_demand_gross!(nonpaddy::NoIrrigationNonPaddy, soil::SbmSoilModel) = nothing + +"Struct to store paddy irrigation model variables" +@get_units @grid_loc @with_kw struct PaddyVariables{T} + demand_gross::Vector{T} # irrigation gross demand [mm Δt⁻¹] + h::Vector{T} | "mm" # actual water depth in rice field [mm] + evaporation::Vector{T} # evaporation rate [mm Δt⁻¹] +end + +"Struct to store paddy irrigation model parameters" +@get_units @grid_loc @with_kw struct PaddyParameters{T} + irrigation_efficiency::Vector{T} | "-" # irrigation efficiency [-] + maximum_irrigation_rate::Vector{T} # maximum irrigation rate [mm Δt⁻¹] + irrigation_areas::Vector{Bool} | "-" # irrigation areas [-] + irrigation_trigger::Vector{Bool} | "-" # irrigation on or off [-] + h_min::Vector{T} | "mm" # minimum required water depth in the irrigated rice field [mm] + h_opt::Vector{T} | "mm" # optimal water depth in the irrigated rice fields [mm] + h_max::Vector{T} | "mm" # water depth when rice field starts spilling water (overflow) [mm] +end + +"Paddy (flooded rice) irrigation model" +@with_kw struct Paddy{T} <: AbstractIrrigationModel{T} + parameters::PaddyParameters{T} + variables::PaddyVariables{T} +end + +"Initialize paddy irrigation model" +function Paddy(nc, config, inds, dt) + h_min = ncread( + nc, + config, + "vertical.demand.paddy.parameters.h_min"; + sel = inds, + defaults = 20.0, + type = Float, + ) + h_opt = ncread( + nc, + config, + "vertical.demand.paddy.parameters.h_opt"; + sel = inds, + defaults = 50.0, + type = Float, + ) + h_max = ncread( + nc, + config, + "vertical.demand.paddy.parameters.h_max"; + sel = inds, + defaults = 80.0, + type = Float, + ) + efficiency = ncread( + nc, + config, + "vertical.demand.paddy.parameters.irrigation_efficiency"; + sel = inds, + defaults = 1.0, + type = Float, + ) + areas = ncread( + nc, + config, + "vertical.demand.paddy.parameters.irrigation_areas"; + sel = inds, + optional = false, + type = Bool, + ) + irrigation_trigger = ncread( + nc, + config, + "vertical.demand.paddy.parameters.irrigation_trigger"; + sel = inds, + optional = false, + type = Bool, + ) + max_irri_rate = + ncread( + nc, + config, + "vertical.demand.paddy.parameters.maximum_irrigation_rate"; + sel = inds, + defaults = 25.0, + type = Float, + ) .* (dt / basetimestep) + n = length(inds) + params = PaddyParameters{Float}(; + irrigation_efficiency = efficiency, + maximum_irrigation_rate = max_irri_rate, + irrigation_trigger, + h_min, + h_max, + h_opt, + irrigation_areas = areas, + ) + vars = PaddyVariables{Float}(; + demand_gross = fill(mv, n), + h = fill(0.0, n), + evaporation = fill(0.0, n), + ) + paddy = Paddy{Float}(; parameters = params, variables = vars) + return paddy +end + +# wrapper methods +get_water_depth(model::Paddy) = model.variables.h +get_water_depth(model::NoIrrigationPaddy) = 0.0 +get_demand_gross(model::Paddy) = model.variables.demand_gross +get_demand_gross(model::NoIrrigationPaddy) = 0.0 + +""" + evaporation!(model::Paddy, potential_evaporation) + +Update `evaporation` and the water depth `h` of the paddy irrigation model for a single +timestep. +""" +function evaporation!(model::Paddy, potential_evaporation) + for i in eachindex(potential_evaporation) + if model.parameters.irrigation_areas[i] + evaporation = min(model.variables.h[i], potential_evaporation[i]) + model.variables.h[i] -= evaporation + model.variables.evaporation[i] = evaporation + end + end + return nothing +end +evaporation!(model::NoIrrigationPaddy, potential_evaporation) = nothing + +# wrapper methods +get_evaporation(model::NoIrrigationPaddy) = 0.0 +get_evaporation(model::Paddy) = model.variables.evaporation + +""" + update_runoff!(model::Paddy, runoff) + +Update `runoff` based on the water depth `h_max` (paddy field starts spilling), and update +the water depth `h` of the paddy irrigation model for a single timestep. +""" +function update_runoff!(model::Paddy, runoff) + for i in eachindex(model.parameters.irrigation_areas) + if model.parameters.irrigation_areas[i] + paddy_runoff = max(runoff[i] - model.parameters.h_max[i], 0.0) + model.variables.h[i] = runoff[i] - paddy_runoff + runoff[i] = paddy_runoff + end + end + return nothing +end +update_runoff!(model::NoIrrigationPaddy, runoff) = nothing + +""" + update_demand_gross!(model::Paddy) + +Update gross water demand `demand_gross` of the paddy irrigation model for a single +timestep. + +The gross water demand is based on irrigation that is applied when the `irrigation_trigger` +is `true` (`on`) and when the paddy water depth `h` reaches below the minimum water depth +`h_min`. Irrigation is the amount required to reach the optimal paddy water depth `h_opt`, +taking into account limited irrigation efficiency and limited by a maximum irrigation rate. +""" +function update_demand_gross!(model::Paddy) + (; + irrigation_areas, + irrigation_trigger, + irrigation_efficiency, + maximum_irrigation_rate, + h_opt, + h_min, + ) = model.parameters + (; h, demand_gross) = model.variables + for i in eachindex(irrigation_areas) + if irrigation_areas[i] && irrigation_trigger[i] + # check if maximum irrigation rate has been applied at the previous time step. + max_irri_rate_applied = demand_gross[i] == maximum_irrigation_rate[i] + # start irrigation + irr_depth_paddy = if h[i] < h_min[i] + h_opt[i] - h[i] + elseif h[i] < h_opt[i] && max_irri_rate_applied # continue irrigation + h_opt[i] - h[i] + else + 0.0 + end + irri_dem_gross = irr_depth_paddy / irrigation_efficiency[i] + # limit irrigation demand to the maximum irrigation rate + irri_dem_gross = min(irri_dem_gross, maximum_irrigation_rate[i]) + else + irri_dem_gross = 0.0 + end + demand_gross[i] = irri_dem_gross + end + return nothing +end + +update_demand_gross!(paddy::NoIrrigationPaddy) = nothing + +"Struct to store water demand model variables" +@get_units @grid_loc @with_kw struct DemandVariables{T} + irri_demand_gross::Vector{T} # irrigation gross demand [mm Δt⁻¹] + nonirri_demand_gross::Vector{T} # non-irrigation gross demand [mm Δt⁻¹] + total_gross_demand::Vector{T} # total gross demand [mm Δt⁻¹] + surfacewater_demand::Vector{T} # demand from surface water [mm Δt⁻¹] + groundwater_demand::Vector{T} # demand from groundwater [mm Δt⁻¹] +end + +"Initialize water demand variables" +function DemandVariables(T::Type{<:AbstractFloat}, n::Int) + return DemandVariables{T}(; + irri_demand_gross = zeros(T, n), + nonirri_demand_gross = zeros(T, n), + total_gross_demand = zeros(T, n), + surfacewater_demand = zeros(T, n), + groundwater_demand = zeros(T, n), + ) +end + +"Water demand model" +@with_kw struct Demand{D, I, L, P, NP, V} <: AbstractDemandModel + domestic::D + industry::I + livestock::L + paddy::P + nonpaddy::NP + variables::V +end + +@with_kw struct NoDemand{T} <: AbstractDemandModel + domestic::NoNonIrrigationDemand = NoNonIrrigationDemand() + industry::NoNonIrrigationDemand = NoNonIrrigationDemand() + livestock::NoNonIrrigationDemand = NoNonIrrigationDemand() + paddy::NoIrrigationPaddy{T} = NoIrrigationPaddy{T}() + nonpaddy::NoIrrigationNonPaddy{T} = NoIrrigationNonPaddy{T}() +end + +"Initialize water demand model" +function Demand(nc, config, inds, dt) + domestic = if get(config.model.water_demand, "domestic", false) + NonIrrigationDemand(nc, config, inds, dt, "domestic") + else + NoNonIrrigationDemand() + end + industry = if get(config.model.water_demand, "industry", false) + NonIrrigationDemand(nc, config, inds, dt, "industry") + else + NoNonIrrigationDemand() + end + livestock = if get(config.model.water_demand, "livestock", false) + NonIrrigationDemand(nc, config, inds, dt, "livestock") + else + NoNonIrrigationDemand() + end + paddy = if get(config.model.water_demand, "paddy", false) + Paddy(nc, config, inds, dt) + else + NoIrrigationPaddy{Float}() + end + nonpaddy = if get(config.model.water_demand, "nonpaddy", false) + NonPaddy(nc, config, inds, dt) + else + NoIrrigationNonPaddy{Float}() + end + + n = length(inds) + vars = DemandVariables(Float, n) + demand = Demand(; domestic, industry, livestock, paddy, nonpaddy, variables = vars) + return demand +end + +"Struct to store river allocation model variables" +@get_units @grid_loc @with_kw struct AllocationRiverVariables{T} + act_surfacewater_abst::Vector{T} # actual surface water abstraction [mm Δt⁻¹] + act_surfacewater_abst_vol::Vector{T} | "m3 dt-1" # actual surface water abstraction [m³ Δt⁻¹] + available_surfacewater::Vector{T} | "m3" # available surface water [m³] + nonirri_returnflow::Vector{T} # return flow from non irrigation [mm Δt⁻¹] +end + +"Initialize river allocation model variables" +function AllocationRiverVariables(T::Type{<:AbstractFloat}, n::Int) + return AllocationRiverVariables{T}(; + act_surfacewater_abst = zeros(T, n), + act_surfacewater_abst_vol = zeros(T, n), + available_surfacewater = zeros(T, n), + nonirri_returnflow = zeros(T, n), + ) +end + +"River allocation model" +@with_kw struct AllocationRiver{T} <: AbstractAllocationModel{T} + variables::AllocationRiverVariables{T} +end + +"Initialize water allocation for the river domain" +function AllocationRiver(n) + vars = AllocationRiverVariables(Float, n) + allocation = AllocationRiver{Float}(; variables = vars) + return allocation +end + +"Struct to store land allocation allocation model parameters" +@get_units @grid_loc @with_kw struct AllocationLandParameters{T} + frac_sw_used::Vector{T} | "-" # fraction surface water used [-] + areas::Vector{Int} | "-" # allocation areas [-] +end + +"Struct to store land allocation model variables" +@get_units @grid_loc @with_kw struct AllocationLandVariables{T} + surfacewater_alloc::Vector{T} # allocation from surface water [mm Δt⁻¹] + act_groundwater_abst::Vector{T} # actual groundwater abstraction [mm Δt⁻¹] + act_groundwater_abst_vol::Vector{T} | "m3 dt-1" # actual groundwater abstraction [m³ Δt⁻¹] + available_groundwater::Vector{T} | "m3" # available groundwater [m³] + groundwater_alloc::Vector{T} # allocation from groundwater [mm Δt⁻¹] + irri_alloc::Vector{T} # allocated water for irrigation [mm Δt⁻¹] + nonirri_alloc::Vector{T} # allocated water for non-irrigation [mm Δt⁻¹] + total_alloc::Vector{T} # total allocated water [mm Δt⁻¹] + nonirri_returnflow::Vector{T} # return flow from non irrigation [mm Δt⁻¹] +end + +"Initialize land allocation model variables" +function AllocationLandVariables(T::Type{<:AbstractFloat}, n::Int) + return AllocationLandVariables{T}(; + surfacewater_alloc = zeros(T, n), + act_groundwater_abst = zeros(T, n), + act_groundwater_abst_vol = zeros(T, n), + available_groundwater = zeros(T, n), + groundwater_alloc = zeros(T, n), + irri_alloc = zeros(T, n), + nonirri_alloc = zeros(T, n), + total_alloc = zeros(T, n), + nonirri_returnflow = zeros(T, n), + ) +end + +"Land allocation model" +@with_kw struct AllocationLand{T} <: AbstractAllocationModel{T} + parameters::AllocationLandParameters{T} + variables::AllocationLandVariables{T} +end + +"Initialize water allocation for the land domain" +function AllocationLand(nc, config, inds) + frac_sw_used = ncread( + nc, + config, + "vertical.allocation.parameters.frac_sw_used"; + sel = inds, + defaults = 1, + type = Float, + ) + areas = ncread( + nc, + config, + "vertical.allocation.parameters.areas"; + sel = inds, + defaults = 1, + type = Int, + ) + + n = length(inds) + + params = AllocationLandParameters(; areas = areas, frac_sw_used = frac_sw_used) + vars = AllocationLandVariables(Float, n) + allocation = AllocationLand(; parameters = params, variables = vars) + return allocation +end + +# wrapper methods +get_irrigation_allocated(model::AllocationLand) = model.variables.irri_alloc +get_irrigation_allocated(model::NoAllocationLand) = 0.0 + +"Return return flow fraction based on gross water demand `demand_gross` and net water demand `demand_net`" +function return_flow_fraction(demand_gross, demand_net) + fraction = bounded_divide(demand_net, demand_gross) + returnflow_fraction = 1.0 - fraction + return returnflow_fraction +end + +"Update returnflow fraction for a non-irrigation water demand model" +function return_flow_fraction!(model::NonIrrigationDemand) + (; returnflow_fraction) = model.variables + (; demand_gross, demand_net) = model.demand + @. returnflow_fraction = return_flow_fraction(demand_gross, demand_net) + return nothing +end + +# return zero (gross water demand) if non-irrigation water demand sector is not defined +return_flow_fraction!(model::NoNonIrrigationDemand) = nothing + +"Update water allocation for river and land domains based on local surface water (river) availability." +function surface_water_allocation_local!(land_allocation, demand, river, network, dt) + (; surfacewater_alloc) = land_allocation.variables + (; surfacewater_demand) = demand.variables + (; act_surfacewater_abst_vol, act_surfacewater_abst, available_surfacewater) = + river.allocation.variables + # maps from the land domain to the internal river domain (linear index), excluding water bodies + index_river = network.land.index_river_wb + for i in eachindex(surfacewater_demand) + if index_river[i] > 0.0 + # the available volume is limited by a fixed scaling factor of 0.8 to prevent + # rivers completely drying out. check for abstraction through inflow (external + # negative inflow) and adjust available volume. + if river.inflow[index_river[i]] < 0.0 + inflow = river.inflow[index_river[i]] * dt + available_volume = max(river.volume[index_river[i]] * 0.80 + inflow, 0.0) + else + available_volume = river.volume[index_river[i]] * 0.80 + end + # satisfy surface water demand with available local river volume + surfacewater_demand_vol = surfacewater_demand[i] * 0.001 * network.land.area[i] + abstraction_vol = min(surfacewater_demand_vol, available_volume) + act_surfacewater_abst_vol[index_river[i]] = abstraction_vol + # remaining available surface water and demand + available_surfacewater[index_river[i]] = + max(available_volume - abstraction_vol, 0.0) + abstraction = (abstraction_vol / network.land.area[i]) * 1000.0 + surfacewater_demand[i] = max(surfacewater_demand[i] - abstraction, 0.0) + # update actual abstraction from river and surface water allocation (land cell) + act_surfacewater_abst[index_river[i]] = abstraction + surfacewater_alloc[i] = abstraction + end + end + return nothing +end + +"Update water allocation for river and land domains based on surface water (river) availability for allocation areas." +function surface_water_allocation_area!(land_allocation, demand, river, network) + inds_river = network.river.indices_allocation_areas + inds_land = network.land.indices_allocation_areas + res_index = network.river.reservoir_index + lake_index = network.river.lake_index + + (; available_surfacewater, act_surfacewater_abst_vol, act_surfacewater_abst) = + river.allocation.variables + (; surfacewater_alloc) = land_allocation.variables + (; surfacewater_demand) = demand.variables + + # loop over allocation areas + for i in eachindex(inds_river) + # surface water demand (allocation area) + sw_demand_vol = 0.0 + for j in inds_land[i] + sw_demand_vol += surfacewater_demand[j] * 0.001 * network.land.area[j] + end + # surface water availability (allocation area) + sw_available = 0.0 + for j in inds_river[i] + if res_index[j] > 0 + # for reservoir locations use reservoir volume + k = res_index[j] + available_surfacewater[j] = river.reservoir.volume[k] * 0.98 # limit available reservoir volume + sw_available += available_surfacewater[j] + elseif lake_index[j] > 0 + # for lake locations use lake volume + k = lake_index[j] + available_surfacewater[j] = river.lake.storage[k] * 0.98 # limit available lake volume + sw_available += available_surfacewater[j] + + else + # river volume + sw_available += available_surfacewater[j] + end + end + # total actual surface water abstraction [m3] in an allocation area, minimum of + # available surface water and demand in an allocation area. + sw_abstraction = min(sw_available, sw_demand_vol) + + # fraction of available surface water that can be abstracted at allocation area + # level + frac_abstract_sw = bounded_divide(sw_abstraction, sw_available) + # fraction of water demand that can be satisfied by available surface water at + # allocation area level. + frac_allocate_sw = bounded_divide(sw_abstraction, sw_demand_vol) + + # water abstracted from surface water at each river cell (including reservoir and + # lake locations). + for j in inds_river[i] + act_surfacewater_abst_vol[j] += frac_abstract_sw * available_surfacewater[j] + act_surfacewater_abst[j] = + (act_surfacewater_abst_vol[j] / network.river.area[j]) * 1000.0 + end + + # water allocated to each land cell. + for j in inds_land[i] + surfacewater_alloc[j] += frac_allocate_sw * surfacewater_demand[j] + end + end + return nothing +end + +"Update water allocation for land domain based on local groundwater availability." +function groundwater_allocation_local!(land_allocation, demand, groundwater_volume, network) + (; + surfacewater_alloc, + act_groundwater_abst_vol, + available_groundwater, + act_groundwater_abst, + groundwater_alloc, + ) = land_allocation.variables + (; groundwater_demand, total_gross_demand) = demand.variables + + for i in eachindex(groundwater_demand) + # groundwater demand based on allocation from surface water. + groundwater_demand[i] = max(total_gross_demand[i] - surfacewater_alloc[i], 0.0) + # land index excluding water bodies + if network.index_wb[i] + # satisfy groundwater demand with available local groundwater volume + groundwater_demand_vol = groundwater_demand[i] * 0.001 * network.area[i] + available_volume = groundwater_volume[i] * 0.75 # limit available groundwater volume + abstraction_vol = min(groundwater_demand_vol, available_volume) + act_groundwater_abst_vol[i] = abstraction_vol + # remaining available groundwater and demand + available_groundwater[i] = max(available_volume - abstraction_vol, 0.0) + abstraction = (abstraction_vol / network.area[i]) * 1000.0 + groundwater_demand[i] = max(groundwater_demand[i] - abstraction, 0.0) + # update actual abstraction from groundwater and groundwater allocation (land cell) + act_groundwater_abst[i] = abstraction + groundwater_alloc[i] = abstraction + end + end + return nothing +end + +"Update water allocation for land domain based on groundwater availability for allocation areas." +function groundwater_allocation_area!(land_allocation, demand, network) + inds_river = network.river.indices_allocation_areas + inds_land = network.land.indices_allocation_areas + (; + act_groundwater_abst_vol, + available_groundwater, + act_groundwater_abst, + groundwater_alloc, + ) = land_allocation.variables + + (; groundwater_demand) = demand.variables + + # loop over allocation areas + for i in eachindex(inds_river) + # groundwater demand and availability (allocation area) + gw_demand_vol = 0.0 + gw_available = 0.0 + for j in inds_land[i] + gw_demand_vol += groundwater_demand[j] * 0.001 * network.land.area[j] + gw_available += available_groundwater[j] + end + # total actual groundwater abstraction [m3] in an allocation area, minimum of + # available groundwater and demand in an allocation area. + gw_abstraction = min(gw_available, gw_demand_vol) + + # fraction of available groundwater that can be abstracted at allocation area level + frac_abstract_gw = bounded_divide(gw_abstraction, gw_available) + # fraction of water demand that can be satisfied by available groundwater at + # allocation area level. + frac_allocate_gw = bounded_divide(gw_abstraction, gw_demand_vol) + + # water abstracted from groundwater and allocated. + for j in inds_land[i] + act_groundwater_abst_vol[j] += frac_abstract_gw * available_groundwater[j] + act_groundwater_abst[j] = + 1000.0 * (act_groundwater_abst_vol[j] / network.land.area[j]) + groundwater_alloc[j] += frac_allocate_gw * groundwater_demand[j] + end + end + return nothing +end + +"Return and update non-irrigation sector (domestic, livestock, industry) return flow" +function return_flow(non_irri::NonIrrigationDemand, nonirri_demand_gross, nonirri_alloc) + for i in eachindex(non_irri.variables.returnflow) + frac = bounded_divide(non_irri.demand.demand_gross[i], nonirri_demand_gross[i]) + allocate = frac * nonirri_alloc[i] + non_irri.variables.returnflow[i] = + non_irri.variables.returnflow_fraction[i] * allocate + end + return non_irri.variables.returnflow +end + +# return zero (return flow) if non-irrigation sector is not defined +return_flow(non_irri::NoNonIrrigationDemand, nonirri_demand_gross, nonirri_alloc) = 0.0 + +# wrapper methods +groundwater_volume(model::LateralSSF) = model.volume +groundwater_volume(model) = model.flow.aquifer.volume + +""" + update_water_allocation!(land_allocation, demand::Demand, lateral, network, dt) + +Update water allocation for the land domain `land_allocation` and water allocation for the +river domain (part of `lateral`) based on the water `demand` model for a single timestep. +First, surface water abstraction is computed to satisfy local water demand (non-irrigation +and irrigation), and then updated (including lakes and reservoirs) to satisfy the remaining +water demand for allocation areas. Then groundwater abstraction is computed to satisfy the +remaining local water demand, and then updated to satisfy the remaining water demand for +allocation areas. Finally, non-irrigation return flows are updated. +""" +function update_water_allocation!(land_allocation, demand::Demand, lateral, network, dt) + river = lateral.river + index_river = network.land.index_river_wb + res_index_f = network.river.reservoir_index_f + lake_index_f = network.river.lake_index_f + (; + groundwater_alloc, + surfacewater_alloc, + act_groundwater_abst, + act_groundwater_abst_vol, + total_alloc, + irri_alloc, + nonirri_alloc, + nonirri_returnflow, + ) = land_allocation.variables + + (; surfacewater_demand, nonirri_demand_gross, irri_demand_gross, total_gross_demand) = + demand.variables + + (; frac_sw_used) = land_allocation.parameters + (; act_surfacewater_abst, act_surfacewater_abst_vol) = river.allocation.variables + + surfacewater_alloc .= 0.0 + act_surfacewater_abst .= 0.0 + act_surfacewater_abst_vol .= 0.0 + # total surface water demand for each land cell + @. surfacewater_demand = + frac_sw_used * nonirri_demand_gross + frac_sw_used * irri_demand_gross + + # local surface water demand and allocation (river, excluding reservoirs and lakes) + surface_water_allocation_local!(land_allocation, demand, river, network, dt) + # surface water demand and allocation for areas + surface_water_allocation_area!(land_allocation, demand, river, network) + + @. river.abstraction = act_surfacewater_abst_vol / dt + + # for reservoir and lake locations set river abstraction at zero and abstract volume + # from reservoir and lake, including an update of lake waterlevel + if !isnothing(river.reservoir) + @. river.abstraction[res_index_f] = 0.0 + @. river.reservoir.volume -= act_surfacewater_abst_vol[res_index_f] + elseif !isnothing(river.lake) + @. river.abstraction[lake_index_f] = 0.0 + lakes = river.lake + @. lakes.storage -= act_surfacewater_abst_vol[lake_index_f] + @. lakes.waterlevel = + waterlevel(lakes.storfunc, lakes.area, lakes.storage, lakes.sh) + end + + groundwater_alloc .= 0.0 + act_groundwater_abst_vol .= 0.0 + act_groundwater_abst .= 0.0 + # local groundwater demand and allocation + groundwater_allocation_local!( + land_allocation, + demand, + groundwater_volume(lateral.subsurface), + network.land, + ) + # groundwater demand and allocation for areas + groundwater_allocation_area!(land_allocation, demand, network) + + # irrigation allocation + for i in eachindex(total_alloc) + total_alloc[i] = groundwater_alloc[i] + surfacewater_alloc[i] + frac_irri = bounded_divide(irri_demand_gross[i], total_gross_demand[i]) + irri_alloc[i] = frac_irri * total_alloc[i] + nonirri_alloc[i] = total_alloc[i] - irri_alloc[i] + end + + # non-irrigation return flows + returnflow_livestock = + return_flow(demand.livestock, nonirri_demand_gross, nonirri_alloc) + returnflow_domestic = return_flow(demand.domestic, nonirri_demand_gross, nonirri_alloc) + returnflow_industry = return_flow(demand.industry, nonirri_demand_gross, nonirri_alloc) + + @. nonirri_returnflow = returnflow_livestock + returnflow_domestic + returnflow_industry + + for i in eachindex(nonirri_returnflow) + if index_river[i] > 0.0 + k = index_river[i] + river.allocation.variables.nonirri_returnflow[k] = nonirri_returnflow[i] + nonirri_returnflow[i] = 0.0 + else + nonirri_returnflow[i] = nonirri_returnflow[i] + end + end +end +update_water_allocation!(allocation, demand::NoDemand, lateral, network, dt) = nothing + +""" + update_demand_gross!(demand::Demand) + +Update total irrigation gross water demand `irri_demand_gross`, total non-irrigation gross +water demand `nonirri_demand_gross` and total gross water demand `total_gross_demand`. +""" +function update_demand_gross!(demand::Demand) + (; nonpaddy, paddy, domestic, industry, livestock) = demand + (; irri_demand_gross, nonirri_demand_gross, total_gross_demand) = demand.variables + # get gross water demands + industry_dem = get_demand_gross(industry) + domestic_dem = get_demand_gross(domestic) + livestock_dem = get_demand_gross(livestock) + nonpaddy_dem_gross = get_demand_gross(nonpaddy) + paddy_dem_gross = get_demand_gross(paddy) + # update gross water demands + @. irri_demand_gross = nonpaddy_dem_gross + paddy_dem_gross + @. nonirri_demand_gross = industry_dem + domestic_dem + livestock_dem + @. total_gross_demand = + nonpaddy_dem_gross + paddy_dem_gross + industry_dem + domestic_dem + livestock_dem + + return nothing +end + +update_demand_gross!(demand::NoDemand) = nothing + +""" + update_water_demand!(demand::Demand, soil) + +Update the return flow fraction `returnflow_fraction` of `industry`, `domestic` and +`livestock`, gross water demand `demand_gross` of `paddy` and `nonpaddy` models, and the +total gross water demand, total irrigation gross water demand and total non-irrigation gross +water demand as part of the water `demand` model. +""" +function update_water_demand!(demand::Demand, soil) + (; nonpaddy, paddy, domestic, industry, livestock) = demand + + return_flow_fraction!(industry) + return_flow_fraction!(domestic) + return_flow_fraction!(livestock) + + update_demand_gross!(nonpaddy, soil) + update_demand_gross!(paddy) + update_demand_gross!(demand) + + return nothing +end +update_water_demand!(demand::NoDemand, soil) = nothing \ No newline at end of file diff --git a/src/flextopo.jl b/src/flextopo.jl deleted file mode 100644 index d547a34b9..000000000 --- a/src/flextopo.jl +++ /dev/null @@ -1,882 +0,0 @@ -@get_units @exchange @grid_type @grid_location @with_kw struct FLEXTOPO{T,N} - # Model time step [s] - dt::T | "s" | 0 | "none" | "none" - # Number of classes - nclass::Int | "-" | 0 | "none" | "none" - # Number of cells - n::Int | "-" | 0 | "none" | "none" - #dictionary with all possible functions for each store - dic_function::Dict | "-" | 0 | "none" | "none" - #current class - kclass::Vector{Int64} | "-" | 0 | "none" | "none" - classes::Vector{String} | "-" | 0 | "none" | "none" - select_snow::Vector{String} | "-" | 0 | "none" | "none" - select_interception::Vector{String} | "-" | 0 | "none" | "none" - select_hortonponding::Vector{String} | "-" | 0 | "none" | "none" - select_hortonrunoff::Vector{String} | "-" | 0 | "none" | "none" - select_rootzone::Vector{String} | "-" | 0 | "none" | "none" - select_fast::Vector{String} | "-" | 0 | "none" | "none" - select_slow::Vector{String} | "-" | 0 | "none" | "none" - - #fraction of each class - hrufrac::Vector{SVector{N,T}} | "-" - ## PARAMETERS - ##SNOW - # Correction factor for precipitation [-] - pcorr::Vector{T} | "-" - # Degree-day factor [mm ᵒC⁻¹ Δt⁻¹] - cfmax::Vector{T} | "mm ᵒC-1 dt-1" - # Threshold temperature for snowfall [ᵒC] - tt::Vector{T} | "ᵒC" - # Threshold temperature interval length [ᵒC] - tti::Vector{T} | "ᵒC" - # Threshold temperature for snowmelt [ᵒC] - ttm::Vector{T} | "ᵒC" - # Water holding capacity as fraction of current snow pack [-] - whc::Vector{T} | "-" - # Refreezing efficiency constant in refreezing of freewater in snow [-] - cfr::Vector{T} | "-" - # Correction factor for precipitation [-] - rfcf::Vector{T} | "-" - # Correction factor for snowfall [-] - sfcf::Vector{T} | "-" - ## GLACIER - # Threshold temperature for snowfall above glacier [ᵒC] - g_tt::Vector{T} | "ᵒC" - # Degree-day factor [mm ᵒC⁻¹ Δt⁻¹] for glacier - g_cfmax::Vector{T} | "mm ᵒC-1 dt-1" - # Fraction of the snowpack on top of the glacier converted into ice [Δt⁻¹] - g_sifrac::Vector{T} | "dt-1" - # Water within the glacier [mm] - glacierstore::Vector{T} | "mm" - # Fraction covered by a glacier [-] - glacierfrac::Vector{T} | "-" - ##INTERCEPTION - # Maximum interception storage (in forested and non-forested areas) [mm] - imax::Vector{SVector{N,T}} | "mm" - # Evap correction [-] - ecorr::Vector{T} | "-" - ##HORTON - # Maximum storage capacity in the hortonian ponding storage [mm] - shmax::Vector{SVector{N,T}} | "mm" - #recession coefficient of the hortonian runoff storage [dt-1] - khf::Vector{SVector{N,T}} | "dt-1" - #maximum modelled accumulated frost resulting in shmin [ᵒC Δt] - facc0::Vector{SVector{N,T}} | "ᵒC" - #minimum modelled accumulated frost resulting in shmax [ᵒC Δt] - facc1::Vector{SVector{N,T}} | "ᵒC" - #exponent for the decline of infiltration capacity [-] - fdec::Vector{SVector{N,T}} | "-" - #maximum infiltration capacity from horton ponding [mm dt-1] - fmax::Vector{SVector{N,T}} | "mm dt-1" - #minimum storage capacity in horton ponding (relative to shmax) [-] - shmin::Vector{SVector{N,T}} | "-" - #melt coefficient for melt of frozen topsoil [-] - kmf::Vector{SVector{N,T}} | "-" - ##ROOTZONE - # maximum root-zone storage capacity [mm] - srmax::Vector{SVector{N,T}} | "mm" - # Fraction of root zone storage below which actual evaporation is potential evaporation [-] - lp::Vector{SVector{N,T}} | "-" - # Exponent in soil runoff generation equation [-] - beta::Vector{SVector{N,T}} | "-" - # maximum percolation rate [mm Δt⁻¹] - perc::Vector{SVector{N,T}} | "mm dt-1" - # maximum capillary rise rate [mm Δt⁻¹] - cap::Vector{SVector{N,T}} | "mm dt-1" - #FAST - # Exponent for non linear recession [-] - alfa::Vector{SVector{N,T}} | "-" - #recession coefficient of fast storage [dt-1] - kf::Vector{SVector{N,T}} | "dt-1" - # fraction of qrootzone to slowstorage (1-ds to faststorage) - ds::Vector{SVector{N,T}} | "-" - # SLOW - #recession coefficient of slow storage [dt-1] - ks::Vector{T} | "dt-1" - - ## STATES - ##SNOW - # Snow water equivalent [mm] - snow::Vector{T} | "mm" - # Liquid water content in the snow pack [mm] - snowwater::Vector{T} | "mm" - # Interception storage [mm] - interceptionstorage::Vector{SVector{N,T}} | "mm" - # Storage in the hortonian ponding reservoir [mm] - hortonpondingstorage::Vector{SVector{N,T}} | "mm" - # Storage in the hortonian runoff generation [mm] - hortonrunoffstorage::Vector{SVector{N,T}} | "mm" - # Storage in the root-zone [mm] - rootzonestorage::Vector{SVector{N,T}} | "mm" - # Storage in the root-zone relative to maximum root-zone storage capacity [mm] - srootzone_over_srmax::Vector{SVector{N,T}} | "mm" - # Storage in the fast store [mm] - faststorage::Vector{SVector{N,T}} | "mm" - # Storage in the slow reservoir (for qcapillary calc) [mm] - slowstorage::Vector{T} | "mm" - #states previous time step to calc water balance [mm] - states_::Vector{SVector{N,T}} | "mm" - #states previous time step to calc water balance combined based on perc class. [mm] - states_m::Vector{T} | "mm" - #states averaged over classes - interceptionstorage_m::Vector{T} | "mm" - hortonpondingstorage_m::Vector{T} | "mm" - hortonrunoffstorage_m::Vector{T} | "mm" - srootzone_m::Vector{T} | "mm" - faststorage_m::Vector{T} | "mm" - srootzone_over_srmax_m::Vector{T} | "mm" - - ## FLUXES - #SNOW - # Precipitation [mm Δt⁻¹] - precipitation::Vector{T} | "mm dt-1" - # Temperature [ᵒC] - temperature::Vector{T} | "ᵒC" - # Potential evapotranspiration [mm Δt⁻¹] - potential_evaporation::Vector{T} | "mm dt-1" - # Potential evapotranspiration corrected [mm Δt⁻¹] - epotcorr::Vector{T} | "mm dt-1" - # Precipitation corrected [mm Δt⁻¹] - precipcorr::Vector{T} | "mm dt-1" - # Snow melt + precipitation as rainfall [mm] - rainfallplusmelt::Vector{T} | "mm dt-1" - # Snowfall [mm] - snowfall::Vector{T} | "mm dt-1" - # Snowmelt [mm] - snowmelt::Vector{T} | "mm dt-1" - #INTERCEPTION - # Potential soil evaporation [mm Δt⁻¹] - potsoilevap::Vector{SVector{N,T}} | "mm dt-1" - # Evaporation from interception storage [mm Δt⁻¹] - intevap::Vector{SVector{N,T}} | "mm dt-1" - #effective precipitation [mm Δt⁻¹] - precipeffective::Vector{SVector{N,T}} | "mm dt-1" - # Evaporation from interception sum classes [mm Δt⁻¹] - intevap_m::Vector{T} | "mm dt-1" - #HORTONPONDING - # Evaporation from the hortonion ponding storage [-] - hortonevap::Vector{SVector{N,T}} | "mm dt-1" - # Flux from the hortonian ponding storage to the hortonian runoff storage [mm Δt⁻¹] - qhortonpond::Vector{SVector{N,T}} | "mm dt-1" - # Flux from the hortonian ponding storage to the root zone storage [mm Δt⁻¹] - qhortonrootzone::Vector{SVector{N,T}} | "mm dt-1" - # modeled accumulated frost [ᵒC Δt] - facc::Vector{SVector{N,T}} | "ᵒC Δt" - # Evaporation from the hortonian sum classes [mm Δt⁻¹] - hortonevap_m::Vector{T} | "mm dt-1" - #HORTONRUNOFF - # Flux from the hortonian runoff storage [mm Δt⁻¹] - qhortonrun::Vector{SVector{N,T}} | "mm dt-1" - #ROOTZONE - # Evaporation from the root-zone storage [mm Δt⁻¹] - rootevap::Vector{SVector{N,T}} | "mm dt-1" - # Flux from the root-zone storage [mm Δt⁻¹] - qrootzone::Vector{SVector{N,T}} | "mm dt-1" - # Pref. recharge to fast storage [mm Δt⁻¹] - qrootzonefast::Vector{SVector{N,T}} | "mm dt-1" - # Pref. recharge to slow storage sum classes [mm Δt⁻¹] - qrootzoneslow_m::Vector{T} | "mm dt-1" - # Capillary flux from the slow to the root-zone storage [mm Δt⁻¹] - qcapillary::Vector{SVector{N,T}} | "mm dt-1" - # Capillary flux from the slow to the root-zone storage sum classes [mm Δt⁻¹] - qcapillary_m::Vector{T} | "mm dt-1" - # Percolation flux from the root-zone to the slow storage [mm Δt⁻¹] - qpercolation::Vector{SVector{N,T}} | "mm dt-1" - # Percolation flux from the root-zone to the slow storage sum classes [mm Δt⁻¹] - qpercolation_m::Vector{T} | "mm dt-1" - # Evaporation from the root-zone storage, interception and hortonian [mm Δt⁻¹] - actevap::Vector{SVector{N,T}} | "mm dt-1" - # Evaporation from the root-zone storage, interception and hortonian sum classes [mm Δt⁻¹] - actevap_m::Vector{T} | "mm dt-1" - # Evaporation from the root-zone storage sum classes [mm Δt⁻¹] - rootevap_m::Vector{T} | "mm dt-1" - #FAST - # runoff from fast reservoir [mm Δt⁻¹] - qfast::Vector{SVector{N,T}} | "mm dt-1" - #SLOW - # runoff from slow reservoir [mm Δt⁻¹] - qslow::Vector{T} | "mm dt-1" - #Total [mm Δt⁻¹] - runoff::Vector{T} | "mm dt-1" - # fast runoff sum classes [mm Δt⁻¹] - qfast_tot = Vector{T} | "mm dt-1" - - - ## WATERBALANCES - #water balance snow store - wb_snow::Vector{T} | "mm dt-1" - #water balance interception storage [mm Δt⁻¹] - wb_interception::Vector{SVector{N,T}} | "mm dt-1" - #water balance hortonian ponding storage [mm Δt⁻¹] - wb_hortonponding::Vector{SVector{N,T}} | "mm dt-1" - #water balance hortonian runoff storage [mm Δt⁻¹] - wb_hortonrunoff::Vector{SVector{N,T}} | "mm dt-1" - #water balance root-zone storage [mm Δt⁻¹] - wb_rootzone::Vector{SVector{N,T}} | "mm dt-1" - #water balance fast storage [mm Δt⁻¹] - wb_fast::Vector{SVector{N,T}} | "mm dt-1" - #water balance slow storage [mm Δt⁻¹] - wb_slow::Vector{T} | "mm dt-1" - #total water balance [mm Δt⁻¹] - wb_tot::Vector{T} | "mm dt-1" - -end - - -function common_snow_hbv(flextopo::FLEXTOPO) - for i = 1:flextopo.n - # precip correction - precipcorr = flextopo.precipitation[i] * flextopo.pcorr[i] - - #hbv snow - snow, snowwater, snowmelt, rainfallplusmelt, snowfall = snowpack_hbv( - flextopo.snow[i], - flextopo.snowwater[i], - precipcorr, - flextopo.temperature[i], - flextopo.tti[i], - flextopo.tt[i], - flextopo.ttm[i], - flextopo.cfmax[i], - flextopo.whc[i], - ) - - #wb - wb_snow = - precipcorr - rainfallplusmelt - snow + flextopo.snow[i] - snowwater + - flextopo.snowwater[i] - - #update stores - # states_ is sum of states of previous time steps to compute WB at the end (per class); states_m is for combined stores - flextopo.states_m[i] = flextopo.snow[i] + flextopo.snowwater[i] - flextopo.snowfall[i] = snowfall - flextopo.snowmelt[i] = snowmelt - flextopo.snow[i] = snow - flextopo.snowwater[i] = snowwater - flextopo.rainfallplusmelt[i] = rainfallplusmelt - flextopo.precipcorr[i] = precipcorr - flextopo.wb_snow[i] = wb_snow - end -end - -function common_snow_no_storage(flextopo::FLEXTOPO) - for i = 1:flextopo.n - snow = 0.0 - snowwater = 0.0 - snowfall = 0.0 - snowmelt = 0.0 - # precip correction - precipcorr = flextopo.precipitation[i] * flextopo.pcorr[i] - rainfallplusmelt = precipcorr - - wb_snow = precipcorr - rainfallplusmelt - snow + flextopo.snow[i] - - #update stores - # states_ is sum of states of previous time steps to compute WB at the end (per class); states_m is for combined stores - flextopo.states_m[i] = flextopo.snow[i] + flextopo.snowwater[i] - flextopo.snowfall[i] = snowfall - flextopo.snowmelt[i] = snowmelt - flextopo.snow[i] = snow - flextopo.snowwater[i] = snowwater - flextopo.rainfallplusmelt[i] = rainfallplusmelt - flextopo.wb_snow[i] = wb_snow - flextopo.precipcorr[i] = precipcorr - end -end - -function common_glaciers(flextopo::FLEXTOPO, config) - modelglacier = get(config.model, "glacier", false)::Bool - for i = 1:flextopo.n - if modelglacier - # Run Glacier module and add the snowpack on-top of it. - # Estimate the fraction of snow turned into ice (HBV-light). - # Estimate glacier melt. - - flextopo.snow[i], snow2glacier, flextopo.glacierstore[i], glaciermelt = - glacier_hbv( - flextopo.glacierfrac[i], - flextopo.glacierstore[i], - flextopo.snow[i], - flextopo.temperature[i], - flextopo.g_tt[i], - flextopo.g_cfmax[i], - flextopo.g_sifrac[i], - Second(flextopo.dt), - ) - # Convert to mm per grid cell and add to snowmelt - glaciermelt = glaciermelt * flextopo.glacierfrac[i] - rainfallplusmelt = flextopo.rainfallplusmelt[i] + glaciermelt - else - rainfallplusmelt = flextopo.rainfallplusmelt[i] - end - end -end - - -function interception_no_storage(flextopo::FLEXTOPO) - k = flextopo.kclass[1] - for i = 1:flextopo.n - intevap = 0.0 - precipeffective = max(flextopo.rainfallplusmelt[i], 0.0) - interceptionstorage = 0.0 - - # correction for potential evaporation - epotcorr = flextopo.ecorr[i] * flextopo.potential_evaporation[i] - restevap = max(0.0, epotcorr - intevap) - - #wb interceptionstore - wb_interception = - flextopo.rainfallplusmelt[i] - intevap - precipeffective - interceptionstorage + - flextopo.interceptionstorage[i][k] - - #update stores - flextopo.states_[i] = - setindex(flextopo.states_[i], flextopo.interceptionstorage[i][k], k) - flextopo.interceptionstorage[i] = - setindex(flextopo.interceptionstorage[i], interceptionstorage, k) - flextopo.intevap[i] = setindex(flextopo.intevap[i], intevap, k) - flextopo.precipeffective[i] = - setindex(flextopo.precipeffective[i], precipeffective, k) - flextopo.potsoilevap[i] = setindex(flextopo.potsoilevap[i], restevap, k) - flextopo.epotcorr[i] = epotcorr - flextopo.wb_interception[i] = - setindex(flextopo.wb_interception[i], wb_interception, k) - - #average storage over classes - flextopo.interceptionstorage_m[i] = - sum(flextopo.interceptionstorage[i] .* flextopo.hrufrac[i]) - flextopo.intevap_m[i] = sum(flextopo.intevap[i] .* flextopo.hrufrac[i]) - - end -end - -function interception_overflow(flextopo::FLEXTOPO) - k = flextopo.kclass[1] - for i = 1:flextopo.n - # rainfall added to interception store interceptionstorage - interception = min( - flextopo.rainfallplusmelt[i], - flextopo.imax[i][k] - flextopo.interceptionstorage[i][k], - ) - # current interception storage - interceptionstorage = flextopo.interceptionstorage[i][k] + interception - precipeffective = flextopo.rainfallplusmelt[i] - interception - - # correction for potential evaporation - epotcorr = flextopo.ecorr[i] * flextopo.potential_evaporation[i] - - # evaporation from interception storage - intevap = min(interceptionstorage, epotcorr) - interceptionstorage = interceptionstorage - intevap - restevap = max(0.0, epotcorr - intevap) - - #wb interceptionstore - wb_interception = - flextopo.rainfallplusmelt[i] - intevap - precipeffective - interceptionstorage + - flextopo.interceptionstorage[i][k] - - #update stores - flextopo.states_[i] = - setindex(flextopo.states_[i], flextopo.interceptionstorage[i][k], k) - flextopo.interceptionstorage[i] = - setindex(flextopo.interceptionstorage[i], interceptionstorage, k) - flextopo.intevap[i] = setindex(flextopo.intevap[i], intevap, k) - flextopo.precipeffective[i] = - setindex(flextopo.precipeffective[i], precipeffective, k) - flextopo.potsoilevap[i] = setindex(flextopo.potsoilevap[i], restevap, k) - flextopo.epotcorr[i] = epotcorr - flextopo.wb_interception[i] = - setindex(flextopo.wb_interception[i], wb_interception, k) - - #average storage over classes - flextopo.interceptionstorage_m[i] = - sum(flextopo.interceptionstorage[i] .* flextopo.hrufrac[i]) - flextopo.intevap_m[i] = sum(flextopo.intevap[i] .* flextopo.hrufrac[i]) - - end -end - - -function hortonponding_no_storage(flextopo::FLEXTOPO) - k = flextopo.kclass[1] - for i = 1:flextopo.n - hortonevap = 0.0 - qhortonpond = 0.0 - qhortonrootzone = max(flextopo.precipeffective[i][k], 0.0) - hortonpondingstorage = 0.0 - - wb_hortonponding = - flextopo.precipeffective[i][k] - hortonevap - qhortonpond - qhortonrootzone - - hortonpondingstorage + flextopo.hortonpondingstorage[i][k] - - #update with setindex - flextopo.states_[i] = setindex( - flextopo.states_[i], - flextopo.states_[i][k] + flextopo.hortonpondingstorage[i][k], - k, - ) - flextopo.qhortonpond[i] = setindex(flextopo.qhortonpond[i], qhortonpond, k) - flextopo.qhortonrootzone[i] = - setindex(flextopo.qhortonrootzone[i], qhortonrootzone, k) - flextopo.hortonpondingstorage[i] = - setindex(flextopo.hortonpondingstorage[i], hortonpondingstorage, k) - flextopo.hortonevap[i] = setindex(flextopo.hortonevap[i], hortonevap, k) - flextopo.wb_hortonponding[i] = - setindex(flextopo.wb_hortonponding[i], wb_hortonponding, k) - - #average storage over classes - flextopo.hortonpondingstorage_m[i] = - sum(flextopo.hortonpondingstorage[i] .* flextopo.hrufrac[i]) - flextopo.hortonevap_m[i] = sum(flextopo.hortonevap[i] .* flextopo.hrufrac[i]) - end -end - -function hortonponding(flextopo::FLEXTOPO) - k = flextopo.kclass[1] - for i = 1:flextopo.n - - #calculate reduction of shmax due to frost - facc = min(flextopo.facc[i][k] + flextopo.temperature[i] * flextopo.kmf[i][k], 0.0) - ft = min( - max( - facc / (flextopo.facc1[i][k] - flextopo.facc0[i][k]) - - flextopo.facc0[i][k] / (flextopo.facc1[i][k] - flextopo.facc0[i][k]), - flextopo.shmin[i][k], - ), - 1.0, - ) - shmax_frost = ft * flextopo.shmax[i][k] - - #add effective precipitation from interception to the horton ponding storage. - hortonpondingstorage = - flextopo.hortonpondingstorage[i][k] + flextopo.precipeffective[i][k] - # if soil is filled until max capacity, additional water runs of directly - directrunoff_h = max(hortonpondingstorage - shmax_frost, 0.0) - #update hortonpondingstorage - hortonpondingstorage = hortonpondingstorage - directrunoff_h - #net water which infiltrates in horton (careful, shmax_frost can decrease from one timestep to another) - netin_hortonpond = max(flextopo.precipeffective[i][k] - directrunoff_h, 0.0) - - #evaporation from the horton ponding storage - hortonevap = - hortonpondingstorage > shmax_frost * flextopo.lp[i][k] ? - min(hortonpondingstorage, flextopo.potsoilevap[i][k]) : - min( - flextopo.potsoilevap[i][k] * - (hortonpondingstorage / (shmax_frost * flextopo.lp[i][k])), - ) - #update storage - hortonpondingstorage = hortonpondingstorage - hortonevap - #update restevap - restevap = max(0.0, flextopo.potsoilevap[i][k] - hortonevap) - - #excess water from beta function of netin_hortonpond (due to rouding hortonpondingstorage could be slightly larger than shmax_frost, make sure it is always less than 1) - qhorton_in = - netin_hortonpond * ( - 1.0 - pow( - (1.0 - min(hortonpondingstorage / shmax_frost, 1.0)), - flextopo.beta[i][k], - ) - ) - #update storage - hortonpondingstorage = hortonpondingstorage - qhorton_in - - #total water out of horton ponding consists of directrunoff and excess water from beta function - qhortonpond = directrunoff_h + qhorton_in - - #infiltration to root-zone - qhortonrootzone = - min(hortonpondingstorage / shmax_frost, 1.0) > 0.0 ? - min( - flextopo.fmax[i][k] * exp( - -flextopo.fdec[i][k] * - (1.0 - min(hortonpondingstorage / shmax_frost, 1.0)), - ), - hortonpondingstorage, - ) : 0.0 - hortonpondingstorage = hortonpondingstorage - qhortonrootzone - - #water balance - wb_hortonponding = - flextopo.precipeffective[i][k] - hortonevap - qhortonpond - qhortonrootzone - - hortonpondingstorage + flextopo.hortonpondingstorage[i][k] - - #update states - flextopo.states_[i] = setindex( - flextopo.states_[i], - flextopo.states_[i][k] + flextopo.hortonpondingstorage[i][k], - k, - ) - flextopo.potsoilevap[i] = setindex(flextopo.potsoilevap[i], restevap, k) - flextopo.qhortonpond[i] = setindex(flextopo.qhortonpond[i], qhortonpond, k) - flextopo.qhortonrootzone[i] = - setindex(flextopo.qhortonrootzone[i], qhortonrootzone, k) - flextopo.hortonpondingstorage[i] = - setindex(flextopo.hortonpondingstorage[i], hortonpondingstorage, k) - flextopo.hortonevap[i] = setindex(flextopo.hortonevap[i], hortonevap, k) - flextopo.wb_hortonponding[i] = - setindex(flextopo.wb_hortonponding[i], wb_hortonponding, k) - - #average storage over classes - flextopo.hortonpondingstorage_m[i] = - sum(flextopo.hortonpondingstorage[i] .* flextopo.hrufrac[i]) - flextopo.hortonevap_m[i] = sum(flextopo.hortonevap[i] .* flextopo.hrufrac[i]) - end -end - -function hortonrunoff_no_storage(flextopo::FLEXTOPO) - k = flextopo.kclass[1] - for i = 1:flextopo.n - qhortonrun = 0.0 - hortonrunoffstorage = 0.0 - - wb_hortonrunoff = - flextopo.qhortonpond[i][k] - qhortonrun - hortonrunoffstorage + - flextopo.hortonrunoffstorage[i][k] - - #update with setindex - flextopo.states_[i] = setindex( - flextopo.states_[i], - flextopo.states_[i][k] + flextopo.hortonrunoffstorage[i][k], - k, - ) - flextopo.qhortonrun[i] = setindex(flextopo.qhortonrun[i], qhortonrun, k) - flextopo.hortonrunoffstorage[i] = - setindex(flextopo.hortonrunoffstorage[i], hortonrunoffstorage, k) - flextopo.wb_hortonrunoff[i] = - setindex(flextopo.wb_hortonrunoff[i], wb_hortonrunoff, k) - - #average storage over classes - flextopo.hortonrunoffstorage_m[i] = - sum(flextopo.hortonrunoffstorage[i] .* flextopo.hrufrac[i]) - end -end - -function hortonrunoff(flextopo::FLEXTOPO) - k = flextopo.kclass[1] - for i = 1:flextopo.n - - qhortonrun = min( - flextopo.hortonrunoffstorage[i][k], - flextopo.hortonrunoffstorage[i][k] * flextopo.khf[i][k], - ) - hortonrunoffstorage = - flextopo.hortonrunoffstorage[i][k] + flextopo.qhortonpond[i][k] - qhortonrun - - wb_hortonrunoff = - flextopo.qhortonpond[i][k] - qhortonrun - hortonrunoffstorage + - flextopo.hortonrunoffstorage[i][k] - - #update with setindex - flextopo.states_[i] = setindex( - flextopo.states_[i], - flextopo.states_[i][k] + flextopo.hortonrunoffstorage[i][k], - k, - ) - flextopo.qhortonrun[i] = setindex(flextopo.qhortonrun[i], qhortonrun, k) - flextopo.hortonrunoffstorage[i] = - setindex(flextopo.hortonrunoffstorage[i], hortonrunoffstorage, k) - flextopo.wb_hortonrunoff[i] = - setindex(flextopo.wb_hortonrunoff[i], wb_hortonrunoff, k) - - #average storage over classes - flextopo.hortonrunoffstorage_m[i] = - sum(flextopo.hortonrunoffstorage[i] .* flextopo.hrufrac[i]) - - end -end - -function rootzone_no_storage(flextopo::FLEXTOPO) - k = flextopo.kclass[1] - for i = 1:flextopo.n - rootevap = 0.0 - qpercolation = 0.0 - qcapillary = 0.0 - qrootzone = - max(flextopo.qhortonrootzone[i][k] + flextopo.rootzonestorage[i][k], 0.0) #if store not empty initial conditions - rootzonestorage = 0.0 - - #compute water balance rootzonestorage storage - wb_rootzone = - flextopo.qhortonrootzone[i][k] - rootevap - qrootzone - qpercolation + - qcapillary - rootzonestorage + flextopo.rootzonestorage[i][k] - - #update states and fluxes with setindex - flextopo.states_[i] = setindex( - flextopo.states_[i], - flextopo.states_[i][k] + flextopo.rootzonestorage[i][k], - k, - ) - flextopo.qrootzone[i] = setindex(flextopo.qrootzone[i], qrootzone, k) - flextopo.rootzonestorage[i] = - setindex(flextopo.rootzonestorage[i], rootzonestorage, k) - flextopo.srootzone_over_srmax[i] = setindex( - flextopo.srootzone_over_srmax[i], - flextopo.rootzonestorage[i][k] / flextopo.srmax[i][k], - k, - ) - flextopo.qcapillary[i] = setindex(flextopo.qcapillary[i], qcapillary, k) - flextopo.qpercolation[i] = setindex(flextopo.qpercolation[i], qpercolation, k) - flextopo.rootevap[i] = setindex(flextopo.rootevap[i], rootevap, k) - flextopo.actevap[i] = setindex( - flextopo.actevap[i], - flextopo.rootevap[i][k] + flextopo.intevap[i][k] + flextopo.hortonevap[i][k], - k, - ) - flextopo.wb_rootzone[i] = setindex(flextopo.wb_rootzone[i], wb_rootzone, k) - - #average storage over classes - flextopo.srootzone_m[i] = sum(flextopo.rootzonestorage[i] .* flextopo.hrufrac[i]) - flextopo.srootzone_over_srmax_m[i] = - sum(flextopo.srootzone_over_srmax[i] .* flextopo.hrufrac[i]) - flextopo.rootevap_m[i] = sum(flextopo.rootevap[i] .* flextopo.hrufrac[i]) - flextopo.actevap_m[i] = sum(flextopo.actevap[i] .* flextopo.hrufrac[i]) - end -end - -function rootzone_storage(flextopo::FLEXTOPO) - k = flextopo.kclass[1] - for i = 1:flextopo.n - - #added water to the root-zone. NB: if no horton storages: qhortonrootzone is in fact Pe!! (effective precip). - rootzonestorage = flextopo.rootzonestorage[i][k] + flextopo.qhortonrootzone[i][k] - # if soil is filled until max capacity, additional water runs of directly - directrunoff = max(rootzonestorage - flextopo.srmax[i][k], 0.0) - #update rootzonestorage - rootzonestorage = rootzonestorage - directrunoff - #net water which infiltrates in root-zone - netin_rootzone = flextopo.qhortonrootzone[i][k] - directrunoff - - #evaporation from the rootzone - rootevap = - rootzonestorage > flextopo.srmax[i][k] * flextopo.lp[i][k] ? - min(rootzonestorage, flextopo.potsoilevap[i][k]) : - min( - flextopo.potsoilevap[i][k] * - (rootzonestorage / (flextopo.srmax[i][k] * flextopo.lp[i][k])), - ) - #update storage - rootzonestorage = rootzonestorage - rootevap - - #excess water from beta function of netin_rootzone - qrootzone_in = - netin_rootzone * - (1.0 - pow(1.0 - rootzonestorage / flextopo.srmax[i][k], flextopo.beta[i][k])) - #update storage - rootzonestorage = rootzonestorage - qrootzone_in - - #total water out of root-zone consists of directrunoff and excess water from beta function - qrootzone = directrunoff + qrootzone_in - - #percolation - qpercolation = flextopo.perc[i][k] * rootzonestorage / flextopo.srmax[i][k] - rootzonestorage = rootzonestorage - qpercolation - - #capillary rise - qcapillary = min( - flextopo.cap[i][k] * (1.0 - rootzonestorage / flextopo.srmax[i][k]), - flextopo.slowstorage[i], - ) - rootzonestorage = rootzonestorage + qcapillary - - #compute water balance rootzonestorage storage - wb_rootzone = - flextopo.qhortonrootzone[i][k] - rootevap - qrootzone - qpercolation + - qcapillary - rootzonestorage + flextopo.rootzonestorage[i][k] - - #update states and fluxes with setindex - flextopo.states_[i] = setindex( - flextopo.states_[i], - flextopo.states_[i][k] + flextopo.rootzonestorage[i][k], - k, - ) - flextopo.qrootzone[i] = setindex(flextopo.qrootzone[i], qrootzone, k) - flextopo.rootzonestorage[i] = - setindex(flextopo.rootzonestorage[i], rootzonestorage, k) - flextopo.srootzone_over_srmax[i] = setindex( - flextopo.srootzone_over_srmax[i], - flextopo.rootzonestorage[i][k] / flextopo.srmax[i][k], - k, - ) - flextopo.qcapillary[i] = setindex(flextopo.qcapillary[i], qcapillary, k) - flextopo.qpercolation[i] = setindex(flextopo.qpercolation[i], qpercolation, k) - flextopo.rootevap[i] = setindex(flextopo.rootevap[i], rootevap, k) - flextopo.actevap[i] = setindex( - flextopo.actevap[i], - flextopo.rootevap[i][k] + flextopo.intevap[i][k] + flextopo.hortonevap[i][k], - k, - ) - flextopo.wb_rootzone[i] = setindex(flextopo.wb_rootzone[i], wb_rootzone, k) - - #average storage over classes - flextopo.srootzone_m[i] = sum(flextopo.rootzonestorage[i] .* flextopo.hrufrac[i]) - flextopo.srootzone_over_srmax_m[i] = - sum(flextopo.srootzone_over_srmax[i] .* flextopo.hrufrac[i]) - flextopo.rootevap_m[i] = sum(flextopo.rootevap[i] .* flextopo.hrufrac[i]) - flextopo.actevap_m[i] = sum(flextopo.actevap[i] .* flextopo.hrufrac[i]) - end -end - - -function fast_no_storage(flextopo::FLEXTOPO) - k = flextopo.kclass[1] - for i = 1:flextopo.n - #make sure ds does not exceed 1 - ds = flextopo.ds[i][k] > 1.0 ? 1.0 : flextopo.ds[i][k] - #calc inflow to faststorage - qrootzonefast = flextopo.qrootzone[i][k] * (1.0 - ds) - qfast = qrootzonefast + flextopo.faststorage[i][k] #if store not empty initial conditions, make sure to empty - faststorage = 0.0 - - #compute wb faststorage - wb_fast = qrootzonefast - qfast - faststorage + flextopo.faststorage[i][k] - - #update with setindex - flextopo.states_[i] = setindex( - flextopo.states_[i], - flextopo.states_[i][k] + flextopo.faststorage[i][k], - k, - ) - flextopo.qrootzonefast[i] = setindex(flextopo.qrootzonefast[i], qrootzonefast, k) - flextopo.qfast[i] = setindex(flextopo.qfast[i], qfast, k) - flextopo.faststorage[i] = setindex(flextopo.faststorage[i], faststorage, k) - flextopo.wb_fast[i] = setindex(flextopo.wb_fast[i], wb_fast, k) - - #average storage over classes - flextopo.faststorage_m[i] = sum(flextopo.faststorage[i] .* flextopo.hrufrac[i]) - end -end - -function fast_storage(flextopo::FLEXTOPO) - k = flextopo.kclass[1] - for i = 1:flextopo.n - - #make sure ds does not exceed 1 - ds = flextopo.ds[i][k] > 1.0 ? 1.0 : flextopo.ds[i][k] - - #split part of the outflow from the root-zone to the fast runoff (and part as preferential recharge to the slow reservoir) - qrootzonefast = flextopo.qrootzone[i][k] * (1.0 - ds) - - #fast runoff - qfast = min( - flextopo.faststorage[i][k], - pow(flextopo.faststorage[i][k], flextopo.alfa[i][k]) * flextopo.kf[i][k], - ) - #update store - faststorage = flextopo.faststorage[i][k] + qrootzonefast - qfast - - #compute wb faststorage - wb_fast = qrootzonefast - qfast - faststorage + flextopo.faststorage[i][k] - - #update with setindex - flextopo.states_[i] = setindex( - flextopo.states_[i], - flextopo.states_[i][k] + flextopo.faststorage[i][k], - k, - ) - flextopo.qrootzonefast[i] = setindex(flextopo.qrootzonefast[i], qrootzonefast, k) - flextopo.qfast[i] = setindex(flextopo.qfast[i], qfast, k) - flextopo.faststorage[i] = setindex(flextopo.faststorage[i], faststorage, k) - flextopo.wb_fast[i] = setindex(flextopo.wb_fast[i], wb_fast, k) - - #average storage over classes - flextopo.faststorage_m[i] = sum(flextopo.faststorage[i] .* flextopo.hrufrac[i]) - end -end - -function slow_no_storage(flextopo::FLEXTOPO) - for i = 1:flextopo.n - #make sure ds does not exceed 1 - pref_recharge = flextopo.qrootzone[i] .* min.(flextopo.ds[i], 1.0) - pref_recharge_sum_classes = sum(pref_recharge .* flextopo.hrufrac[i]) - - Qcap_sum_classes = sum(flextopo.qcapillary[i] .* flextopo.hrufrac[i]) - Qperc_sum_classes = sum(flextopo.qpercolation[i] .* flextopo.hrufrac[i]) - - Qsin = pref_recharge_sum_classes + Qperc_sum_classes - Qcap_sum_classes - qslow = Qsin + flextopo.slowstorage[i] # if at start store is not empty - slowstorage = 0.0 - - qfast_tot = - sum(flextopo.qfast[i] .* flextopo.hrufrac[i]) + - sum(flextopo.qhortonrun[i] .* flextopo.hrufrac[i]) - runoff = max(0.0, qslow + qfast_tot) - - wb_slow = Qsin - qslow - slowstorage + flextopo.slowstorage[i] - - #update - flextopo.qfast_tot[i] = qfast_tot - flextopo.states_m[i] = - flextopo.states_m[i] + - sum(flextopo.states_[i] .* flextopo.hrufrac[i]) + - flextopo.slowstorage[i] - flextopo.runoff[i] = runoff - flextopo.qrootzoneslow_m[i] = pref_recharge_sum_classes - flextopo.qpercolation_m[i] = Qperc_sum_classes - flextopo.qcapillary_m[i] = Qcap_sum_classes - flextopo.qslow[i] = qslow - flextopo.slowstorage[i] = slowstorage - flextopo.wb_slow[i] = wb_slow - end -end - -function common_slow_storage(flextopo::FLEXTOPO) - for i = 1:flextopo.n - #make sure ds does not exceed 1 - pref_recharge = flextopo.qrootzone[i] .* min.(flextopo.ds[i], 1.0) - pref_recharge_sum_classes = sum(pref_recharge .* flextopo.hrufrac[i]) - - Qcap_sum_classes = sum(flextopo.qcapillary[i] .* flextopo.hrufrac[i]) - Qperc_sum_classes = sum(flextopo.qpercolation[i] .* flextopo.hrufrac[i]) - - Qsin = pref_recharge_sum_classes + Qperc_sum_classes - Qcap_sum_classes - slowstorage = flextopo.slowstorage[i] + Qsin - - qslow = min(flextopo.slowstorage[i], flextopo.slowstorage[i] * flextopo.ks[i]) - slowstorage = slowstorage - qslow - - qfast_tot = - sum(flextopo.qfast[i] .* flextopo.hrufrac[i]) + - sum(flextopo.qhortonrun[i] .* flextopo.hrufrac[i]) - runoff = max(0.0, qslow + qfast_tot) - - wb_slow = Qsin - qslow - slowstorage + flextopo.slowstorage[i] - - #update - flextopo.qfast_tot[i] = qfast_tot - flextopo.states_m[i] = - flextopo.states_m[i] + - sum(flextopo.states_[i] .* flextopo.hrufrac[i]) + - flextopo.slowstorage[i] - flextopo.runoff[i] = runoff - flextopo.qrootzoneslow_m[i] = pref_recharge_sum_classes - flextopo.qpercolation_m[i] = Qperc_sum_classes - flextopo.qcapillary_m[i] = Qcap_sum_classes - flextopo.qslow[i] = qslow - flextopo.slowstorage[i] = slowstorage - flextopo.wb_slow[i] = wb_slow - end -end - -function watbal(flextopo::FLEXTOPO) - for i = 1:flextopo.n - states = - flextopo.snow[i] + - flextopo.snowwater[i] + - sum(flextopo.interceptionstorage[i] .* flextopo.hrufrac[i]) + - sum(flextopo.hortonpondingstorage[i] .* flextopo.hrufrac[i]) + - sum(flextopo.hortonrunoffstorage[i] .* flextopo.hrufrac[i]) + - sum(flextopo.rootzonestorage[i] .* flextopo.hrufrac[i]) + - sum(flextopo.faststorage[i] .* flextopo.hrufrac[i]) + - sum(flextopo.slowstorage[i] .* flextopo.hrufrac[i]) - wb_tot = - flextopo.precipitation[i] - ( - sum(flextopo.intevap[i] .* flextopo.hrufrac[i]) + - sum(flextopo.hortonevap[i] .* flextopo.hrufrac[i]) + - sum(flextopo.rootevap[i] .* flextopo.hrufrac[i]) - ) - flextopo.runoff[i] - states + flextopo.states_m[i] - #update wb - flextopo.wb_tot[i] = wb_tot - end -end diff --git a/src/flextopo_model.jl b/src/flextopo_model.jl deleted file mode 100644 index 8ea773d63..000000000 --- a/src/flextopo_model.jl +++ /dev/null @@ -1,739 +0,0 @@ -""" - initialize_flextopo_model(config::Config) - -Initial part of the FlexTopo model concept. Reads the input settings and data as defined in the -Config object. Will return a Model that is ready to run. -""" -function initialize_flextopo_model(config::Config) - # unpack the paths to the netCDF files - static_path = input_path(config, config.input.path_static) - - reader = prepare_reader(config) - clock = Clock(config, reader) - dt = clock.dt - - do_reservoirs = get(config.model, "reservoirs", false)::Bool - do_lakes = get(config.model, "lakes", false)::Bool - do_pits = get(config.model, "pits", false)::Bool - - kw_river_tstep = get(config.model, "kw_river_tstep", 0) - kw_land_tstep = get(config.model, "kw_land_tstep", 0) - kinwave_it = get(config.model, "kin_wave_iteration", false)::Bool - - classes = get(config.model, "classes", "") - nclass = length(classes) - @info "Classes are set to names `$(join(classes,", "))` and have size `$nclass`." - kclass = [1] #needed to initialize - - # dictionary of available functions for each store - dic_function = Dict{String,Function}( - "common_snow_hbv" => common_snow_hbv, - "common_snow_no_storage" => common_snow_no_storage, - "common_glaciers" => common_glaciers, - "interception_overflow" => interception_overflow, - "interception_no_storage" => interception_no_storage, - "hortonponding" => hortonponding, - "hortonponding_no_storage" => hortonponding_no_storage, - "hortonrunoff" => hortonrunoff, - "hortonrunoff_no_storage" => hortonrunoff_no_storage, - "rootzone_storage" => rootzone_storage, - "rootzone_no_storage" => rootzone_no_storage, - "fast_no_storage" => fast_no_storage, - "fast_storage" => fast_storage, - "slow_no_storage" => slow_no_storage, - "common_slow_storage" => common_slow_storage, - ) - - select_snow = get(config.model, "select_snow", ["common_snow_hbv"]) - select_interception = - get(config.model, "select_interception", ["interception_overflow"]) - select_hortonponding = - get(config.model, "select_hortonponding", ["hortonponding_no_storage"]) - select_hortonrunoff = - get(config.model, "select_hortonrunoff", ["hortonrunoff_no_storage"]) - select_rootzone = get(config.model, "select_rootzone", ["rootzone_storage"]) - select_fast = get(config.model, "select_fast", ["fast_storage"]) - select_slow = get(config.model, "select_slow", ["common_slow_storage"]) - - nc = NCDataset(static_path) - - subcatch_2d = ncread(nc, config, "subcatchment"; optional = false, allow_missing = true) - # indices based on catchment - inds, rev_inds = active_indices(subcatch_2d, missing) - n = length(inds) - - - # glacier parameters - g_tt = ncread( - nc, - config, - "vertical.g_tt"; - sel = inds, - defaults = 0.0, - type = Float, - fill = 0.0, - ) - g_cfmax = - ncread( - nc, - config, - "vertical.g_cfmax"; - sel = inds, - defaults = 3.0, - type = Float, - fill = 0.0, - ) .* (dt / basetimestep) - - g_sifrac = - ncread( - nc, - config, - "vertical.g_sifrac"; - sel = inds, - defaults = 0.001, - type = Float, - fill = 0.0, - ) .* (dt / basetimestep) - glacierfrac = ncread( - nc, - config, - "vertical.glacierfrac"; - sel = inds, - defaults = 0.0, - type = Float, - fill = 0.0, - ) - glacierstore = ncread( - nc, - config, - "vertical.glacierstore"; - sel = inds, - defaults = 5500.0, - type = Float, - fill = 0.0, - ) - - #snow param (single class) - cfmax = - ncread( - nc, - config, - "vertical.cfmax"; - sel = inds, - defaults = 3.75653, - type = Float, - ) .* (dt / basetimestep) - - tt = ncread(nc, config, "vertical.tt"; sel = inds, defaults = -1.41934, type = Float) - - tti = ncread(nc, config, "vertical.tti"; sel = inds, defaults = 1.0, type = Float) - - ttm = ncread(nc, config, "vertical.ttm"; sel = inds, defaults = -1.41934, type = Float) - - whc = ncread(nc, config, "vertical.whc"; sel = inds, defaults = 0.1, type = Float) - - cfr = ncread(nc, config, "vertical.cfr"; sel = inds, defaults = 0.05, type = Float) - - #parameters which are not class specific - pcorr = ncread(nc, config, "vertical.pcorr"; sel = inds, defaults = 1.0, type = Float) - ecorr = ncread(nc, config, "vertical.ecorr"; sel = inds, defaults = 1.0, type = Float) - rfcf = ncread(nc, config, "vertical.rfcf"; sel = inds, defaults = 1.0, type = Float) - sfcf = ncread(nc, config, "vertical.sfcf"; sel = inds, defaults = 1.0, type = Float) - ks = - ncread(nc, config, "vertical.ks"; sel = inds, defaults = 0.006, type = Float) .* - (dt / basetimestep) - - #initialize parameters that differ per class - hrufrac = ncread( - nc, - config, - "vertical.hrufrac"; - sel = inds, - defaults = 1.0 / length(classes), - type = Float, - dimname = :classes, - ) - imax = ncread( - nc, - config, - "vertical.imax"; - sel = inds, - defaults = 3.0, - type = Float, - dimname = :classes, - ) - shmax = ncread( - nc, - config, - "vertical.shmax"; - sel = inds, - defaults = 30.0, - type = Float, - dimname = :classes, - ) - khf = - ncread( - nc, - config, - "vertical.khf"; - sel = inds, - defaults = 0.5, - type = Float, - dimname = :classes, - ) .* (dt / basetimestep) - facc0 = ncread( - nc, - config, - "vertical.facc0"; - sel = inds, - defaults = -3.0, - type = Float, - dimname = :classes, - ) - facc1 = ncread( - nc, - config, - "vertical.facc1"; - sel = inds, - defaults = 0.0, - type = Float, - dimname = :classes, - ) - fdec = ncread( - nc, - config, - "vertical.fdec"; - sel = inds, - defaults = 0.2, - type = Float, - dimname = :classes, - ) - fmax = - ncread( - nc, - config, - "vertical.fmax"; - sel = inds, - defaults = 2.0, - type = Float, - dimname = :classes, - ) .* (dt / basetimestep) - shmin = ncread( - nc, - config, - "vertical.shmin"; - sel = inds, - defaults = 0.2, - type = Float, - dimname = :classes, - ) - kmf = ncread( - nc, - config, - "vertical.kmf"; - sel = inds, - defaults = 1.0, - type = Float, - dimname = :classes, - ) - srmax = ncread( - nc, - config, - "vertical.srmax"; - sel = inds, - defaults = 260.0, - type = Float, - dimname = :classes, - ) - beta = ncread( - nc, - config, - "vertical.beta"; - sel = inds, - defaults = 0.3, - type = Float, - dimname = :classes, - ) - lp = ncread( - nc, - config, - "vertical.lp"; - sel = inds, - defaults = 0.3, - type = Float, - dimname = :classes, - ) - perc = - ncread( - nc, - config, - "vertical.perc"; - sel = inds, - defaults = 0.30, - type = Float, - dimname = :classes, - ) .* (dt / basetimestep) - cap = - ncread( - nc, - config, - "vertical.cap"; - sel = inds, - defaults = 0.20, - type = Float, - dimname = :classes, - ) .* (dt / basetimestep) - kf = - ncread( - nc, - config, - "vertical.kf"; - sel = inds, - defaults = 0.1, - type = Float, - dimname = :classes, - ) .* (dt / basetimestep) - alfa = ncread( - nc, - config, - "vertical.alfa"; - sel = inds, - defaults = 1.0, - type = Float, - dimname = :classes, - ) - ds = ncread( - nc, - config, - "vertical.ds"; - sel = inds, - defaults = 0.2, - type = Float, - dimname = :classes, - ) - - # read x, y coordinates and calculate cell length [m] - y_nc = read_y_axis(nc) - x_nc = read_x_axis(nc) - y = permutedims(repeat(y_nc, outer = (1, length(x_nc))))[inds] - cellength = abs(mean(diff(x_nc))) - - - sizeinmetres = get(config.model, "sizeinmetres", false)::Bool - xl, yl = cell_lengths(y, cellength, sizeinmetres) - - - interceptionstorage = zeros(Float, nclass, n) - hortonpondingstorage = zeros(Float, nclass, n) - hortonrunoffstorage = zeros(Float, nclass, n) - srootzone_over_srmax = zeros(Float, nclass, n) .+ 1.0 - - states_ = fill(mv, nclass, n) - - potsoilevap = fill(mv, nclass, n) - intevap = fill(mv, nclass, n) - precipeffective = fill(mv, nclass, n) - hortonevap = fill(mv, nclass, n) - rootevap = fill(mv, nclass, n) - qhortonpond = fill(mv, nclass, n) - qhortonrootzone = fill(mv, nclass, n) - facc = zeros(Float, nclass, n) - qhortonrun = fill(mv, nclass, n) - qrootzone = fill(mv, nclass, n) - qrootzonefast = fill(mv, nclass, n) - qfast = fill(mv, nclass, n) - actevap = fill(mv, nclass, n) - qpercolation = fill(mv, nclass, n) - qcapillary = fill(mv, nclass, n) - - wb_interception = fill(mv, nclass, n) - wb_hortonponding = fill(mv, nclass, n) - wb_hortonrunoff = fill(mv, nclass, n) - wb_rootzone = fill(mv, nclass, n) - wb_fast = fill(mv, nclass, n) - - - flextopo = FLEXTOPO{Float,nclass}( - dt = Float(tosecond(dt)), - nclass = nclass, - n = n, - dic_function = dic_function, - kclass = kclass, - classes = classes, - select_snow = select_snow, - select_interception = select_interception, - select_hortonponding = select_hortonponding, - select_hortonrunoff = select_hortonrunoff, - select_rootzone = select_rootzone, - select_fast = select_fast, - select_slow = select_slow, - hrufrac = svectorscopy(hrufrac, Val{nclass}()), - pcorr = pcorr, - ecorr = ecorr, - #glaciers - g_tt = g_tt, - g_cfmax = g_cfmax, - g_sifrac = g_sifrac, - glacierfrac = glacierfrac, - glacierstore = glacierstore, - # snow - tti = tti, - tt = tt, - ttm = ttm, - cfmax = cfmax, - whc = whc, - cfr = cfr, - rfcf = rfcf, - sfcf = sfcf, - #interception - imax = svectorscopy(imax, Val{nclass}()), - #horton - shmax = svectorscopy(shmax, Val{nclass}()), - khf = svectorscopy(khf, Val{nclass}()), - facc0 = svectorscopy(facc0, Val{nclass}()), - facc1 = svectorscopy(facc1, Val{nclass}()), - fdec = svectorscopy(fdec, Val{nclass}()), - fmax = svectorscopy(fmax, Val{nclass}()), - shmin = svectorscopy(shmin, Val{nclass}()), - kmf = svectorscopy(kmf, Val{nclass}()), - #root zone - srmax = svectorscopy(srmax, Val{nclass}()), - lp = svectorscopy(lp, Val{nclass}()), - beta = svectorscopy(beta, Val{nclass}()), - perc = svectorscopy(perc, Val{nclass}()), - cap = svectorscopy(cap, Val{nclass}()), - #fast - ds = svectorscopy(ds, Val{nclass}()), - alfa = svectorscopy(alfa, Val{nclass}()), - kf = svectorscopy(kf, Val{nclass}()), - #slow - ks = ks, - - # # default (cold) states: - snow = zeros(Float, n), - snowwater = zeros(Float, n), - interceptionstorage = svectorscopy(interceptionstorage, Val{nclass}()), - hortonpondingstorage = svectorscopy(hortonpondingstorage, Val{nclass}()), - hortonrunoffstorage = svectorscopy(hortonrunoffstorage, Val{nclass}()), - rootzonestorage = svectorscopy(srmax, Val{nclass}()), - faststorage = 0.0 .* svectorscopy(srmax, Val{nclass}()), - srootzone_over_srmax = svectorscopy(srootzone_over_srmax, Val{nclass}()), - slowstorage = zeros(Float, n) .+ 30.0, - #states previous time step - states_m = fill(mv, n), - states_ = svectorscopy(states_, Val{nclass}()), - #states averaged over all classes - interceptionstorage_m = zeros(Float, n), - hortonpondingstorage_m = zeros(Float, n), - hortonrunoffstorage_m = zeros(Float, n), - srootzone_m = zeros(Float, n), - faststorage_m = zeros(Float, n), - srootzone_over_srmax_m = zeros(Float, n), - - # variables: - precipitation = fill(mv, n), - temperature = fill(mv, n), - potential_evaporation = fill(mv, n), - epotcorr = fill(mv, n), - precipcorr = fill(mv, n), - rainfallplusmelt = fill(mv, n), - snowfall = fill(mv, n), - snowmelt = fill(mv, n), - potsoilevap = svectorscopy(potsoilevap, Val{nclass}()), - precipeffective = svectorscopy(precipeffective, Val{nclass}()), - intevap = svectorscopy(intevap, Val{nclass}()), - hortonevap = svectorscopy(hortonevap, Val{nclass}()), - rootevap = svectorscopy(rootevap, Val{nclass}()), - qhortonpond = svectorscopy(qhortonpond, Val{nclass}()), - qhortonrootzone = svectorscopy(qhortonrootzone, Val{nclass}()), - facc = svectorscopy(facc, Val{nclass}()), - qhortonrun = svectorscopy(qhortonrun, Val{nclass}()), - qrootzone = svectorscopy(qrootzone, Val{nclass}()), - qrootzonefast = svectorscopy(qrootzonefast, Val{nclass}()), - qrootzoneslow_m = fill(mv, n), - qfast = svectorscopy(qfast, Val{nclass}()), - actevap = svectorscopy(actevap, Val{nclass}()), - qpercolation = svectorscopy(qpercolation, Val{nclass}()), - qcapillary = svectorscopy(qcapillary, Val{nclass}()), - qpercolation_m = fill(mv, n), - qcapillary_m = fill(mv, n), - # combined for the classes - actevap_m = fill(mv, n), - intevap_m = fill(mv, n), - hortonevap_m = fill(mv, n), - rootevap_m = fill(mv, n), - qslow = fill(mv, n), - qfast_tot = fill(mv, n), - runoff = fill(mv, n), - wb_snow = fill(mv, n), - wb_interception = svectorscopy(wb_interception, Val{nclass}()), - wb_hortonponding = svectorscopy(wb_hortonponding, Val{nclass}()), - wb_hortonrunoff = svectorscopy(wb_hortonrunoff, Val{nclass}()), - wb_rootzone = svectorscopy(wb_rootzone, Val{nclass}()), - wb_fast = svectorscopy(wb_fast, Val{nclass}()), - wb_slow = fill(mv, n), - wb_tot = fill(mv, n), - ) - - modelsize_2d = size(subcatch_2d) - river_2d = - ncread(nc, config, "river_location"; optional = false, type = Bool, fill = false) - river = river_2d[inds] - riverwidth_2d = - ncread(nc, config, "lateral.river.width"; optional = false, type = Float, fill = 0) - riverwidth = riverwidth_2d[inds] - riverlength_2d = - ncread(nc, config, "lateral.river.length"; optional = false, type = Float, fill = 0) - riverlength = riverlength_2d[inds] - - inds_riv, rev_inds_riv = active_indices(river_2d, 0) - nriv = length(inds_riv) - - # reservoirs - pits = zeros(Bool, modelsize_2d) - if do_reservoirs - reservoirs, resindex, reservoir, pits = - initialize_simple_reservoir(config, nc, inds_riv, nriv, pits, tosecond(dt)) - else - reservoir = () - reservoirs = nothing - resindex = fill(0, nriv) - end - - # lakes - if do_lakes - lakes, lakeindex, lake, pits = - initialize_lake(config, nc, inds_riv, nriv, pits, tosecond(dt)) - else - lake = () - lakes = nothing - lakeindex = fill(0, nriv) - end - - ldd_2d = ncread(nc, config, "ldd"; optional = false, allow_missing = true) - ldd = ldd_2d[inds] - if do_pits - pits_2d = ncread(nc, config, "pits"; optional = false, type = Bool, fill = false) - ldd = set_pit_ldd(pits_2d, ldd, inds) - end - - landslope = - ncread(nc, config, "lateral.land.slope"; optional = false, sel = inds, type = Float) - clamp!(landslope, 0.00001, Inf) - - dl = map(detdrainlength, ldd, xl, yl) - dw = (xl .* yl) ./ dl - olf = initialize_surfaceflow_land( - nc, - config, - inds; - sl = landslope, - dl = dl, - width = map(det_surfacewidth, dw, riverwidth, river), - iterate = kinwave_it, - tstep = kw_land_tstep, - dt = dt, - ) - - graph = flowgraph(ldd, inds, pcr_dir) - - riverlength = riverlength_2d[inds_riv] - riverwidth = riverwidth_2d[inds_riv] - - ldd_riv = ldd_2d[inds_riv] - if do_pits - ldd_riv = set_pit_ldd(pits_2d, ldd_riv, inds_riv) - end - graph_riv = flowgraph(ldd_riv, inds_riv, pcr_dir) - - # the indices of the river cells in the land(+river) cell vector - index_river = filter(i -> !isequal(river[i], 0), 1:n) - frac_toriver = fraction_runoff_toriver(graph, ldd, index_river, landslope, n) - - rf = initialize_surfaceflow_river( - nc, - config, - inds_riv; - dl = riverlength, - width = riverwidth, - reservoir_index = resindex, - reservoir = reservoirs, - lake_index = lakeindex, - lake = lakes, - iterate = kinwave_it, - tstep = kw_river_tstep, - dt = dt, - ) - - # setup subdomains for the land and river kinematic wave domain, if nthreads = 1 - # subdomain is equal to the complete domain - toposort = topological_sort_by_dfs(graph) - toposort_riv = topological_sort_by_dfs(graph_riv) - index_pit_land = findall(x -> x == 5, ldd) - index_pit_river = findall(x -> x == 5, ldd_riv) - streamorder = stream_order(graph, toposort) - min_streamorder_land = get(config.model, "min_streamorder_land", 5) - subbas_order, indices_subbas, topo_subbas = kinwave_set_subdomains( - graph, - toposort, - index_pit_land, - streamorder, - min_streamorder_land, - ) - min_streamorder_river = get(config.model, "min_streamorder_river", 6) - subriv_order, indices_subriv, topo_subriv = kinwave_set_subdomains( - graph_riv, - toposort_riv, - index_pit_river, - streamorder[index_river], - min_streamorder_river, - ) - - modelmap = (vertical = flextopo, lateral = (land = olf, river = rf)) - indices_reverse = ( - land = rev_inds, - river = rev_inds_riv, - reservoir = isempty(reservoir) ? nothing : reservoir.reverse_indices, - lake = isempty(lake) ? nothing : lake.reverse_indices, - ) - - # if fews_run = true, then classes are specified as Float64 index so FEWS can import - # NetCDF output (3D data/SVector). - fews_run = get(config, "fews_run", false)::Bool - classes = fews_run ? Float64.(collect(1:length(flextopo.classes))) : flextopo.classes - - writer = prepare_writer( - config, - modelmap, - indices_reverse, - x_nc, - y_nc, - nc, - extra_dim = (name = "classes", value = classes), - ) - close(nc) - - # for each domain save: - # - the directed acyclic graph (graph), - # - the traversion order (order), - # - upstream_nodes, - # - subdomains for the kinematic wave domains for parallel execution (execution order of - # subbasins (subdomain_order), traversion order per subbasin (topo_subdomain) and - # Vector indices per subbasin matching the traversion order of the complete domain - # (indices_subdomain)) - # - the indices that map it back to the two dimensional grid (indices) - - # for the land domain the x and y length [m] of the grid cells are stored - # for reservoirs and lakes indices information is available from the initialization - # functions - land = ( - graph = graph, - upstream_nodes = filter_upsteam_nodes(graph, pits[inds]), - subdomain_order = subbas_order, - topo_subdomain = topo_subbas, - indices_subdomain = indices_subbas, - order = toposort, - indices = inds, - reverse_indices = rev_inds, - area = xl .* yl, - ) - river = ( - graph = graph_riv, - upstream_nodes = filter_upsteam_nodes(graph_riv, pits[inds_riv]), - subdomain_order = subriv_order, - topo_subdomain = topo_subriv, - indices_subdomain = indices_subriv, - order = toposort_riv, - indices = inds_riv, - reverse_indices = rev_inds_riv, - ) - - model = Model( - config, - (; land, river, reservoir, lake, index_river, frac_toriver), - (subsurface = nothing, land = olf, river = rf), - flextopo, - clock, - reader, - writer, - FlextopoModel(), - ) - - model = set_states(model) - - return model -end - -function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:FlextopoModel} - (; lateral, vertical, network, config) = model - - inds_riv = network.index_river - - #COMMON SNOW - vertical.dic_function[vertical.select_snow[1]](vertical) - - #lateral snow transport - if get(config.model, "masswasting", false)::Bool - lateral_snow_transport!( - vertical.snow, - vertical.snowwater, - lateral.land.sl, - network.land, - ) - end - - if get(config.model, "glacier", false)::Bool - common_glaciers(vertical, config) - end - - for (k, class) in enumerate(vertical.classes) - vertical.kclass[1] = k - - #INTERCEPTION - vertical.dic_function[vertical.select_interception[k]](vertical) - - #HORTON - vertical.dic_function[vertical.select_hortonponding[k]](vertical) - vertical.dic_function[vertical.select_hortonrunoff[k]](vertical) - - #ROOT-ZONE - vertical.dic_function[vertical.select_rootzone[k]](vertical) - - #FAST - vertical.dic_function[vertical.select_fast[k]](vertical) - - end - - #COMMON SLOW - vertical.dic_function[vertical.select_slow[1]](vertical) - - # WAT BAL - watbal(vertical) - - surface_routing(model) - - return model -end - -function set_states(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:FlextopoModel} - (; lateral, config) = model - reinit = get(config.model, "reinit", true)::Bool - # read and set states in model object if reinit=true - if reinit == false - instate_path = input_path(config, config.state.path_input) - set_states(instate_path, model; type = Float, dimname = :classes) - - # update kinematic wave volume for river and land domain - lateral.land.volume .= lateral.land.h .* lateral.land.width .* lateral.land.dl - lateral.river.volume .= lateral.river.h .* lateral.river.width .* lateral.river.dl - - if do_lakes - # storage must be re-initialized after loading the state with the current - # waterlevel otherwise the storage will be based on the initial water level - lakes = lateral.river.lake - lakes.storage .= - initialize_storage(lakes.storfunc, lakes.area, lakes.waterlevel, lakes.sh) - end - end - return model -end diff --git a/src/flow.jl b/src/flow.jl index f7578cc03..dab39db52 100644 --- a/src/flow.jl +++ b/src/flow.jl @@ -1,68 +1,60 @@ abstract type SurfaceFlow end -@get_units @exchange @grid_type @grid_location @with_kw struct SurfaceFlowRiver{T,R,L,W} <: - SurfaceFlow - beta::T | "-" | 0 | "scalar" # constant in Manning's equation - sl::Vector{T} | "m m-1" # Slope [m m⁻¹] - n::Vector{T} | "s m-1/3" # Manning's roughness [s m⁻⅓] - dl::Vector{T} | "m" # Drain length [m] - q::Vector{T} | "m3 s-1" # Discharge [m³ s⁻¹] - qin::Vector{T} | "m3 s-1" # Inflow from upstream cells [m³ s⁻¹] - q_av::Vector{T} | "m3 s-1" # Average discharge [m³ s⁻¹] - qlat::Vector{T} | "m2 s-1" # Lateral inflow per unit length [m² s⁻¹] - inwater::Vector{T} | "m3 s-1" # Lateral inflow [m³ s⁻¹] - inflow::Vector{T} | "m3 s-1" # External inflow (abstraction/supply/demand) [m³ s⁻¹] - inflow_wb::Vector{T} | "m3 s-1" # inflow waterbody (lake or reservoir model) from land part [m³ s⁻¹] - abstraction::Vector{T} | "m3 s-1" # Abstraction (computed as part of water demand and allocation) [m³ s⁻¹] - volume::Vector{T} | "m3" # Kinematic wave volume [m³] (based on water level h) - h::Vector{T} | "m" # Water level [m] - h_av::Vector{T} | "m" # Average water level [m] - bankfull_depth::Vector{T} | "m" # Bankfull water level [m] - dt::T | "s" | 0 | "none" | "none" # Model time step [s] - its::Int | "-" | 0 | "none" | "none" # Number of fixed iterations - width::Vector{T} | "m" # Flow width [m] - alpha_pow::T | "-" | 0 | "scalar" # Used in the power part of alpha - alpha_term::Vector{T} | "-" # Term used in computation of alpha - alpha::Vector{T} | "s3/5 m1/5" # Constant in momentum equation A = alpha*Q^beta, based on Manning's equation - cel::Vector{T} | "m s-1" # Celerity of the kinematic wave - reservoir_index::Vector{Int} | "-" # map cell to 0 (no reservoir) or i (pick reservoir i in reservoir field) - lake_index::Vector{Int} | "-" # map cell to 0 (no lake) or i (pick lake i in lake field) - reservoir::R | "-" | 0 # Reservoir model struct of arrays - lake::L | "-" | 0 # Lake model struct of arrays - allocation::W | "-" | 0 # Water allocation - kinwave_it::Bool | "-" | 0 | "none" | "none" # Boolean for iterations kinematic wave - - # TODO unclear why this causes a MethodError - # function SurfaceFlow{T,R,L}(args...) where {T,R,L} - # equal_size_vectors(args) - # return new(args...) - # end +@get_units @grid_loc @with_kw struct SurfaceFlowRiver{T, R, L, A} <: SurfaceFlow + beta::T # constant in Manning's equation [-] + sl::Vector{T} | "m m-1" # Slope [m m⁻¹] + n::Vector{T} | "s m-1/3" # Manning's roughness [s m⁻⅓] + dl::Vector{T} | "m" # Drain length [m] + q::Vector{T} | "m3 s-1" # Discharge [m³ s⁻¹] + qin::Vector{T} | "m3 s-1" # Inflow from upstream cells [m³ s⁻¹] + q_av::Vector{T} | "m3 s-1" # Average discharge [m³ s⁻¹] + qlat::Vector{T} | "m2 s-1" # Lateral inflow per unit length [m² s⁻¹] + inwater::Vector{T} | "m3 s-1" # Lateral inflow [m³ s⁻¹] + inflow::Vector{T} | "m3 s-1" # External inflow (abstraction/supply/demand) [m³ s⁻¹] + inflow_wb::Vector{T} | "m3 s-1" # inflow waterbody (lake or reservoir model) from land part [m³ s⁻¹] + abstraction::Vector{T} | "m3 s-1" # Abstraction (computed as part of water demand and allocation) [m³ s⁻¹] + volume::Vector{T} | "m3" # Kinematic wave volume [m³] (based on water level h) + h::Vector{T} | "m" # Water level [m] + h_av::Vector{T} | "m" # Average water level [m] + bankfull_depth::Vector{T} | "m" # Bankfull water level [m] + dt::T # Model time step [s] + its::Int # Number of fixed iterations [-] + width::Vector{T} | "m" # Flow width [m] + alpha_pow::T # Used in the power part of alpha [-] + alpha_term::Vector{T} | "-" # Term used in computation of alpha [-] + alpha::Vector{T} | "s3/5 m1/5" # Constant in momentum equation A = alpha*Q^beta, based on Manning's equation + cel::Vector{T} | "m s-1" # Celerity of the kinematic wave + reservoir_index::Vector{Int} | "-" # map cell to 0 (no reservoir) or i (pick reservoir i in reservoir field) + lake_index::Vector{Int} | "-" # map cell to 0 (no lake) or i (pick lake i in lake field) + reservoir::R # Reservoir model struct of arrays + lake::L # Lake model struct of arrays + allocation::A # Water allocation + kinwave_it::Bool # Boolean for iterations kinematic wave end -@get_units @exchange @grid_type @grid_location @with_kw struct SurfaceFlowLand{T} <: - SurfaceFlow - beta::T | "-" | 0 | "scalar" # constant in Manning's equation - sl::Vector{T} | "m m-1" # Slope [m m⁻¹] - n::Vector{T} | "s m-1/3" # Manning's roughness [s m⁻⅓] - dl::Vector{T} | "m" # Drain length [m] - q::Vector{T} | "m3 s-1" # Discharge [m³ s⁻¹] - qin::Vector{T} | "m3 s-1" # Inflow from upstream cells [m³ s⁻¹] - q_av::Vector{T} | "m3 s-1" # Average discharge [m³ s⁻¹] - qlat::Vector{T} | "m2 s-1" # Lateral inflow per unit length [m² s⁻¹] - inwater::Vector{T} | "m3 s-1" # Lateral inflow [m³ s⁻¹] - volume::Vector{T} | "m3" # Kinematic wave volume [m³] (based on water level h) - h::Vector{T} | "m" # Water level [m] - h_av::Vector{T} | "m" # Average water level [m] - dt::T | "s" | 0 | "none" | "none" # Model time step [s] - its::Int | "-" | 0 | "none" | "none" # Number of fixed iterations - width::Vector{T} | "m" # Flow width [m] - alpha_pow::T | "-" | 0 | "scalar" # Used in the power part of alpha - alpha_term::Vector{T} | "-" # Term used in computation of alpha - alpha::Vector{T} | "s3/5 m1/5" # Constant in momentum equation A = alpha * Q^beta, based on Manning's equation - cel::Vector{T} | "m s-1" # Celerity of the kinematic wave - to_river::Vector{T} | "m3 s-1" # Part of overland flow [m³ s⁻¹] that flows to the river - kinwave_it::Bool | "-" | 0 | "none" | "none" # Boolean for iterations kinematic wave +@get_units @grid_loc @with_kw struct SurfaceFlowLand{T} <: SurfaceFlow + beta::T # constant in Manning's equation [-] + sl::Vector{T} | "m m-1" # Slope [m m⁻¹] + n::Vector{T} | "s m-1/3" # Manning's roughness [s m⁻⅓] + dl::Vector{T} | "m" # Drain length [m] + q::Vector{T} | "m3 s-1" # Discharge [m³ s⁻¹] + qin::Vector{T} | "m3 s-1" # Inflow from upstream cells [m³ s⁻¹] + q_av::Vector{T} | "m3 s-1" # Average discharge [m³ s⁻¹] + qlat::Vector{T} | "m2 s-1" # Lateral inflow per unit length [m² s⁻¹] + inwater::Vector{T} | "m3 s-1" # Lateral inflow [m³ s⁻¹] + volume::Vector{T} | "m3" # Kinematic wave volume [m³] (based on water level h) + h::Vector{T} | "m" # Water level [m] + h_av::Vector{T} | "m" # Average water level [m] + dt::T # Model time step [s] + its::Int # Number of fixed iterations [-] + width::Vector{T} | "m" # Flow width [m] + alpha_pow::T # Used in the power part of alpha [-] + alpha_term::Vector{T} | "-" # Term used in computation of alpha [-] + alpha::Vector{T} | "s3/5 m1/5" # Constant in momentum equation A = alpha * Q^beta, based on Manning's equation + cel::Vector{T} | "m s-1" # Celerity of the kinematic wave + to_river::Vector{T} | "m3 s-1" # Part of overland flow [m³ s⁻¹] that flows to the river + kinwave_it::Bool # Boolean for iterations kinematic wave [-] end function initialize_surfaceflow_land(nc, config, inds; sl, dl, width, iterate, tstep, dt) @@ -75,7 +67,7 @@ function initialize_surfaceflow_land(nc, config, inds; sl, dl, width, iterate, t ncread(nc, config, "lateral.land.n"; sel = inds, defaults = 0.072, type = Float) n = length(inds) - sf_land = SurfaceFlowLand( + sf_land = SurfaceFlowLand(; beta = Float(0.6), sl = sl, n = n_land, @@ -149,10 +141,9 @@ function initialize_surfaceflow_river( clamp!(sl, 0.00001, Inf) do_water_demand = haskey(config.model, "water_demand") - n = length(inds) - sf_river = SurfaceFlowRiver( + sf_river = SurfaceFlowRiver(; beta = Float(0.6), sl = sl, n = n_river, @@ -181,16 +172,14 @@ function initialize_surfaceflow_river( reservoir = reservoir, lake = lake, kinwave_it = iterate, - allocation = do_water_demand ? initialize_allocation_river(n) : nothing, + allocation = do_water_demand ? AllocationRiver(n) : nothing, ) return sf_river end - -function update(sf::SurfaceFlowLand, network, frac_toriver) - (; subdomain_order, topo_subdomain, indices_subdomain, upstream_nodes) = - network +function update!(sf::SurfaceFlowLand, network, frac_toriver) + (; subdomain_order, topo_subdomain, indices_subdomain, upstream_nodes) = network ns = length(subdomain_order) @@ -204,10 +193,10 @@ function update(sf::SurfaceFlowLand, network, frac_toriver) sf.to_river .= 0.0 dt, its = stable_timestep(sf) - for _ = 1:its + for _ in 1:its sf.qin .= 0.0 - for k = 1:ns - threaded_foreach(eachindex(subdomain_order[k]), basesize = 1) do i + for k in 1:ns + threaded_foreach(eachindex(subdomain_order[k]); basesize = 1) do i m = subdomain_order[k][i] for (n, v) in zip(indices_subdomain[m], topo_subdomain[m]) # for a river cell without a reservoir or lake part of the upstream @@ -252,12 +241,11 @@ function update(sf::SurfaceFlowLand, network, frac_toriver) sf.h_av ./= its sf.to_river ./= its sf.volume .= sf.dl .* sf.width .* sf.h + return nothing end -function update(sf::SurfaceFlowRiver, network, doy) - - (; graph, subdomain_order, topo_subdomain, indices_subdomain, upstream_nodes) = - network +function update!(sf::SurfaceFlowRiver, network, doy) + (; graph, subdomain_order, topo_subdomain, indices_subdomain, upstream_nodes) = network ns = length(subdomain_order) @@ -283,10 +271,10 @@ function update(sf::SurfaceFlowRiver, network, doy) end dt, its = stable_timestep(sf) - for _ = 1:its + for _ in 1:its sf.qin .= 0.0 - for k = 1:ns - threaded_foreach(eachindex(subdomain_order[k]), basesize = 1) do i + for k in 1:ns + threaded_foreach(eachindex(subdomain_order[k]); basesize = 1) do i m = subdomain_order[k][i] for (n, v) in zip(indices_subdomain[m], topo_subdomain[m]) # sf.qin by outflow from upstream reservoir or lake location is added @@ -318,7 +306,7 @@ function update(sf::SurfaceFlowRiver, network, doy) # run reservoir model and copy reservoir outflow to inflow (qin) of # downstream river cell i = sf.reservoir_index[v] - update(sf.reservoir, i, sf.q[v] + sf.inflow_wb[v], dt) + update!(sf.reservoir, i, sf.q[v] + sf.inflow_wb[v], dt) downstream_nodes = outneighbors(graph, v) n_downstream = length(downstream_nodes) @@ -339,7 +327,7 @@ function update(sf::SurfaceFlowRiver, network, doy) # run lake model and copy lake outflow to inflow (qin) of downstream river # cell i = sf.lake_index[v] - update(sf.lake, i, sf.q[v] + sf.inflow_wb[v], doy, dt) + update!(sf.lake, i, sf.q[v] + sf.inflow_wb[v], doy, dt) downstream_nodes = outneighbors(graph, v) n_downstream = length(downstream_nodes) @@ -361,7 +349,6 @@ function update(sf::SurfaceFlowRiver, network, doy) crossarea = sf.alpha[v] * pow(sf.q[v], sf.beta) sf.h[v] = crossarea / sf.width[v] sf.volume[v] = sf.dl[v] * sf.width[v] * sf.h[v] - sf.q_av[v] += sf.q[v] sf.h_av[v] += sf.h[v] end @@ -370,9 +357,11 @@ function update(sf::SurfaceFlowRiver, network, doy) end sf.q_av ./= its sf.h_av ./= its + sf.volume .= sf.dl .* sf.width .* sf.h + return nothing end -function stable_timestep(sf::S) where {S<:SurfaceFlow} +function stable_timestep(sf::S) where {S <: SurfaceFlow} n = length(sf.q) # two options for iteration, fixed or based on courant number. if sf.kinwave_it @@ -381,7 +370,7 @@ function stable_timestep(sf::S) where {S<:SurfaceFlow} else # calculate celerity courant = zeros(n) - for v = 1:n + for v in 1:n if sf.q[v] > 0.0 sf.cel[v] = 1.0 / (sf.alpha[v] * sf.beta * pow(sf.q[v], (sf.beta - 1.0))) @@ -400,20 +389,38 @@ function stable_timestep(sf::S) where {S<:SurfaceFlow} return dt, its end -@get_units @exchange @grid_type @grid_location @with_kw struct LateralSSF{T} - kh_0::Vector{T} | "m d-1" # Horizontal hydraulic conductivity at soil surface [m d⁻¹] - f::Vector{T} | "m-1" # A scaling parameter [m⁻¹] (controls exponential decline of kh_0) - kh::Vector{T} | "m d-1" # Horizontal hydraulic conductivity [m d⁻¹] +@get_units @grid_loc struct KhExponential{T} + # Horizontal hydraulic conductivity at soil surface [m d⁻¹] + kh_0::Vector{T} | "m d-1" + # A scaling parameter [m⁻¹] (controls exponential decline of kh_0) + f::Vector{T} | "m-1" +end + +@get_units @grid_loc struct KhExponentialConstant{T} + # Exponential horizontal hydraulic conductivity profile type + exponential::KhExponential + # Depth [m] from soil surface for which exponential decline of kv_0 is valid + z_exp::Vector{T} | "m" +end + +@get_units @grid_loc struct KhLayered{T} + # Horizontal hydraulic conductivity [m d⁻¹] + kh::Vector{T} | "m d-1" +end + +abstract type SubsurfaceFlow end + +@get_units @grid_loc @with_kw struct LateralSSF{T, Kh} <: SubsurfaceFlow + kh_profile::Kh # Horizontal hydraulic conductivity profile type [-] khfrac::Vector{T} | "-" # A muliplication factor applied to vertical hydraulic conductivity `kv` [-] soilthickness::Vector{T} | "m" # Soil thickness [m] theta_s::Vector{T} | "-" # Saturated water content (porosity) [-] theta_r::Vector{T} | "-" # Residual water content [-] - dt::T | "d" | 0 | "none" | "none" # model time step [d] - slope::Vector{T} | "m m-1" # Slope [m m⁻¹] + dt::T # model time step [d] + slope::Vector{T} | "m m-1" # Slope [m m⁻¹] dl::Vector{T} | "m" # Drain length [m] dw::Vector{T} | "m" # Flow width [m] zi::Vector{T} | "m" # Pseudo-water table depth [m] (top of the saturated zone) - z_exp::Vector{T} | "m" # Depth [m] from soil surface for which exponential decline of kv_0 is valid exfiltwater::Vector{T} | "m dt-1" # Exfiltration [m Δt⁻¹] (groundwater above surface level, saturated excess conditions) recharge::Vector{T} | "m2 dt-1" # Net recharge to saturated store [m² Δt⁻¹] ssf::Vector{T} | "m3 d-1" # Subsurface flow [m³ d⁻¹] @@ -422,20 +429,18 @@ end to_river::Vector{T} | "m3 d-1" # Part of subsurface flow [m³ d⁻¹] that flows to the river volume::Vector{T} | "m3" # Subsurface volume [m³] - function LateralSSF{T}(args...) where {T} + function LateralSSF{T, Kh}(args...) where {T, Kh} equal_size_vectors(args) return new(args...) end end -function update(ssf::LateralSSF, network, frac_toriver, ksat_profile) - (; subdomain_order, topo_subdomain, indices_subdomain, upstream_nodes, area) = - network - +function update!(ssf::LateralSSF, network, frac_toriver) + (; subdomain_order, topo_subdomain, indices_subdomain, upstream_nodes, area) = network ns = length(subdomain_order) - for k = 1:ns - threaded_foreach(eachindex(subdomain_order[k]), basesize = 1) do i + for k in 1:ns + threaded_foreach(eachindex(subdomain_order[k]); basesize = 1) do i m = subdomain_order[k][i] for (n, v) in zip(indices_subdomain[m], topo_subdomain[m]) # for a river cell without a reservoir or lake part of the upstream @@ -452,42 +457,21 @@ function update(ssf::LateralSSF, network, frac_toriver, ksat_profile) upstream_nodes[n], eltype(ssf.to_river), ) - if (ksat_profile == "exponential") || - (ksat_profile == "exponential_constant") - ssf.ssf[v], ssf.zi[v], ssf.exfiltwater[v] = kinematic_wave_ssf( - ssf.ssfin[v], - ssf.ssf[v], - ssf.zi[v], - ssf.recharge[v], - ssf.kh_0[v], - ssf.slope[v], - ssf.theta_s[v] - ssf.theta_r[v], - ssf.f[v], - ssf.soilthickness[v], - ssf.dt, - ssf.dl[v], - ssf.dw[v], - ssf.ssfmax[v], - ssf.z_exp[v], - ksat_profile, - ) - elseif (ksat_profile == "layered") || - (ksat_profile == "layered_exponential") - ssf.ssf[v], ssf.zi[v], ssf.exfiltwater[v] = kinematic_wave_ssf( - ssf.ssfin[v], - ssf.ssf[v], - ssf.zi[v], - ssf.recharge[v], - ssf.kh[v], - ssf.slope[v], - ssf.theta_s[v] - ssf.theta_r[v], - ssf.soilthickness[v], - ssf.dt, - ssf.dl[v], - ssf.dw[v], - ssf.ssfmax[v], - ) - end + ssf.ssf[v], ssf.zi[v], ssf.exfiltwater[v] = kinematic_wave_ssf( + ssf.ssfin[v], + ssf.ssf[v], + ssf.zi[v], + ssf.recharge[v], + ssf.slope[v], + ssf.theta_s[v] - ssf.theta_r[v], + ssf.soilthickness[v], + ssf.dt, + ssf.dl[v], + ssf.dw[v], + ssf.ssfmax[v], + ssf.kh_profile, + v, + ) ssf.volume[v] = (ssf.theta_s[v] - ssf.theta_r[v]) * (ssf.soilthickness[v] - ssf.zi[v]) * @@ -495,61 +479,65 @@ function update(ssf::LateralSSF, network, frac_toriver, ksat_profile) end end end + return nothing end -@get_units @exchange @grid_type @grid_location @with_kw struct GroundwaterExchange{T} - dt::T | "d" | 0 | "none" | "none" # model time step [d] +@get_units@grid_loc @with_kw struct GroundwaterExchange{T} <: SubsurfaceFlow + dt::T # model time step [d] exfiltwater::Vector{T} | "m dt-1" # Exfiltration [m Δt⁻¹] (groundwater above surface level, saturated excess conditions) zi::Vector{T} | "m" # Pseudo-water table depth [m] (top of the saturated zone) to_river::Vector{T} | "m3 d-1" # Part of subsurface flow [m³ d⁻¹] that flows to the river ssf::Vector{T} | "m3 d-1" # Subsurface flow [m³ d⁻¹] end -@get_units @exchange @grid_type @grid_location @with_kw struct ShallowWaterRiver{T,R,L,F,W} - n::Int | "-" | 0 | "none" | "none" # number of cells - ne::Int | "-" | 0 | "none" | "none" # number of edges/links - active_n::Vector{Int} | "-" # active nodes - active_e::Vector{Int} | "-" | _ | "edge" # active edges/links - g::T | "m s-2" | 0 | "scalar" # acceleration due to gravity - alpha::T | "-" | 0 | "scalar" # stability coefficient (Bates et al., 2010) - h_thresh::T | "m" | 0 | "scalar" # depth threshold for calculating flow - dt::T | "s" | 0 | "none" | "none" # model time step [s] - q::Vector{T} | "m3 s-1" | _ | "edge" # river discharge (subgrid channel) - q0::Vector{T} | "m3 s-1" | _ | "edge" # river discharge (subgrid channel) at previous time step - q_av::Vector{T} | "m3 s-1" | _ | "edge" # average river channel (+ floodplain) discharge [m³ s⁻¹] - q_channel_av::Vector{T} | "m3 s-1" # average river channel discharge [m³ s⁻¹] - zb_max::Vector{T} | "m" # maximum channel bed elevation - mannings_n_sq::Vector{T} | "(s m-1/3)2" | _ | "edge" # Manning's roughness squared at edge/link - mannings_n::Vector{T} | "s m-1/3" # Manning's roughness at node - h::Vector{T} | "m" # water depth - zs_max::Vector{T} | "m" | _ | "edge" # maximum water elevation at edge - zs_src::Vector{T} | "m" # water elevation of source node of edge - zs_dst::Vector{T} | "m" # water elevation of downstream node of edge - hf::Vector{T} | "m" | _ | "edge" # water depth at edge/link - h_av::Vector{T} | "m" # average water depth - dl::Vector{T} | "m" # river length - dl_at_link::Vector{T} | "m" | _ | "edge" # river length at edge/link - width::Vector{T} | "m" # river width - width_at_link::Vector{T} | "m" | _ | "edge" # river width at edge/link - a::Vector{T} | "m2" | _ | "edge" # flow area at edge/link - r::Vector{T} | "m" | _ | "edge" # wetted perimeter at edge/link - volume::Vector{T} | "m3" # river volume - error::Vector{T} | "m3" # error volume - inwater::Vector{T} | "m3 s-1" # lateral inflow [m³ s⁻¹] - inflow::Vector{T} | "m3 s-1" # external inflow (abstraction/supply/demand) [m³ s⁻¹] - abstraction::Vector{T} | "m3 s-1" # abstraction (computed as part of water demand and allocation) [m³ s⁻¹] - inflow_wb::Vector{T} | "m3 s-1" # inflow waterbody (lake or reservoir model) from land part [m³ s⁻¹] - bankfull_volume::Vector{T} | "m3" # bankfull volume - bankfull_depth::Vector{T} | "m" # bankfull depth - zb::Vector{T} | "m" # river bed elevation - froude_limit::Bool | "-" | 0 | "none" | "none" # if true a check is performed if froude number > 1.0 (algorithm is modified) - reservoir_index::Vector{Int} | "-" | 0 # river cell index with a reservoir (each index of reservoir_index maps to reservoir i in reservoir field) - lake_index::Vector{Int} | "-" | 0 # river cell index with a lake (each index of lake_index maps to lake i in lake field) - waterbody::Vector{Bool} | "-" # water body cells (reservoir or lake) - reservoir::R | "-" | 0 # Reservoir model struct of arrays - lake::L | "-" | 0 # Lake model struct of arrays - floodplain::F | "-" | 0 # Floodplain (1D) schematization - allocation::W | "-" | 0 # Water allocation +get_water_depth(subsurface::SubsurfaceFlow) = subsurface.zi +get_exfiltwater(subsurface::SubsurfaceFlow) = subsurface.exfiltwater + +@get_units @grid_loc @with_kw struct ShallowWaterRiver{T, R, L, F, A} + n::Int # number of cells [-] + ne::Int # number of edges/links [-] + active_n::Vector{Int} | "-" # active nodes [-] + active_e::Vector{Int} | "-" | "edge" # active edges/links [-] + g::T # acceleration due to gravity [m s⁻²] + alpha::T # stability coefficient (Bates et al., 2010) [-] + h_thresh::T # depth threshold for calculating flow [m] + dt::T # model time step [s] + q::Vector{T} | "m3 s-1" | "edge" # river discharge (subgrid channel) + q0::Vector{T} | "m3 s-1" | "edge" # river discharge (subgrid channel) at previous time step + q_av::Vector{T} | "m3 s-1" | "edge" # average river channel (+ floodplain) discharge [m³ s⁻¹] + q_channel_av::Vector{T} | "m3 s-1" # average river channel discharge [m³ s⁻¹] + zb_max::Vector{T} | "m" # maximum channel bed elevation + mannings_n_sq::Vector{T} | "(s m-1/3)2" | "edge" # Manning's roughness squared at edge/link + mannings_n::Vector{T} | "s m-1/3" # Manning's roughness at node + h::Vector{T} | "m" # water depth + zs_max::Vector{T} | "m" | "edge" # maximum water elevation at edge + zs_src::Vector{T} | "m" # water elevation of source node of edge + zs_dst::Vector{T} | "m" # water elevation of downstream node of edge + hf::Vector{T} | "m" | "edge" # water depth at edge/link + h_av::Vector{T} | "m" # average water depth + dl::Vector{T} | "m" # river length + dl_at_link::Vector{T} | "m" | "edge" # river length at edge/link + width::Vector{T} | "m" # river width + width_at_link::Vector{T} | "m" | "edge" # river width at edge/link + a::Vector{T} | "m2" | "edge" # flow area at edge/link + r::Vector{T} | "m" | "edge" # wetted perimeter at edge/link + volume::Vector{T} | "m3" # river volume + error::Vector{T} | "m3" # error volume + inwater::Vector{T} | "m3 s-1" # lateral inflow [m³ s⁻¹] + inflow::Vector{T} | "m3 s-1" # external inflow (abstraction/supply/demand) [m³ s⁻¹] + abstraction::Vector{T} | "m3 s-1" # abstraction (computed as part of water demand and allocation) [m³ s⁻¹] + inflow_wb::Vector{T} | "m3 s-1" # inflow waterbody (lake or reservoir model) from land part [m³ s⁻¹] + bankfull_volume::Vector{T} | "m3" # bankfull volume + bankfull_depth::Vector{T} | "m" # bankfull depth + zb::Vector{T} | "m" # river bed elevation + froude_limit::Bool # if true a check is performed if froude number > 1.0 (algorithm is modified) [-] + reservoir_index::Vector{Int} | "-" # river cell index with a reservoir (each index of reservoir_index maps to reservoir i in reservoir field) + lake_index::Vector{Int} | "-" # river cell index with a lake (each index of lake_index maps to lake i in lake field) + waterbody::Vector{Bool} | "-" # water body cells (reservoir or lake) + reservoir::R # Reservoir model struct of arrays + lake::L # Lake model struct of arrays + floodplain::F # Floodplain (1D) schematization + allocation::A # Water allocation end function initialize_shallowwater_river( @@ -672,7 +660,7 @@ function initialize_shallowwater_river( width_at_link = fill(Float(0), _ne) length_at_link = fill(Float(0), _ne) mannings_n_sq = fill(Float(0), _ne) - for i = 1:_ne + for i in 1:_ne src_node = nodes_at_link.src[i] dst_node = nodes_at_link.dst[i] zb_max[i] = max(zb[src_node], zb[dst_node]) @@ -689,7 +677,7 @@ function initialize_shallowwater_river( active_index = findall(x -> x == 0, waterbody) do_water_demand = haskey(config.model, "water_demand") - sw_river = ShallowWaterRiver( + sw_river = ShallowWaterRiver(; n = n, ne = _ne, active_n = active_index, @@ -747,8 +735,7 @@ function get_inflow_waterbody(sw::ShallowWaterRiver, src_edge) return q_in end -function shallowwater_river_update(sw::ShallowWaterRiver, network, dt, doy, update_h) - +function shallowwater_river_update!(sw::ShallowWaterRiver, network, dt, doy, update_h) (; nodes_at_link, links_at_node) = network sw.q0 .= sw.q @@ -805,7 +792,7 @@ function shallowwater_river_update(sw::ShallowWaterRiver, network, dt, doy, upda end end - @tturbo for j = 1:n + @tturbo for j in 1:n i = sw.floodplain.hf_index[j] i_src = nodes_at_link.src[i] i_dst = nodes_at_link.dst[i] @@ -891,7 +878,7 @@ function shallowwater_river_update(sw::ShallowWaterRiver, network, dt, doy, upda i = sw.reservoir_index[v] q_in = get_inflow_waterbody(sw, links_at_node.src[i]) - update(sw.reservoir, v, q_in + sw.inflow_wb[i], dt) + update!(sw.reservoir, v, q_in + sw.inflow_wb[i], dt) sw.q[i] = sw.reservoir.outflow[v] sw.q_av[i] += sw.q[i] * dt end @@ -899,13 +886,12 @@ function shallowwater_river_update(sw::ShallowWaterRiver, network, dt, doy, upda i = sw.lake_index[v] q_in = get_inflow_waterbody(sw, links_at_node.src[i]) - update(sw.lake, v, q_in + sw.inflow_wb[i], doy, dt) + update!(sw.lake, v, q_in + sw.inflow_wb[i], doy, dt) sw.q[i] = sw.lake.outflow[v] sw.q_av[i] += sw.q[i] * dt end if update_h @batch per = thread minbatch = 2000 for i in sw.active_n - q_src = sum_at(sw.q, links_at_node.src[i]) q_dst = sum_at(sw.q, links_at_node.dst[i]) sw.volume[i] = @@ -949,9 +935,10 @@ function shallowwater_river_update(sw::ShallowWaterRiver, network, dt, doy, upda sw.h_av[i] += sw.h[i] * dt end end + return nothing end -function update(sw::ShallowWaterRiver{T}, network, doy; update_h = true) where {T} +function update!(sw::ShallowWaterRiver{T}, network, doy; update_h = true) where {T} if !isnothing(sw.reservoir) sw.reservoir.inflow .= 0.0 sw.reservoir.totaloutflow .= 0.0 @@ -975,7 +962,7 @@ function update(sw::ShallowWaterRiver{T}, network, doy; update_h = true) where { if t + dt > sw.dt dt = sw.dt - t end - shallowwater_river_update(sw, network, dt, doy, update_h) + shallowwater_river_update!(sw, network, dt, doy, update_h) t = t + dt end sw.q_av ./= sw.dt @@ -1004,33 +991,33 @@ end # neigbors. const dirs = (:yd, :xd, :xu, :yu) -@get_units @exchange @grid_type @grid_location @with_kw struct ShallowWaterLand{T} - n::Int | "-" | 0 | "none" | "none" # number of cells - xl::Vector{T} | "m" # cell length x direction - yl::Vector{T} | "m" # cell length y direction - xwidth::Vector{T} | "m" | _ | "edge" # effective flow width x direction (floodplain) - ywidth::Vector{T} | "m" | _ | "edge" # effective flow width y direction (floodplain) - g::T | "m2 s-1" | 0 | "scalar" # acceleration due to gravity - theta::T | "-" | 0 | "scalar" # weighting factor (de Almeida et al., 2012) - alpha::T | "-" | 0 | "scalar" # stability coefficient (de Almeida et al., 2012) - h_thresh::T | "m" | 0 | "scalar" # depth threshold for calculating flow - dt::T | "s" | 0 | "none" | "none" # model time step [s] - qy0::Vector{T} | "m3 s-1" | _ | "edge" # flow in y direction at previous time step - qx0::Vector{T} | "m3 s-1" | _ | "edge" # flow in x direction at previous time step - qx::Vector{T} | "m3 s-1" | _ | "edge" # flow in x direction - qy::Vector{T} | "m3 s-1" | _ | "edge" # flow in y direction - zx_max::Vector{T} | "m" | _ | "edge" # maximum cell elevation (x direction) - zy_max::Vector{T} | "m" | _ | "edge" # maximum cell elevation (y direction) - mannings_n_sq::Vector{T} | "(s m-1/3)2" | _ | "edge" # Manning's roughness squared - volume::Vector{T} | "m3" # total volume of cell (including river volume for river cells) - error::Vector{T} | "m3" # error volume - runoff::Vector{T} | "m3 s-1" # runoff from hydrological model - inflow_wb::Vector{T} | "m3 s-1" # inflow to water body from hydrological model - h::Vector{T} | "m" # water depth of cell (for river cells the reference is the river bed elevation `zb`) - z::Vector{T} | "m" # elevation of cell - froude_limit::Bool | "-" | 0 | "none" | "none" # if true a check is performed if froude number > 1.0 (algorithm is modified) - rivercells::Vector{Bool} | "-" # river cells - h_av::Vector{T} | "m" # average water depth (for river cells the reference is the river bed elevation `zb`) +@get_units @grid_loc @with_kw struct ShallowWaterLand{T} + n::Int # number of cells [-] + xl::Vector{T} | "m" # cell length x direction [m] + yl::Vector{T} | "m" # cell length y direction [m] + xwidth::Vector{T} | "m" | "edge" # effective flow width x direction (floodplain) [m] + ywidth::Vector{T} | "m" | "edge" # effective flow width y direction (floodplain) [m] + g::T # acceleration due to gravity [m s⁻²] + theta::T # weighting factor (de Almeida et al., 2012) [-] + alpha::T # stability coefficient (de Almeida et al., 2012) [-] + h_thresh::T # depth threshold for calculating flow [m] + dt::T # model time step [s] + qy0::Vector{T} | "m3 s-1" | "edge" # flow in y direction at previous time step + qx0::Vector{T} | "m3 s-1" | "edge" # flow in x direction at previous time step + qx::Vector{T} | "m3 s-1" | "edge" # flow in x direction + qy::Vector{T} | "m3 s-1" | "edge" # flow in y direction + zx_max::Vector{T} | "m" | "edge" # maximum cell elevation (x direction) + zy_max::Vector{T} | "m" | "edge" # maximum cell elevation (y direction) + mannings_n_sq::Vector{T} | "(s m-1/3)2" | "edge" # Manning's roughness squared + volume::Vector{T} | "m3" # total volume of cell (including river volume for river cells) + error::Vector{T} | "m3" # error volume + runoff::Vector{T} | "m3 s-1" # runoff from hydrological model + inflow_wb::Vector{T} | "m3 s-1" # inflow to water body from hydrological model + h::Vector{T} | "m" # water depth of cell (for river cells the reference is the river bed elevation `zb`) + z::Vector{T} | "m" # elevation of cell + froude_limit::Bool # if true a check is performed if froude number > 1.0 (algorithm is modified) [-] + rivercells::Vector{Bool} | "-" # river cells + h_av::Vector{T} | "m" # average water depth (for river cells the reference is the river bed elevation `zb`) end function initialize_shallowwater_land( @@ -1070,7 +1057,7 @@ function initialize_shallowwater_land( n = length(inds) # initialize links between cells in x and y direction. - indices = Indices(xu = zeros(n), xd = zeros(n), yu = zeros(n), yd = zeros(n)) + indices = Indices(; xu = zeros(n), xd = zeros(n), yu = zeros(n), yd = zeros(n)) # links without neigbors are handled by an extra index (at n + 1, with n links), which # is set to a value of 0.0 m³ s⁻¹ for qx and qy fields at initialization. @@ -1094,7 +1081,7 @@ function initialize_shallowwater_land( # determine z at links in x and y direction zx_max = fill(Float(0), n) zy_max = fill(Float(0), n) - for i = 1:n + for i in 1:n xu = indices.xu[i] if xu <= n zx_max[i] = max(elevation[i], elevation[xu]) @@ -1121,7 +1108,7 @@ function initialize_shallowwater_land( indices_reverse[inds_riv], ) - sw_land = ShallowWaterLand{Float}( + sw_land = ShallowWaterLand{Float}(; n = n, xl = xlength, yl = ylength, @@ -1163,7 +1150,7 @@ dt = alpha * (Δx / sqrt(g max(h)) """ function stable_timestep(sw::ShallowWaterRiver{T})::T where {T} dt_min = T(Inf) - @batch per = thread reduction = ((min, dt_min),) for i = 1:sw.n + @batch per = thread reduction = ((min, dt_min),) for i in 1:(sw.n) @fastmath @inbounds dt = sw.alpha * sw.dl[i] / sqrt(sw.g * sw.h[i]) dt_min = min(dt, dt_min) end @@ -1173,23 +1160,27 @@ end function stable_timestep(sw::ShallowWaterLand{T})::T where {T} dt_min = T(Inf) - @batch per = thread reduction = ((min, dt_min),) for i = 1:sw.n - @fastmath @inbounds dt = - sw.rivercells[i] == 0 ? - sw.alpha * min(sw.xl[i], sw.yl[i]) / sqrt(sw.g * sw.h[i]) : T(Inf) + @batch per = thread reduction = ((min, dt_min),) for i in 1:(sw.n) + @fastmath @inbounds dt = if sw.rivercells[i] == 0 + sw.alpha * min(sw.xl[i], sw.yl[i]) / sqrt(sw.g * sw.h[i]) + else + T(Inf) + end dt_min = min(dt, dt_min) end dt_min = isinf(dt_min) ? T(10.0) : dt_min return dt_min end -function update( +function update!( sw::ShallowWaterLand{T}, swr::ShallowWaterRiver{T}, network, doy; update_h = false, ) where {T} + (; nodes_at_link, links_at_node) = network.river + if !isnothing(swr.reservoir) swr.reservoir.inflow .= 0.0 swr.reservoir.totaloutflow .= 0.0 @@ -1212,22 +1203,23 @@ function update( if t + dt > swr.dt dt = swr.dt - t end - shallowwater_river_update(swr, network.river, dt, doy, update_h) - shallowwater_update(sw, swr, network, dt) + shallowwater_river_update!(swr, network.river, dt, doy, update_h) + shallowwater_update!(sw, swr, network, dt) t = t + dt end swr.q_av ./= swr.dt swr.h_av ./= swr.dt sw.h_av ./= sw.dt + + return nothing end -function shallowwater_update( +function shallowwater_update!( sw::ShallowWaterLand{T}, swr::ShallowWaterRiver{T}, network, dt, ) where {T} - indices = network.land.staggered_indices inds_riv = network.land.index_river @@ -1237,7 +1229,7 @@ function shallowwater_update( sw.qy0 .= sw.qy # update qx - @batch per = thread minbatch = 6000 for i = 1:sw.n + @batch per = thread minbatch = 6000 for i in 1:(sw.n) yu = indices.yu[i] yd = indices.yd[i] xu = indices.xu[i] @@ -1246,7 +1238,6 @@ function shallowwater_update( # the effective flow width is zero when the river width exceeds the cell width (dy # for flow in x dir) and floodplain flow is not calculated. if xu <= sw.n && sw.ywidth[i] != T(0.0) - zs_x = sw.z[i] + sw.h[i] zs_xu = sw.z[xu] + sw.h[xu] zs_max = max(zs_x, zs_xu) @@ -1286,7 +1277,6 @@ function shallowwater_update( # the effective flow width is zero when the river width exceeds the cell width (dx # for flow in y dir) and floodplain flow is not calculated. if yu <= sw.n && sw.xwidth[i] != T(0.0) - zs_y = sw.z[i] + sw.h[i] zs_yu = sw.z[yu] + sw.h[yu] zs_max = max(zs_y, zs_yu) @@ -1323,7 +1313,7 @@ function shallowwater_update( end # change in volume and water levels based on horizontal fluxes for river and land cells - @batch per = thread minbatch = 6000 for i = 1:sw.n + @batch per = thread minbatch = 6000 for i in 1:(sw.n) yd = indices.yd[i] xd = indices.xd[i] @@ -1375,6 +1365,7 @@ function shallowwater_update( end sw.h_av[i] += sw.h[i] * dt end + return nothing end """ @@ -1384,35 +1375,35 @@ Floodplain `volume` is a function of `depth` (flood depth intervals). Based on t cumulative floodplain `volume` a floodplain profile as a function of `flood_depth` is derived with floodplain area `a` (cumulative) and wetted perimeter radius `p` (cumulative). """ -@get_units @exchange @grid_type @grid_location @with_kw struct FloodPlainProfile{T,N} - depth::Vector{T} | "m" | 0 # Flood depth - volume::Array{T,2} | "m3" # Flood volume (cumulative) - width::Array{T,2} | "m" # Flood width - a::Array{T,2} | "m2" # Flow area (cumulative) - p::Array{T,2} | "m" # Wetted perimeter (cumulative) +@get_units @grid_loc @with_kw struct FloodPlainProfile{T, N} + depth::Vector{T} | "m" # Flood depth + volume::Array{T, 2} | "m3" # Flood volume (cumulative) + width::Array{T, 2} | "m" # Flood width + a::Array{T, 2} | "m2" # Flow area (cumulative) + p::Array{T, 2} | "m" # Wetted perimeter (cumulative) end -@get_units @exchange @grid_type @grid_location @with_kw struct FloodPlain{T,P} - profile::P | "-" | 0 # floodplain profile - mannings_n::Vector{T} | "s m-1/3" # manning's roughness - mannings_n_sq::Vector{T} | "(s m-1/3)2" | _ | "edge" # manning's roughness squared - volume::Vector{T} | "m3" # volume - h::Vector{T} | "m" # water depth - h_av::Vector{T} | "m" # average water depth - error::Vector{T} | "m3" # error volume - a::Vector{T} | "m2" | _ | "edge" # flow area - r::Vector{T} | "m" | _ | "edge" # hydraulic radius - hf::Vector{T} | "m" | _ | "edge" # water depth at edge/link - zb_max::Vector{T} | "m" | _ | "edge" # maximum bankfull elevation (edge/link) - q0::Vector{T} | "m3 s-1" | _ | "edge" # discharge at previous time step - q::Vector{T} | "m3 s-1" | _ | "edge" # discharge - q_av::Vector{T} | "m" | _ | "edge" # average river discharge - hf_index::Vector{Int} | "-" | _ | "edge" # index with `hf` above depth threshold +@get_units @grid_loc @with_kw struct FloodPlain{T, P} + profile::P # floodplain profile + mannings_n::Vector{T} | "s m-1/3" # manning's roughness + mannings_n_sq::Vector{T} | "(s m-1/3)2" | "edge" # manning's roughness squared + volume::Vector{T} | "m3" # volume + h::Vector{T} | "m" # water depth + h_av::Vector{T} | "m" # average water depth + error::Vector{T} | "m3" # error volume + a::Vector{T} | "m2" | "edge" # flow area + r::Vector{T} | "m" | "edge" # hydraulic radius + hf::Vector{T} | "m" | "edge" # water depth at edge/link + zb_max::Vector{T} | "m" | "edge" # maximum bankfull elevation (edge/link) + q0::Vector{T} | "m3 s-1" | "edge" # discharge at previous time step + q::Vector{T} | "m3 s-1" | "edge" # discharge + q_av::Vector{T} | "m" | "edge" # average river discharge + hf_index::Vector{Int} | "-" | "edge" # index with `hf` above depth threshold end "Determine the initial floodplain volume" function initialize_volume!(river, nriv::Int) - for i = 1:nriv + for i in 1:nriv i1, i2 = interpolation_indices(river.floodplain.h[i], river.floodplain.profile.depth) a = flow_area( @@ -1488,7 +1479,6 @@ function initialize_floodplain_1d( n_edges, nodes_at_link, ) - n_floodplain = ncread( nc, config, @@ -1526,31 +1516,32 @@ function initialize_floodplain_1d( incorrect_vol = 0 riv_cells = 0 error_vol = 0 - for i = 1:n + for i in 1:n riv_cell = 0 diff_volume = diff(volume[:, i]) - for j = 1:(n_depths-1) + for j in 1:(n_depths - 1) # assume rectangular shape of flood depth segment - width[j+1, i] = diff_volume[j] / (h[j] * riverlength[i]) + width[j + 1, i] = diff_volume[j] / (h[j] * riverlength[i]) # check provided flood volume (floodplain width should be constant or increasing # as a function of flood depth) - if width[j+1, i] < width[j, i] + if width[j + 1, i] < width[j, i] # raise warning only if difference is larger than rounding error of 0.01 m³ - if ((width[j, i] - width[j+1, i]) * h[j] * riverlength[i]) > 0.01 + if ((width[j, i] - width[j + 1, i]) * h[j] * riverlength[i]) > 0.01 incorrect_vol += 1 riv_cell = 1 error_vol = - error_vol + ((width[j, i] - width[j+1, i]) * h[j] * riverlength[i]) + error_vol + + ((width[j, i] - width[j + 1, i]) * h[j] * riverlength[i]) end - width[j+1, i] = width[j, i] + width[j + 1, i] = width[j, i] end - a[j+1, i] = width[j+1, i] * h[j] - p[j+1, i] = (width[j+1, i] - width[j, i]) + 2.0 * h[j] - segment_volume[j+1, i] = a[j+1, i] * riverlength[i] + a[j + 1, i] = width[j + 1, i] * h[j] + p[j + 1, i] = (width[j + 1, i] - width[j, i]) + 2.0 * h[j] + segment_volume[j + 1, i] = a[j + 1, i] * riverlength[i] if j == 1 # for interpolation wetted perimeter at flood depth 0.0 is required - p[j, i] = p[j+1, i] - 2.0 * h[j] + p[j, i] = p[j + 1, i] - 2.0 * h[j] end end @@ -1562,8 +1553,8 @@ function initialize_floodplain_1d( end if incorrect_vol > 0 - perc_riv_cells = round(100.0 * (riv_cells / n), digits = 2) - perc_error_vol = round(100.0 * (error_vol / sum(start_volume[end, :])), digits = 2) + perc_riv_cells = round(100.0 * (riv_cells / n); digits = 2) + perc_error_vol = round(100.0 * (error_vol / sum(start_volume[end, :])); digits = 2) @warn string( "The provided volume of $incorrect_vol rectangular floodplain schematization", " segments for $riv_cells river cells ($perc_riv_cells % of total river cells)", @@ -1578,7 +1569,7 @@ function initialize_floodplain_1d( p = hcat(p, p[:, index_pit]) # initialize floodplain profile parameters - profile = FloodPlainProfile{Float,n_depths}( + profile = FloodPlainProfile{Float, n_depths}(; volume = volume, width = width, depth = flood_depths, @@ -1590,7 +1581,7 @@ function initialize_floodplain_1d( append!(n_floodplain, n_floodplain[index_pit]) # copy to ghost nodes mannings_n_sq = fill(Float(0), n_edges) zb_max = fill(Float(0), n_edges) - for i = 1:n_edges + for i in 1:n_edges src_node = nodes_at_link.src[i] dst_node = nodes_at_link.dst[i] mannings_n = @@ -1602,7 +1593,7 @@ function initialize_floodplain_1d( zb_max[i] = max(zb[src_node], zb[dst_node]) end - floodplain = FloodPlain( + floodplain = FloodPlain(; profile = profile, mannings_n = n_floodplain, mannings_n_sq = mannings_n_sq, @@ -1623,16 +1614,14 @@ function initialize_floodplain_1d( end """ - set_river_inwater(model::Model{N,L,V,R,W,T}, ssf_toriver) where {N,L,V,R,W,T<:Union{SbmModel,SbmGwfModel}} + set_river_inwater!(model::Model, ssf_toriver) -Set `inwater` of the lateral river component for a `Model` of type `SbmModel` or `SbmGwfModel`. -`ssf_toriver` is the subsurface flow to the river. +Set `inwater` of the lateral river component for a `Model`. `ssf_toriver` is the subsurface +flow to the river. """ -function set_river_inwater( - model::Model{N,L,V,R,W,T}, - ssf_toriver, -) where {N,L,V,R,W,T<:Union{SbmModel,SbmGwfModel}} +function set_river_inwater!(model::Model, ssf_toriver) (; lateral, vertical, network, config) = model + (; net_runoff_river) = vertical.runoff.variables inds = network.index_river do_water_demand = haskey(config.model, "water_demand") if do_water_demand @@ -1640,86 +1629,73 @@ function set_river_inwater( ssf_toriver[inds] + lateral.land.to_river[inds] + # net_runoff_river - (vertical.net_runoff_river[inds] * network.land.area[inds] * 0.001) / - vertical.dt + - (lateral.river.allocation.nonirri_returnflow * 0.001 * network.river.area) / - vertical.dt + (net_runoff_river[inds] * network.land.area[inds] * 0.001) / vertical.dt + + ( + lateral.river.allocation.variables.nonirri_returnflow * + 0.001 * + network.river.area + ) / vertical.dt ) else @. lateral.river.inwater = ( ssf_toriver[inds] + lateral.land.to_river[inds] + # net_runoff_river - (vertical.net_runoff_river[inds] * network.land.area[inds] * 0.001) / - vertical.dt + (net_runoff_river[inds] * network.land.area[inds] * 0.001) / vertical.dt ) end + return nothing end """ - set_river_inwater(model, ssf_toriver) - -Set `inwater` of the lateral river component (based on overland flow). -""" -function set_river_inwater(model, ssf_toriver) - (; lateral, network) = model - inds = network.index_river - lateral.river.inwater .= lateral.land.to_river[inds] -end - -""" - set_land_inwater(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmGwfModel} + set_land_inwater!(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmGwfModel} Set `inwater` of the lateral land component for the `SbmGwfModel` type. """ -function set_land_inwater(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmGwfModel} +function set_land_inwater!( + model::Model{N, L, V, R, W, T}, +) where {N, L, V, R, W, T <: SbmGwfModel} (; lateral, vertical, network, config) = model - do_water_demand = haskey(config.model, "water_demand") + (; net_runoff) = vertical.soil.variables do_drains = get(config.model, "drains", false)::Bool - drainflux = zeros(vertical.n) + drainflux = zeros(length(net_runoff)) + do_water_demand = haskey(config.model, "water_demand") if do_drains drainflux[lateral.subsurface.drain.index] = -lateral.subsurface.drain.flux ./ tosecond(basetimestep) end if do_water_demand @. lateral.land.inwater = - (vertical.net_runoff + vertical.allocation.nonirri_returnflow) * + (net_runoff + vertical.allocation.variables.nonirri_returnflow) * network.land.area * 0.001 / lateral.land.dt + drainflux else @. lateral.land.inwater = - (vertical.net_runoff * network.land.area * 0.001) / lateral.land.dt + drainflux + (net_runoff * network.land.area * 0.001) / lateral.land.dt + drainflux end + return nothing end """ - set_land_inwater(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmModel} + set_land_inwater!(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmModel} Set `inwater` of the lateral land component for the `SbmModel` type. """ -function set_land_inwater(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmModel} +function set_land_inwater!( + model::Model{N, L, V, R, W, T}, +) where {N, L, V, R, W, T <: SbmModel} (; lateral, vertical, network, config) = model + (; net_runoff) = vertical.soil.variables do_water_demand = haskey(config.model, "water_demand") if do_water_demand @. lateral.land.inwater = - (vertical.net_runoff + vertical.allocation.nonirri_returnflow) * + (net_runoff + vertical.allocation.variables.nonirri_returnflow) * network.land.area * 0.001 / lateral.land.dt else - @. lateral.land.inwater = - (vertical.net_runoff * network.land.area * 0.001) / lateral.land.dt + @. lateral.land.inwater = (net_runoff * network.land.area * 0.001) / lateral.land.dt end -end - -""" - set_land_inwater(model) - -Set `inwater` of the lateral land component, based on `runoff` of the `vertical` concept. -""" -function set_land_inwater(model) - (; lateral, vertical, network) = model - @. lateral.land.inwater = - (vertical.runoff * network.land.area * 0.001) / lateral.land.dt + return nothing end # Computation of inflow from the lateral components `land` and `subsurface` to water bodies @@ -1731,16 +1707,16 @@ end # (Darcian flow in 4 directions), the lateral subsurface flow is excluded (for now) and # inflow consists of overland flow. """ - set_inflow_waterbody( + set_inflow_waterbody!( model::Model{N,L,V,R,W,T}, ) where {N,L<:NamedTuple{<:Any,<:Tuple{Any,SurfaceFlow,SurfaceFlow}},V,R,W,T} Set inflow from the subsurface and land components to a water body (reservoir or lake) `inflow_wb` from a model type that contains the lateral components `SurfaceFlow`. """ -function set_inflow_waterbody( - model::Model{N,L,V,R,W,T}, -) where {N,L<:NamedTuple{<:Any,<:Tuple{Any,SurfaceFlow,SurfaceFlow}},V,R,W,T} +function set_inflow_waterbody!( + model::Model{N, L, V, R, W, T}, +) where {N, L <: NamedTuple{<:Any, <:Tuple{Any, SurfaceFlow, SurfaceFlow}}, V, R, W, T} (; lateral, network) = model (; subsurface, land, river) = lateral inds = network.index_river @@ -1753,10 +1729,11 @@ function set_inflow_waterbody( river.inflow_wb .= land.q_av[inds] end end + return nothing end """ - set_inflow_waterbody( + set_inflow_waterbody!( model::Model{N,L,V,R,W,T}, ) where {N,L<:NamedTuple{<:Any,<:Tuple{Any,SurfaceFlow,ShallowWaterRiver}},V,R,W,T} @@ -1764,9 +1741,16 @@ Set inflow from the subsurface and land components to a water body (reservoir or `inflow_wb` from a model type that contains the lateral components `SurfaceFlow` and `ShallowWaterRiver`. """ -function set_inflow_waterbody( - model::Model{N,L,V,R,W,T}, -) where {N,L<:NamedTuple{<:Any,<:Tuple{Any,SurfaceFlow,ShallowWaterRiver}},V,R,W,T} +function set_inflow_waterbody!( + model::Model{N, L, V, R, W, T}, +) where { + N, + L <: NamedTuple{<:Any, <:Tuple{Any, SurfaceFlow, ShallowWaterRiver}}, + V, + R, + W, + T, +} (; lateral, network) = model (; subsurface, land, river) = lateral inds = network.index_river @@ -1782,10 +1766,11 @@ function set_inflow_waterbody( @. river.inflow_wb = lateral.land.q_av[inds] + lateral.land.to_river[inds] end end + return nothing end """ - set_inflow_waterbody( + set_inflow_waterbody!( model::Model{N,L,V,R,W,T}, ) where {N,L<:NamedTuple{<:Any,<:Tuple{Any,ShallowWaterLand,ShallowWaterRiver}},V,R,W,T} @@ -1793,11 +1778,18 @@ Set inflow from the subsurface and land components to a water body (reservoir or `inflow_wb` from a model type that contains the lateral components `ShallowWaterLand` and `ShallowWaterRiver`. """ -function set_inflow_waterbody( - model::Model{N,L,V,R,W,T}, -) where {N,L<:NamedTuple{<:Any,<:Tuple{Any,ShallowWaterLand,ShallowWaterRiver}},V,R,W,T} +function set_inflow_waterbody!( + model::Model{N, L, V, R, W, T}, +) where { + N, + L <: NamedTuple{<:Any, <:Tuple{Any, ShallowWaterLand, ShallowWaterRiver}}, + V, + R, + W, + T, +} (; lateral, network) = model - (; subsurface, land) = lateral + (; subsurface, land, river) = lateral inds = network.index_river if !isnothing(lateral.river.reservoir) || !isnothing(lateral.river.lake) @@ -1806,25 +1798,27 @@ function set_inflow_waterbody( (subsurface.ssf[inds] + subsurface.to_river[inds]) / tosecond(basetimestep) end end + return nothing end """ - surface_routing(model; ssf_toriver = 0.0) + surface_routing!(model; ssf_toriver = 0.0) Run surface routing (land and river). Kinematic wave for overland flow and kinematic wave or local inertial model for river flow. """ -function surface_routing(model; ssf_toriver = 0.0) +function surface_routing!(model; ssf_toriver = 0.0) (; lateral, network, clock) = model # run kinematic wave for overland flow - set_land_inwater(model) - update(lateral.land, network.land, network.frac_toriver) + set_land_inwater!(model) + update!(lateral.land, network.land, network.frac_toriver) # run river flow - set_river_inwater(model, ssf_toriver) - set_inflow_waterbody(model) - update(lateral.river, network.river, julian_day(clock.time - clock.dt)) + set_river_inwater!(model, ssf_toriver) + set_inflow_waterbody!(model) + update!(lateral.river, network.river, julian_day(clock.time - clock.dt)) + return nothing end """ @@ -1836,19 +1830,28 @@ end Run surface routing (land and river) for a model type that contains the lateral components `ShallowWaterLand` and `ShallowWaterRiver`. """ -function surface_routing( - model::Model{N,L,V,R,W,T}; +function surface_routing!( + model::Model{N, L, V, R, W, T}; ssf_toriver = 0.0, -) where {N,L<:NamedTuple{<:Any,<:Tuple{Any,ShallowWaterLand,ShallowWaterRiver}},V,R,W,T} - +) where { + N, + L <: NamedTuple{<:Any, <:Tuple{Any, ShallowWaterLand, ShallowWaterRiver}}, + V, + R, + W, + T, +} (; lateral, vertical, network, clock) = model + (; net_runoff) = vertical.soil.variables + (; net_runoff_river) = vertical.runoff.variables @. lateral.land.runoff = ( - (vertical.net_runoff / 1000.0) * network.land.area / vertical.dt + + (net_runoff / 1000.0) * (network.land.area) / vertical.dt + ssf_toriver + # net_runoff_river - ((vertical.net_runoff_river * network.land.area * 0.001) / vertical.dt) + ((net_runoff_river * network.land.area * 0.001) / vertical.dt) ) - set_inflow_waterbody(model) - update(lateral.land, lateral.river, network, julian_day(clock.time - clock.dt)) + set_inflow_waterbody!(model) + update!(lateral.land, lateral.river, network, julian_day(clock.time - clock.dt)) + return nothing end diff --git a/src/forcing.jl b/src/forcing.jl new file mode 100644 index 000000000..294aa2908 --- /dev/null +++ b/src/forcing.jl @@ -0,0 +1,23 @@ +"Struct to store atmospheric forcing variables" +@get_units @with_kw struct AtmosphericForcing{T} + # Precipitation [mm Δt⁻¹] + precipitation::Vector{T} + # Potential reference evapotranspiration [mm Δt⁻¹] + potential_evaporation::Vector{T} + # Temperature [ᵒC] + temperature::Vector{T} | "°C" +end + +"Initialize atmospheric forcing" +function AtmosphericForcing( + n; + precipitation::Vector{T} = fill(mv, n), + potential_evaporation::Vector{T} = fill(mv, n), + temperature::Vector{T} = fill(mv, n), +) where {T} + return AtmosphericForcing{T}(; + precipitation, + potential_evaporation, + temperature, + ) +end \ No newline at end of file diff --git a/src/glacier/glacier.jl b/src/glacier/glacier.jl new file mode 100644 index 000000000..8f5be9553 --- /dev/null +++ b/src/glacier/glacier.jl @@ -0,0 +1,150 @@ +abstract type AbstractGlacierModel{T} end + +"Struct for storing glacier model variables" +@get_units @grid_loc @with_kw struct GlacierVariables{T} + # Water within the glacier [mm] + glacier_store::Vector{T} | "mm" + # Glacier melt [mm Δt⁻¹] + glacier_melt::Vector{T} +end + +"Initialize glacier model variables" +function GlacierVariables(nc, config, inds) + glacier_store = ncread( + nc, + config, + "vertical.glacier.variables.glacier_store"; + sel = inds, + defaults = 5500.0, + type = Float, + fill = 0.0, + ) + n = length(glacier_store) + vars = GlacierVariables(; glacier_store = glacier_store, glacier_melt = fill(mv, n)) + return vars +end + +"Struct for storing boundary condition (snow storage from a snow model) of a glacier model" +@get_units @grid_loc @with_kw struct SnowStateBC{T} + # Snow storage [mm] + snow_storage::Vector{T} | "mm" +end + +"Struct for storing glacier HBV model parameters" +@get_units @grid_loc @with_kw struct GlacierHbvParameters{T} + # Threshold temperature for snowfall above glacier [ᵒC] + g_tt::Vector{T} | "ᵒC" + # Degree-day factor [mm ᵒC⁻¹ Δt⁻¹] for glacier + g_cfmax::Vector{T} | "mm ᵒC-1 dt-1" + # Fraction of the snowpack on top of the glacier converted into ice [Δt⁻¹] + g_sifrac::Vector{T} | "dt-1" + # Fraction covered by a glacier [-] + glacier_frac::Vector{T} | "-" + # Maximum snow to glacier conversion rate [mm Δt⁻¹] + max_snow_to_glacier::T +end + +"Glacier HBV model" +@with_kw struct GlacierHbvModel{T} <: AbstractGlacierModel{T} + boundary_conditions::SnowStateBC{T} + parameters::GlacierHbvParameters{T} + variables::GlacierVariables{T} +end + +struct NoGlacierModel{T} <: AbstractGlacierModel{T} end + +"Initialize glacier HBV model parameters" +function GlacierHbvParameters(nc, config, inds, dt) + g_tt = ncread( + nc, + config, + "vertical.glacier.parameters.g_tt"; + sel = inds, + defaults = 0.0, + type = Float, + fill = 0.0, + ) + g_cfmax = + ncread( + nc, + config, + "vertical.glacier.parameters.g_cfmax"; + sel = inds, + defaults = 3.0, + type = Float, + fill = 0.0, + ) .* (dt / basetimestep) + g_sifrac = + ncread( + nc, + config, + "vertical.glacier.parameters.g_sifrac"; + sel = inds, + defaults = 0.001, + type = Float, + fill = 0.0, + ) .* (dt / basetimestep) + glacier_frac = ncread( + nc, + config, + "vertical.glacier.parameters.glacier_frac"; + sel = inds, + defaults = 0.0, + type = Float, + fill = 0.0, + ) + max_snow_to_glacier = 8.0 * (dt / basetimestep) + glacier_hbv_params = GlacierHbvParameters(; + g_tt = g_tt, + g_cfmax = g_cfmax, + g_sifrac = g_sifrac, + glacier_frac = glacier_frac, + max_snow_to_glacier = max_snow_to_glacier, + ) + return glacier_hbv_params +end + +"Initialize glacier HBV model" +function GlacierHbvModel(nc, config, inds, dt, bc) + params = GlacierHbvParameters(nc, config, inds, dt) + vars = GlacierVariables(nc, config, inds) + model = + GlacierHbvModel(; boundary_conditions = bc, parameters = params, variables = vars) + return model +end + +"Update glacier HBV model for a single timestep" +function update!(model::GlacierHbvModel, atmospheric_forcing::AtmosphericForcing) + (; temperature) = atmospheric_forcing + (; glacier_store, glacier_melt) = model.variables + (; snow_storage) = model.boundary_conditions + (; g_tt, g_cfmax, g_sifrac, glacier_frac, max_snow_to_glacier) = model.parameters + + n = length(temperature) + + threaded_foreach(1:n; basesize = 1000) do i + snow_storage[i], _, glacier_store[i], glacier_melt[i] = glacier_hbv( + glacier_frac[i], + glacier_store[i], + snow_storage[i], + temperature[i], + g_tt[i], + g_cfmax[i], + g_sifrac[i], + max_snow_to_glacier, + ) + end + return nothing +end + +function update!(model::NoGlacierModel, atmospheric_forcing::AtmosphericForcing) + return nothing +end + +# wrapper methods +get_glacier_melt(model::NoGlacierModel) = 0.0 +get_glacier_melt(model::AbstractGlacierModel) = model.variables.glacier_melt +get_glacier_fraction(model::NoGlacierModel) = 0.0 +get_glacier_fraction(model::AbstractGlacierModel) = model.parameters.glacier_frac +get_glacier_store(model::NoGlacierModel) = 0.0 +get_glacier_store(model::AbstractGlacierModel) = model.variables.glacier_store \ No newline at end of file diff --git a/src/glacier/glacier_process.jl b/src/glacier/glacier_process.jl new file mode 100644 index 000000000..17175b0d4 --- /dev/null +++ b/src/glacier/glacier_process.jl @@ -0,0 +1,56 @@ +""" + glacier_hbv(glacierfrac, glacierstore, snow_storage, temperature, tt, cfmax, g_sifrac, max_snow_to_glacier) + +HBV-light type of glacier modelling. +First, a fraction of the snowpack is converted into ice using the HBV-light +model (fraction between 0.001-0.005 per day). +Glacier melting is modelled using a temperature degree factor and only +occurs if the snow storage < 10 mm. + +# Arguments +- `glacierFrac` fraction covered by glaciers [-] +- `glacierstore` volume of the glacier [mm] w.e. +- `snow_storage` snow storage on top of glacier [mm] +- `temperature` air temperature [°C] +- `tt` temperature threshold for ice melting [°C] +- `cfmax` ice degree-day factor in [mm/(°C/day)] +- `g_sifrac` fraction of the snow turned into ice [-] +- `max_snow_to_glacier` maximum snow to glacier conversion rate + +# Output +- `snow` +- `snow_to_glacier` +- `glacierstore` +- `glaciermelt` + +""" +function glacier_hbv( + glacierfrac, + glacierstore, + snow, + temperature, + tt, + cfmax, + g_sifrac, + max_snow_to_glacier, +) + + # Fraction of the snow transformed into ice (HBV-light model) + snow_to_glacier = g_sifrac * snow + snow_to_glacier = glacierfrac > 0.0 ? snow_to_glacier : 0.0 + + # Restrict snow_to_glacier conversion + snow_to_glacier = min(snow_to_glacier, max_snow_to_glacier) + + snow = snow - (snow_to_glacier * glacierfrac) + glacierstore = glacierstore + snow_to_glacier + + # Potential snow melt, based on temperature + potmelt = temperature > tt ? cfmax * (temperature - tt) : 0.0 + + # actual Glacier melt + glaciermelt = snow < 10.0 ? min(potmelt, glacierstore) : 0.0 + glacierstore = glacierstore - glaciermelt + + return snow, snow_to_glacier, glacierstore, glaciermelt +end diff --git a/src/groundwater/aquifer.jl b/src/groundwater/aquifer.jl index ea03659c7..3a8619bf6 100644 --- a/src/groundwater/aquifer.jl +++ b/src/groundwater/aquifer.jl @@ -64,10 +64,8 @@ instead. """ abstract type Aquifer end - abstract type AquiferBoundaryCondition end - """ ConfinedAquifer{T} <: Aquifer @@ -86,7 +84,7 @@ NOTA BENE: **specific** storage is per m of aquifer (conf. specific weight). **Storativity** or (**storage coefficient**) is for the entire aquifer (conf. transmissivity). """ -@get_units @exchange @grid_type @grid_location struct ConfinedAquifer{T} <: Aquifer +@get_units @grid_loc struct ConfinedAquifer{T} <: Aquifer head::Vector{T} | "m" # hydraulic head [m] k::Vector{T} | "m d-1" # horizontal conductivity [m d⁻¹] top::Vector{T} | "m" # top of groundwater layer [m] @@ -98,7 +96,6 @@ transmissivity). volume::Vector{T} | "m3" # total volume of water that can be released end - """ UnconfinedAquifer{T} <: Aquifer @@ -110,7 +107,7 @@ aquifer will yield when all water drains and the pore volume is filled by air instead. Specific yield will vary roughly between 0.05 (clay) and 0.45 (peat) (Johnson, 1967). """ -@get_units @exchange @grid_type @grid_location struct UnconfinedAquifer{T} <: Aquifer +@get_units @grid_loc struct UnconfinedAquifer{T} <: Aquifer head::Vector{T} | "m" # hydraulic head [m] k::Vector{T} | "m d-1" # reference horizontal conductivity [m d⁻¹] top::Vector{T} | "m" # top of groundwater layer [m] @@ -124,11 +121,9 @@ instead. Specific yield will vary roughly between 0.05 (clay) and 0.45 (peat) # conductivity_profile is set to "exponential") end - storativity(A::UnconfinedAquifer) = A.specific_yield storativity(A::ConfinedAquifer) = A.storativity - """ harmonicmean_conductance(kH1, kH2, l1, l2, width) @@ -152,12 +147,11 @@ function harmonicmean_conductance(kH1, kH2, l1, l2, width) end function saturated_thickness(aquifer::UnconfinedAquifer, index::Int) - min(aquifer.top[index], aquifer.head[index]) - aquifer.bottom[index] + return min(aquifer.top[index], aquifer.head[index]) - aquifer.bottom[index] end - function saturated_thickness(aquifer::ConfinedAquifer, index::Int) - aquifer.top[index] - aquifer.bottom[index] + return aquifer.top[index] - aquifer.bottom[index] end function saturated_thickness(aquifer::UnconfinedAquifer) @@ -182,7 +176,7 @@ function horizontal_conductance( nzi::Int, aquifer::A, connectivity::Connectivity, -) where {A<:Aquifer} +) where {A <: Aquifer} k1 = aquifer.k[i] k2 = aquifer.k[j] H1 = aquifer.top[i] - aquifer.bottom[i] @@ -202,8 +196,11 @@ Conductance for a confined aquifer is constant, and only has to be set once. For an unconfined aquifer, conductance is computed per timestep by multiplying by degree of saturation [0.0 - 1.0]. """ -function initialize_conductance!(aquifer::A, connectivity::Connectivity) where {A<:Aquifer} - for i = 1:connectivity.ncell +function initialize_conductance!( + aquifer::A, + connectivity::Connectivity, +) where {A <: Aquifer} + for i in 1:(connectivity.ncell) # Loop over connections for cell j for nzi in connections(connectivity, i) j = connectivity.rowval[nzi] @@ -213,7 +210,6 @@ function initialize_conductance!(aquifer::A, connectivity::Connectivity) where { end end - function conductance( aquifer::ConfinedAquifer, i, @@ -225,7 +221,6 @@ function conductance( return aquifer.conductance[nzi] end - """ conductance(aquifer::UnconfinedAquifer, connectivity::Connectivity) @@ -260,7 +255,6 @@ function conductance( conductivity_profile::String, connectivity::Connectivity, ) - if conductivity_profile == "exponential" # Extract required variables zi1 = aquifer.top[i] - aquifer.head[i] @@ -302,7 +296,7 @@ function conductance( end function flux!(Q, aquifer, connectivity, conductivity_profile) - for i = 1:connectivity.ncell + for i in 1:(connectivity.ncell) # Loop over connections for cell j for nzi in connections(connectivity, i) # connection from i -> j @@ -315,13 +309,11 @@ function flux!(Q, aquifer, connectivity, conductivity_profile) return Q end - -@get_units @exchange @grid_type @grid_location struct ConstantHead{T} +@get_units @grid_loc struct ConstantHead{T} head::Vector{T} | "m" index::Vector{Int} | "-" end - """ stable_timestep(aquifer) @@ -352,8 +344,7 @@ end minimum_head(aquifer::ConfinedAquifer) = aquifer.head minimum_head(aquifer::UnconfinedAquifer) = max.(aquifer.head, aquifer.bottom) - -function update(gwf, Q, dt, conductivity_profile) +function update!(gwf, Q, dt, conductivity_profile) Q .= 0.0 # TODO: Probably remove this when linking with other components flux!(Q, gwf.aquifer, gwf.connectivity, conductivity_profile) for boundary in gwf.boundaries @@ -366,22 +357,42 @@ function update(gwf, Q, dt, conductivity_profile) gwf.aquifer.head .= minimum_head(gwf.aquifer) gwf.aquifer.volume .= saturated_thickness(gwf.aquifer) .* gwf.aquifer.area .* storativity(gwf.aquifer) - return gwf + return nothing end - -Base.@kwdef struct GroundwaterFlow{A,B} +Base.@kwdef struct GroundwaterFlow{A, C, CH, B} aquifer::A - connectivity::Connectivity - constanthead::ConstantHead - boundaries::Vector{B} - function GroundwaterFlow( - aquifer::A, - connectivity, - constanthead, - boundaries::Vector{B}, - ) where {A<:Aquifer,B<:AquiferBoundaryCondition} - initialize_conductance!(aquifer, connectivity) - new{A,B}(aquifer, connectivity, constanthead, boundaries) - end + connectivity::C + constanthead::CH + boundaries::B end + +function GroundwaterFlow{T}(; + aquifer::Aquifer, + connectivity::Connectivity{T}, + constanthead::ConstantHead{T}, + boundaries::Vector{AquiferBoundaryCondition}, +) where {T} + initialize_conductance!(aquifer, connectivity) + args = (aquifer, connectivity, constanthead, boundaries) + GroundwaterFlow{typeof.(args)...}(args...) +end + +function get_water_depth( + gwf::GroundwaterFlow{A, C, CH, B}, +) where {A <: UnconfinedAquifer, C, CH, B} + gwf.aquifer.head .= min.(gwf.aquifer.head, gwf.aquifer.top) + gwf.aquifer.head[gwf.constanthead.index] .= gwf.constanthead.head + wtd = gwf.aquifer.top .- gwf.aquifer.head + return wtd +end + +function get_exfiltwater( + gwf::GroundwaterFlow{A, C, CH, B}, +) where {A <: UnconfinedAquifer, C, CH, B} + exfiltwater = + (gwf.aquifer.head .- min.(gwf.aquifer.head, gwf.aquifer.top)) .* + storativity(gwf.aquifer) + exfiltwater[gwf.constanthead.index] .= 0 + return exfiltwater +end \ No newline at end of file diff --git a/src/groundwater/boundary_conditions.jl b/src/groundwater/boundary_conditions.jl index 06a04897e..48e883527 100644 --- a/src/groundwater/boundary_conditions.jl +++ b/src/groundwater/boundary_conditions.jl @@ -8,12 +8,10 @@ function check_flux(flux, aquifer::UnconfinedAquifer, index::Int) end end - # Do nothing for a confined aquifer: aquifer can always provide flux check_flux(flux, aquifer::ConfinedAquifer, index::Int) = flux - -@get_units @exchange @grid_type @grid_location struct River{T} <: AquiferBoundaryCondition +@get_units @grid_loc struct River{T} <: AquiferBoundaryCondition stage::Vector{T} | "m" infiltration_conductance::Vector{T} | "m2 d-1" exfiltration_conductance::Vector{T} | "m2 d-1" @@ -22,7 +20,6 @@ check_flux(flux, aquifer::ConfinedAquifer, index::Int) = flux index::Vector{Int} | "-" end - function flux!(Q, river::River, aquifer) for (i, index) in enumerate(river.index) head = aquifer.head[index] @@ -40,16 +37,13 @@ function flux!(Q, river::River, aquifer) return Q end - -@get_units @exchange @grid_type @grid_location struct Drainage{T} <: - AquiferBoundaryCondition +@get_units @grid_loc struct Drainage{T} <: AquiferBoundaryCondition elevation::Vector{T} | "m" conductance::Vector{T} | "m2 d-1" flux::Vector{T} | "m3 d-1" index::Vector{Int} | "-" end - function flux!(Q, drainage::Drainage, aquifer) for (i, index) in enumerate(drainage.index) cond = drainage.conductance[i] @@ -60,15 +54,13 @@ function flux!(Q, drainage::Drainage, aquifer) return Q end - -@get_units struct HeadBoundary{T} <: AquiferBoundaryCondition +@get_units @grid_loc struct HeadBoundary{T} <: AquiferBoundaryCondition head::Vector{T} | "m" conductance::Vector{T} | "m2 d-1" flux::Vector{T} | "m3 d-1" index::Vector{Int} | "-" end - function flux!(Q, headboundary::HeadBoundary, aquifer) for (i, index) in enumerate(headboundary.index) cond = headboundary.conductance[i] @@ -79,15 +71,12 @@ function flux!(Q, headboundary::HeadBoundary, aquifer) return Q end - -@get_units @exchange @grid_type @grid_location struct Recharge{T} <: - AquiferBoundaryCondition +@get_units @grid_loc struct Recharge{T} <: AquiferBoundaryCondition rate::Vector{T} | "m d-1" flux::Vector{T} | "m3 d-1" index::Vector{Int} | "-" end - function flux!(Q, recharge::Recharge, aquifer) for (i, index) in enumerate(recharge.index) recharge.flux[i] = @@ -97,14 +86,12 @@ function flux!(Q, recharge::Recharge, aquifer) return Q end - -@get_units struct Well{T} <: AquiferBoundaryCondition +@get_units @grid_loc struct Well{T} <: AquiferBoundaryCondition volumetric_rate::Vector{T} | "m3 d-1" flux::Vector{T} | "m3 d-1" index::Vector{Int} | "-" end - function flux!(Q, well::Well, aquifer) for (i, index) in enumerate(well.index) well.flux[i] = check_flux(well.volumetric_rate[i], aquifer, index) diff --git a/src/groundwater/connectivity.jl b/src/groundwater/connectivity.jl index 90a9bf6f2..538aac097 100644 --- a/src/groundwater/connectivity.jl +++ b/src/groundwater/connectivity.jl @@ -29,14 +29,12 @@ struct Connectivity{T} rowval::Vector{Int} end - """ connections(C::Connectivity, id::Int) Returns connections for a single cell, identified by ``id``. """ -connections(C::Connectivity, id::Int) = C.colptr[id]:(C.colptr[id+1]-1) - +connections(C::Connectivity, id::Int) = C.colptr[id]:(C.colptr[id + 1] - 1) """ connection_geometry(I, J, dx, dy) @@ -59,7 +57,6 @@ function connection_geometry(I, J, dx, dy) return (length1, length2, width) end - # Define cartesian indices for neighbors const neighbors = ( CartesianIndex(0, -1), diff --git a/src/hbv.jl b/src/hbv.jl deleted file mode 100644 index 038514622..000000000 --- a/src/hbv.jl +++ /dev/null @@ -1,239 +0,0 @@ -@get_units @exchange @grid_type @grid_location @with_kw struct HBV{T} - dt::T | "s" | 0 | "none" | "none" # Model time step [s] - n::Int | "-" | 0 | "none" | "none" # Number of cells - fc::Vector{T} | "mm" # Field capacity [mm] - betaseepage::Vector{T} | "-" # Exponent in soil runoff generation equation [-] - lp::Vector{T} | "-" # Fraction of field capacity below which actual evaporation=potential evaporation [-] - threshold::Vector{T} | "mm" # Threshold soilwater storage above which AE=PE [mm] - k4::Vector{T} | "dt-1" # Recession constant baseflow [Δt⁻¹] - kquickflow::Vector{T} | "dt-1" # Recession constant upper reservoir [Δt⁻¹] - suz::Vector{T} | "mm" # Level over which k0 is used [mm] - k0::Vector{T} | "dt-1" # Recession constant upper reservoir [Δt⁻¹] - khq::Vector{T} | "dt-1" # Recession rate at flow hq [Δt⁻¹] - hq::Vector{T} # High flow rate hq for which recession rate of upper reservoir is known [mm Δt⁻¹] - alphanl::Vector{T} # Measure of non-linearity of upper reservoir - perc::Vector{T} # Percolation from upper to lower zone [mm Δt⁻¹] - cfr::Vector{T} | "-" # Refreezing efficiency constant in refreezing of freewater in snow [-] - pcorr::Vector{T} | "-" # Correction factor for precipitation [-] - rfcf::Vector{T} | "-" # Correction factor for rainfall [-] - sfcf::Vector{T} | "-" # Correction factor for snowfall [-] - cflux::Vector{T} # Maximum capillary rise from runoff response routine to soil moisture routine [mm Δt⁻¹] - icf::Vector{T} | "mm" # Maximum interception storage (in forested and non-forested areas) [mm] - cevpf::Vector{T} | "-" # Correction factor for potential evaporation [-] - epf::Vector{T} | "mm-1" # Exponent of correction factor for evaporation on days with precipitation - ecorr::Vector{T} | "-" # Evap correction [-] - tti::Vector{T} | "ᵒC" # Critical temperature for snowmelt and refreezing [ᵒC] - tt::Vector{T} | "ᵒC" # Defines interval in which precipitation falls as rainfall and snowfall [ᵒC] - ttm::Vector{T} | "ᵒC" # Threshold temperature for snowmelt [ᵒC] - cfmax::Vector{T} | "mm ᵒC-1 dt-1" # Meltconstant in temperature-index [-] - whc::Vector{T} | "-" # Fraction of snow volume that can store water [-] - g_tt::Vector{T} | "ᵒC" # Threshold temperature for snowfall above glacier [ᵒC] - g_cfmax::Vector{T} | "mm ᵒC-1 dt-1" # Degree-day factor [mm ᵒC⁻¹ Δt⁻¹] for glacier - g_sifrac::Vector{T} | "dt-1" # Fraction of the snowpack on top of the glacier converted into ice [Δt⁻¹] - glacierstore::Vector{T} | "mm" # Water within the glacier [mm] - glacierfrac::Vector{T} | "-" # Fraction covered by a glacier [-] - precipitation::Vector{T} # Precipitation [mm Δt⁻¹] - temperature::Vector{T} | "ᵒC" # Temperature [ᵒC] - potential_evaporation::Vector{T} # Potential evapotranspiration [mm Δt⁻¹] - potsoilevap::Vector{T} # Potential soil evaporation [mm Δt⁻¹] - soilevap::Vector{T} # Soil evaporation [mm Δt⁻¹] - intevap::Vector{T} # Evaporation from interception storage [mm Δt⁻¹] - actevap::Vector{T} # Total actual evapotranspiration (intevap + soilevap) [mm Δt⁻¹] - interceptionstorage::Vector{T} | "mm" # Actual interception storage [mm] - snowwater::Vector{T} | "mm" # Available free water in snow [mm] - snow::Vector{T} | "mm" # Snow pack [mm] - rainfallplusmelt::Vector{T} # Snow melt + precipitation as rainfall [mm Δt⁻¹] - soilmoisture::Vector{T} | "mm" # Actual soil moisture [mm] - directrunoff::Vector{T} # Direct runoff to upper zone [mm Δt⁻¹] - hbv_seepage::Vector{T} # Recharge to upper zone [mm Δt⁻¹] - in_upperzone::Vector{T} # Water inflow into upper zone [mm Δt⁻¹] - upperzonestorage::Vector{T} | "mm" # Water content of the upper zone [mm] - quickflow::Vector{T} # Specific runoff (quickflow part) [mm Δt⁻¹] - real_quickflow::Vector{T} # Specific runoff (quickflow), if K upper zone is precalculated [mm Δt⁻¹] - percolation::Vector{T} # Actual percolation to the lower zone [mm Δt⁻¹] - capflux::Vector{T} # Capillary rise [mm Δt⁻¹] - lowerzonestorage::Vector{T} | "mm" # Water content of the lower zone [mm] - baseflow::Vector{T} # Specific runoff (baseflow part) per cell [mm Δt⁻¹] - runoff::Vector{T} # Total specific runoff per cell [mm Δt⁻¹] -end - - -function update_until_snow(hbv::HBV, config) - - for i = 1:hbv.n - precipitation = hbv.precipitation[i] * hbv.pcorr[i] - # fraction of precipitation which falls as rain - rainfrac = if iszero(hbv.tti[i]) - Float64(hbv.temperature[i] > hbv.tt[i]) - else - frac = (hbv.temperature[i] - (hbv.tt[i] - hbv.tti[i] / 2.0)) / hbv.tti[i] - min(frac, 1.0) - end - rainfrac = max(rainfrac, 0.0) - - # fraction of precipitation which falls as snow - snowfrac = 1.0 - rainfrac - # different correction for rainfall and snowfall - precipitation = - hbv.sfcf[i] * snowfrac * precipitation + hbv.rfcf[i] * rainfrac * precipitation - - # interception - interception = min(precipitation, hbv.icf[i] - hbv.interceptionstorage[i]) - # current interception storage - interceptionstorage = hbv.interceptionstorage[i] + interception - precipitation = precipitation - interception - - # correction for potential evaporation on wet days - potevap = - exp(-hbv.epf[i] * precipitation) * hbv.ecorr[i] * hbv.potential_evaporation[i] - # correct per landuse - potevap = hbv.cevpf[i] * potevap - - # evaporation from interception storage - intevap = min(interceptionstorage, potevap) - interceptionstorage = interceptionstorage - intevap - restevap = max(0.0, potevap - intevap) - - snow, snowwater, snowmelt, rainfallplusmelt, snowfall = snowpack_hbv( - hbv.snow[i], - hbv.snowwater[i], - precipitation, - hbv.temperature[i], - hbv.tti[i], - hbv.tt[i], - hbv.ttm[i], - hbv.cfmax[i], - hbv.whc[i], - ) - - # update the outputs and states - hbv.rainfallplusmelt[i] = rainfallplusmelt - hbv.snowwater[i] = snowwater - hbv.snow[i] = snow - hbv.interceptionstorage[i] = interceptionstorage - hbv.potsoilevap[i] = restevap - hbv.intevap[i] = intevap - end -end - -function update_after_snow(hbv::HBV, config) - - modelglacier = get(config.model, "glacier", false)::Bool - set_kquickflow = get(config.model, "set_kquickflow", false)::Bool - external_qbase = get(config.model, "external_qbase", false)::Bool - - for i = 1:hbv.n - if modelglacier - # Run Glacier module and add the snowpack on-top of it. - # Estimate the fraction of snow turned into ice (HBV-light). - # Estimate glacier melt. - - hbv.snow[i], _, hbv.glacierstore[i], glaciermelt = glacier_hbv( - hbv.glacierfrac[i], - hbv.glacierstore[i], - hbv.snow[i], - hbv.temperature[i], - hbv.g_tt[i], - hbv.g_cfmax[i], - hbv.g_sifrac[i], - Second(hbv.dt), - ) - # Convert to mm per grid cell and add to snowmelt - glaciermelt = glaciermelt * hbv.glacierfrac[i] - rainfallplusmelt = hbv.rainfallplusmelt[i] + glaciermelt - else - rainfallplusmelt = hbv.rainfallplusmelt[i] - end - - soilmoisture = hbv.soilmoisture[i] + rainfallplusmelt - # if soil is filled to capacity: abundant water runs of directly - directrunoff = max(soilmoisture - hbv.fc[i], 0.0) - soilmoisture = soilmoisture - directrunoff - # net water which infiltrates into soil - netinsoil = rainfallplusmelt - directrunoff - - # soil evapotranspiration - soilevap = - soilmoisture > hbv.threshold[i] ? min(soilmoisture, hbv.potsoilevap[i]) : - min(hbv.potsoilevap[i] * (soilmoisture / hbv.threshold[i])) - # evaporation from soil moisture storage - soilmoisture = soilmoisture - soilevap - # sum of evaporation components (IntEvap+SoilEvap) - actevap = hbv.intevap[i] + soilevap - # runoff water from soil - hbv_seepage = - pow(min(soilmoisture / hbv.fc[i], 1.0), hbv.betaseepage[i]) * netinsoil - soilmoisture = soilmoisture - hbv_seepage - # correction for extremely wet periods: soil is filled to capacity - back_tosoil = min(hbv.fc[i] - soilmoisture, directrunoff) - directrunoff = directrunoff - back_tosoil - soilmoisture = soilmoisture + back_tosoil - # total water available for runoff - in_upperzone = directrunoff + hbv_seepage - ### calculations for upper zone ### - upperzonestorage = hbv.upperzonestorage[i] + in_upperzone - percolation = min(hbv.perc[i], upperzonestorage - in_upperzone / 2.0) - upperzonestorage = upperzonestorage - percolation - # capillary flux flowing back to soil - capflux = hbv.cflux[i] * ((hbv.fc[i] - soilmoisture) / hbv.fc[i]) - capflux = min(hbv.fc[i] - soilmoisture, capflux) - upperzonestorage = upperzonestorage - capflux - soilmoisture = soilmoisture + capflux - - real_quickflow = 0.0 - if set_kquickflow == false - - if percolation < hbv.perc[i] - quickflow = 0.0 - else - quickflow = min( - hbv.kquickflow[i] * pow( - (upperzonestorage - min(in_upperzone / 2.0, upperzonestorage)), - 1.0 + hbv.alphanl[i], - ), - upperzonestorage, - ) - - upperzonestorage = - percolation < hbv.perc[i] ? upperzonestorage : - max(upperzonestorage - quickflow, 0.0) - end - else - quickflow = hbv.kquickflow[i] * upperzonestorage - real_quickflow = max(0.0, hbv.k0[i] * (upperzonestorage - hbv.suz[i])) - upperzonestorage = upperzonestorage - quickflow - real_quickflow - - end - - ### calculations for lower zone ### - lowerzonestorage = hbv.lowerzonestorage[i] + percolation - # baseflow in mm/timestep - baseflow = min(lowerzonestorage, hbv.k4[i] * lowerzonestorage) - lowerzonestorage = lowerzonestorage - baseflow - - if external_qbase - directrunoffstorage = quickflow + hbv_seepage + real_quickflow - else - directrunoffstorage = quickflow + baseflow + real_quickflow - end - - runoff = max(0.0, directrunoffstorage) - - # update the outputs and states - hbv.runoff[i] = runoff - hbv.rainfallplusmelt[i] = rainfallplusmelt - hbv.soilmoisture[i] = soilmoisture - hbv.soilevap[i] = soilevap - hbv.actevap[i] = actevap - hbv.quickflow[i] = quickflow - hbv.real_quickflow[i] = real_quickflow - hbv.upperzonestorage[i] = upperzonestorage - hbv.lowerzonestorage[i] = lowerzonestorage - hbv.baseflow[i] = baseflow - hbv.hbv_seepage[i] = hbv_seepage - hbv.percolation[i] = percolation - hbv.capflux[i] = capflux - hbv.in_upperzone[i] = in_upperzone - end - -end diff --git a/src/hbv_model.jl b/src/hbv_model.jl deleted file mode 100644 index 7c63bc252..000000000 --- a/src/hbv_model.jl +++ /dev/null @@ -1,446 +0,0 @@ -""" - initialize_hbv_model(config::Config) - -Initial part of the SBM model concept. Reads the input settings and data as defined in the -Config object. Will return a Model that is ready to run. -""" -function initialize_hbv_model(config::Config) - # unpack the paths to the netCDF files - static_path = input_path(config, config.input.path_static) - - reader = prepare_reader(config) - clock = Clock(config, reader) - dt = clock.dt - - do_reservoirs = get(config.model, "reservoirs", false)::Bool - do_lakes = get(config.model, "lakes", false)::Bool - do_pits = get(config.model, "pits", false)::Bool - set_kquickflow = get(config.model, "set_kquickflow", false)::Bool - - kw_river_tstep = get(config.model, "kw_river_tstep", 0) - kw_land_tstep = get(config.model, "kw_land_tstep", 0) - kinwave_it = get(config.model, "kin_wave_iteration", false)::Bool - - nc = NCDataset(static_path) - - subcatch_2d = ncread(nc, config, "subcatchment"; optional = false, allow_missing = true) - # indices based on catchment - inds, rev_inds = active_indices(subcatch_2d, missing) - n = length(inds) - - cfmax = - ncread( - nc, - config, - "vertical.cfmax"; - sel = inds, - defaults = 3.75653, - type = Float, - ) .* (dt / basetimestep) - tt = ncread(nc, config, "vertical.tt"; sel = inds, defaults = -1.41934, type = Float) - tti = ncread(nc, config, "vertical.tti"; sel = inds, defaults = 1.0, type = Float) - ttm = ncread(nc, config, "vertical.ttm"; sel = inds, defaults = -1.41934, type = Float) - whc = ncread(nc, config, "vertical.whc"; sel = inds, defaults = 0.1, type = Float) - # glacier parameters - g_tt = ncread( - nc, - config, - "vertical.g_tt"; - sel = inds, - defaults = 0.0, - type = Float, - fill = 0.0, - ) - g_cfmax = - ncread( - nc, - config, - "vertical.g_cfmax"; - sel = inds, - defaults = 3.0, - type = Float, - fill = 0.0, - ) .* (dt / basetimestep) - g_sifrac = - ncread( - nc, - config, - "vertical.g_sifrac"; - sel = inds, - defaults = 0.001, - type = Float, - fill = 0.0, - ) .* (dt / basetimestep) - glacierfrac = ncread( - nc, - config, - "vertical.glacierfrac"; - sel = inds, - defaults = 0.0, - type = Float, - fill = 0.0, - ) - glacierstore = ncread( - nc, - config, - "vertical.glacierstore"; - sel = inds, - defaults = 5500.0, - type = Float, - fill = 0.0, - ) - fc = ncread(nc, config, "vertical.fc"; sel = inds, defaults = 260.0, type = Float) - betaseepage = - ncread(nc, config, "vertical.betaseepage"; sel = inds, defaults = 1.8, type = Float) - lp = ncread(nc, config, "vertical.lp"; sel = inds, defaults = 0.53, type = Float) - k4 = - ncread(nc, config, "vertical.k4"; sel = inds, defaults = 0.02307, type = Float) .* - (dt / basetimestep) - kquickflow = - ncread( - nc, - config, - "vertical.kquickflow"; - sel = inds, - defaults = 0.09880, - type = Float, - ) .* (dt / basetimestep) - suz = ncread(nc, config, "vertical.suz"; sel = inds, defaults = 100.0, type = Float) - k0 = - ncread(nc, config, "vertical.k0"; sel = inds, defaults = 0.30, type = Float) .* - (dt / basetimestep) - khq = - ncread(nc, config, "vertical.khq"; sel = inds, defaults = 0.09880, type = Float) .* - (dt / basetimestep) - hq = - ncread(nc, config, "vertical.hq"; sel = inds, defaults = 3.27, type = Float) .* - (dt / basetimestep) - alphanl = - ncread(nc, config, "vertical.alphanl"; sel = inds, defaults = 1.1, type = Float) - perc = - ncread(nc, config, "vertical.perc"; sel = inds, defaults = 0.4, type = Float) .* - (dt / basetimestep) - cfr = ncread(nc, config, "vertical.cfr"; sel = inds, defaults = 0.05, type = Float) - pcorr = ncread(nc, config, "vertical.pcorr"; sel = inds, defaults = 1.0, type = Float) - rfcf = ncread(nc, config, "vertical.rfcf"; sel = inds, defaults = 1.0, type = Float) - sfcf = ncread(nc, config, "vertical.sfcf"; sel = inds, defaults = 1.0, type = Float) - cflux = - ncread(nc, config, "vertical.cflux"; sel = inds, defaults = 2.0, type = Float) .* - (dt / basetimestep) - icf = ncread(nc, config, "vertical.icf"; sel = inds, defaults = 2.0, type = Float) - cevpf = ncread(nc, config, "vertical.cevpf"; sel = inds, defaults = 1.0, type = Float) - epf = ncread(nc, config, "vertical.epf"; sel = inds, defaults = 1.0, type = Float) - ecorr = ncread(nc, config, "vertical.ecorr"; sel = inds, defaults = 1.0, type = Float) - - # read x, y coordinates and calculate cell length [m] - y_nc = read_y_axis(nc) - x_nc = read_x_axis(nc) - y = permutedims(repeat(y_nc, outer = (1, length(x_nc))))[inds] - cellength = abs(mean(diff(x_nc))) - - - sizeinmetres = get(config.model, "sizeinmetres", false)::Bool - xl, yl = cell_lengths(y, cellength, sizeinmetres) - - threshold = fc .* lp - - hbv = HBV{Float}( - dt = Float(tosecond(dt)), - n = n, - fc = fc, - betaseepage = betaseepage, - lp = lp, - threshold = threshold, - k4 = k4, - kquickflow = set_kquickflow ? kquickflow : - pow.(khq, 1.0 .+ alphanl) .* pow.(hq, -alphanl), - suz = suz, - k0 = k0, - khq = khq, - hq = hq, - alphanl = alphanl, - perc = perc, - cfr = cfr, - pcorr = pcorr, - rfcf = rfcf, - sfcf = sfcf, - cflux = cflux, - icf = icf, - cevpf = cevpf, - epf = epf, - ecorr = ecorr, - tti = tti, - tt = tt, - ttm = ttm, - cfmax = cfmax, - whc = whc, - # glacier parameters - g_tt = g_tt, - g_sifrac = g_sifrac, - g_cfmax = g_cfmax, - glacierstore = glacierstore, - glacierfrac = glacierfrac, - # default (cold) states: - interceptionstorage = zeros(Float, n), - snow = zeros(Float, n), - snowwater = zeros(Float, n), - soilmoisture = copy(fc), - upperzonestorage = 0.2 .* fc, - lowerzonestorage = 1.0 ./ (3.0 .* k4), - # variables: - precipitation = fill(mv, n), - temperature = fill(mv, n), - potential_evaporation = fill(mv, n), - potsoilevap = fill(mv, n), - soilevap = fill(mv, n), - intevap = fill(mv, n), - actevap = fill(mv, n), - rainfallplusmelt = fill(mv, n), - directrunoff = fill(mv, n), - hbv_seepage = fill(mv, n), - in_upperzone = fill(mv, n), - quickflow = fill(mv, n), - real_quickflow = fill(mv, n), - percolation = fill(mv, n), - capflux = fill(mv, n), - baseflow = fill(mv, n), - runoff = fill(mv, n), - ) - - modelsize_2d = size(subcatch_2d) - river_2d = - ncread(nc, config, "river_location"; optional = false, type = Bool, fill = false) - river = river_2d[inds] - riverwidth_2d = - ncread(nc, config, "lateral.river.width"; optional = false, type = Float, fill = 0) - riverwidth = riverwidth_2d[inds] - riverlength_2d = - ncread(nc, config, "lateral.river.length"; optional = false, type = Float, fill = 0) - riverlength = riverlength_2d[inds] - - inds_riv, rev_inds_riv = active_indices(river_2d, 0) - nriv = length(inds_riv) - - # reservoirs - pits = zeros(Bool, modelsize_2d) - if do_reservoirs - reservoirs, resindex, reservoir, pits = - initialize_simple_reservoir(config, nc, inds_riv, nriv, pits, tosecond(dt)) - else - reservoir = () - reservoirs = nothing - resindex = fill(0, nriv) - end - - # lakes - if do_lakes - lakes, lakeindex, lake, pits = - initialize_lake(config, nc, inds_riv, nriv, pits, tosecond(dt)) - else - lake = () - lakes = nothing - lakeindex = fill(0, nriv) - end - - ldd_2d = ncread(nc, config, "ldd"; optional = false, allow_missing = true) - ldd = ldd_2d[inds] - if do_pits - pits_2d = ncread(nc, config, "pits"; optional = false, type = Bool, fill = false) - ldd = set_pit_ldd(pits_2d, ldd, inds) - end - - landslope = - ncread(nc, config, "lateral.land.slope"; optional = false, sel = inds, type = Float) - clamp!(landslope, 0.00001, Inf) - - dl = map(detdrainlength, ldd, xl, yl) - dw = (xl .* yl) ./ dl - olf = initialize_surfaceflow_land( - nc, - config, - inds; - sl = landslope, - dl = dl, - width = map(det_surfacewidth, dw, riverwidth, river), - iterate = kinwave_it, - tstep = kw_land_tstep, - dt = dt, - ) - - graph = flowgraph(ldd, inds, pcr_dir) - - riverlength = riverlength_2d[inds_riv] - riverwidth = riverwidth_2d[inds_riv] - minimum(riverlength) > 0 || error("river length must be positive on river cells") - minimum(riverwidth) > 0 || error("river width must be positive on river cells") - - ldd_riv = ldd_2d[inds_riv] - if do_pits - ldd_riv = set_pit_ldd(pits_2d, ldd_riv, inds_riv) - end - graph_riv = flowgraph(ldd_riv, inds_riv, pcr_dir) - - # the indices of the river cells in the land(+river) cell vector - index_river = filter(i -> !isequal(river[i], 0), 1:n) - frac_toriver = fraction_runoff_toriver(graph, ldd, index_river, landslope, n) - - rf = initialize_surfaceflow_river( - nc, - config, - inds_riv; - dl = riverlength, - width = riverwidth, - reservoir_index = resindex, - reservoir = reservoirs, - lake_index = lakeindex, - lake = lakes, - iterate = kinwave_it, - tstep = kw_river_tstep, - dt = dt, - ) - - # setup subdomains for the land and river kinematic wave domain, if nthreads = 1 - # subdomain is equal to the complete domain - toposort = topological_sort_by_dfs(graph) - toposort_riv = topological_sort_by_dfs(graph_riv) - index_pit_land = findall(x -> x == 5, ldd) - index_pit_river = findall(x -> x == 5, ldd_riv) - streamorder = stream_order(graph, toposort) - min_streamorder_land = get(config.model, "min_streamorder_land", 5) - subbas_order, indices_subbas, topo_subbas = kinwave_set_subdomains( - graph, - toposort, - index_pit_land, - streamorder, - min_streamorder_land, - ) - min_streamorder_river = get(config.model, "min_streamorder_river", 6) - subriv_order, indices_subriv, topo_subriv = kinwave_set_subdomains( - graph_riv, - toposort_riv, - index_pit_river, - streamorder[index_river], - min_streamorder_river, - ) - - modelmap = (vertical = hbv, lateral = (land = olf, river = rf)) - indices_reverse = ( - land = rev_inds, - river = rev_inds_riv, - reservoir = isempty(reservoir) ? nothing : reservoir.reverse_indices, - lake = isempty(lake) ? nothing : lake.reverse_indices, - ) - writer = prepare_writer(config, modelmap, indices_reverse, x_nc, y_nc, nc) - close(nc) - - # for each domain save: - # - the directed acyclic graph (graph), - # - the traversion order (order), - # - upstream_nodes, - # - subdomains for the kinematic wave domains for parallel execution (execution order of - # subbasins (subdomain_order), traversion order per subbasin (topo_subdomain) and - # Vector indices per subbasin matching the traversion order of the complete domain - # (indices_subdomain)) - # - the indices that map it back to the two dimensional grid (indices) - - # for the land domain the x and y length [m] of the grid cells are stored - # for reservoirs and lakes indices information is available from the initialization - # functions - land = ( - graph = graph, - upstream_nodes = filter_upsteam_nodes(graph, pits[inds]), - subdomain_order = subbas_order, - topo_subdomain = topo_subbas, - indices_subdomain = indices_subbas, - order = toposort, - indices = inds, - reverse_indices = rev_inds, - area = xl .* yl, - ) - river = ( - graph = graph_riv, - upstream_nodes = filter_upsteam_nodes(graph_riv, pits[inds_riv]), - subdomain_order = subriv_order, - topo_subdomain = topo_subriv, - indices_subdomain = indices_subriv, - order = toposort_riv, - indices = inds_riv, - reverse_indices = rev_inds_riv, - ) - - model = Model( - config, - (; land, river, reservoir, lake, index_river, frac_toriver), - (subsurface = nothing, land = olf, river = rf), - hbv, - clock, - reader, - writer, - HbvModel(), - ) - - model = set_states(model) - - return model -end - -function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:HbvModel} - (; lateral, vertical, network, config) = model - - # vertical hbv concept is updated until snow state, after that (optional) - # snow transport is possible - update_until_snow(vertical, config) - - # lateral snow transport - if get(config.model, "masswasting", false)::Bool - lateral_snow_transport!( - vertical.snow, - vertical.snowwater, - lateral.land.sl, - network.land, - ) - end - - # update vertical hbv concept - update_after_snow(vertical, config) - - surface_routing(model) - - return model -end - -function set_states(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:HbvModel} - (; lateral, config) = model - - reinit = get(config.model, "reinit", true)::Bool - do_lakes = get(config.model, "lakes", false)::Bool - # read and set states in model object if reinit=true - if reinit == false - instate_path = input_path(config, config.state.path_input) - @info "Set initial conditions from state file `$instate_path`." - set_states(instate_path, model; type = Float) - # update kinematic wave volume for river and land domain - (; lateral) = model - # makes sure land cells with zero flow width are set to zero q and h - for i in eachindex(lateral.land.width) - if lateral.land.width[i] <= 0.0 - lateral.land.q[i] = 0.0 - lateral.land.h[i] = 0.0 - end - end - lateral.land.volume .= lateral.land.h .* lateral.land.width .* lateral.land.dl - lateral.river.volume .= lateral.river.h .* lateral.river.width .* lateral.river.dl - - if do_lakes - # storage must be re-initialized after loading the state with the current - # waterlevel otherwise the storage will be based on the initial water level - lakes = lateral.river.lake - lakes.storage .= - initialize_storage(lakes.storfunc, lakes.area, lakes.waterlevel, lakes.sh) - end - else - @info "Set initial conditions from default values." - end - - return model -end diff --git a/src/horizontal_process.jl b/src/horizontal_process.jl index 5977a667c..fe9c57252 100644 --- a/src/horizontal_process.jl +++ b/src/horizontal_process.jl @@ -68,36 +68,49 @@ function kin_wave!(Q, graph, toposort, Qold, q, alpha, beta, DCL, dt) return Q end -"Returns water table depth `zi` based on lateral subsurface flow `ssf` and hydraulic conductivity profile `ksat_profile`" -function ssf_water_table_depth(ssf, kh_0, slope, f, d, dw, z_exp, ksat_profile) - if ksat_profile == "exponential" - zi = log((f * ssf) / (dw * kh_0 * slope) + exp(-f * d)) / -f - elseif ksat_profile == "exponential_constant" - ssf_constant = kh_0 * slope * exp(-f * z_exp) * (d - z_exp) * dw - if ssf > ssf_constant - zi = log((f * (ssf - ssf_constant)) / (dw * kh_0 * slope) + exp(-f * z_exp)) / -f - else - zi = d - ssf / (dw * kh_0 * slope * exp(-f * z_exp)) - end - end +"Returns water table depth `zi` based on lateral subsurface flow `ssf` and hydraulic conductivity profile `KhExponential`" +function ssf_water_table_depth(ssf, slope, d, dw, kh_profile::KhExponential, i) + (; f, kh_0) = kh_profile + zi = log((f[i] * ssf) / (dw * kh_0[i] * slope) + exp(-f[i] * d)) / -f[i] return zi end -"Returns kinematic wave celecity `Cn` of lateral subsurface flow based on hydraulic conductivity profile `ksat_profile`" -function ssf_celerity(zi, kh_0, slope, theta_e, f, z_exp, ksat_profile) - if ksat_profile == "exponential" - Cn = (kh_0 * exp(-f * zi) * slope) / theta_e - elseif ksat_profile == "exponential_constant" - Cn_const = (kh_0 * exp(-f * z_exp) * slope) / theta_e - if zi < z_exp - Cn = (kh_0 * exp(-f * zi) * slope) / theta_e + Cn_const - else - Cn = Cn_const - end +"Returns water table depth `zi` based on lateral subsurface flow `ssf` and hydraulic conductivity profile `KhExponentialConstant`" +function ssf_water_table_depth(ssf, slope, d, dw, kh_profile::KhExponentialConstant, i) + (; z_exp) = kh_profile + (; kh_0, f) = kh_profile.exponential + ssf_constant = kh_0[i] * slope * exp(-f[i] * z_exp[i]) * (d - z_exp[i]) * dw + if ssf > ssf_constant + zi = + log( + (f[i] * (ssf - ssf_constant)) / (dw * kh_0[i] * slope) + + exp(-f[i] * z_exp[i]), + ) / -f[i] + else + zi = d - ssf / (dw * kh_0[i] * slope * exp(-f[i] * z_exp[i])) end + return zi +end + +"Returns kinematic wave celecity `Cn` of lateral subsurface flow based on hydraulic conductivity profile `KhExponential`" +function ssf_celerity(zi, slope, theta_e, kh_profile::KhExponential, i) + (; kh_0, f) = kh_profile + Cn = (kh_0[i] * exp(-f[i] * zi) * slope) / theta_e return Cn end +"Returns kinematic wave celecity `Cn` of lateral subsurface flow based on hydraulic conductivity profile `KhExponentialConstant`" +function ssf_celerity(zi, slope, theta_e, kh_profile::KhExponentialConstant, i) + (; z_exp) = kh_profile + (; kh_0, f) = kh_profile.exponential + Cn_const = (kh_0[i] * exp(-f[i] * z_exp[i]) * slope) / theta_e + return if zi < z_exp[i] + (kh_0[i] * exp(-f[i] * zi) * slope) / theta_e + Cn_const + else + Cn_const + end +end + """ kinematic_wave_ssf(ssfin, ssf_prev, zi_prev, r, kh_0, slope, theta_e, f, d, dt, dx, dw, ssfmax, z_exp, ksat_profile) @@ -115,19 +128,16 @@ function kinematic_wave_ssf( ssf_prev, zi_prev, r, - kh_0, slope, theta_e, - f, d, dt, dx, dw, ssfmax, - z_exp, - ksat_profile, + kh_profile::Union{KhExponential, KhExponentialConstant}, + i, ) - epsilon = 1.0e-12 max_iters = 3000 @@ -139,9 +149,9 @@ function kinematic_wave_ssf( count = 1 # Estimate zi on the basis of the relation between subsurface flow and zi - zi = ssf_water_table_depth(ssf, kh_0, slope, f, d, dw, z_exp, ksat_profile) + zi = ssf_water_table_depth(ssf, slope, d, dw, kh_profile, i) # Reciprocal of derivative delta Q/ delta z_i, constrained w.r.t. neff on the basis of the continuity equation) - Cn = ssf_celerity(zi, kh_0, slope, theta_e, f, z_exp, ksat_profile) + Cn = ssf_celerity(zi, slope, theta_e, kh_profile, i) # Term of the continuity equation for Newton-Raphson iteration for iteration 1 # because celerity Cn is depending on zi, the increase or decrease of zi is moved to the recharge term of the continuity equation # then (1./Cn)*ssf_prev can be replaced with (1./Cn)*ssf, and thus celerity and lateral flow rate ssf are then in line @@ -161,9 +171,9 @@ function kinematic_wave_ssf( # Start while loop of Newton-Raphson iteration m until continuity equation approaches zero while true # Estimate zi on the basis of the relation between lateral flow rate and groundwater level - zi = ssf_water_table_depth(ssf, kh_0, slope, f, d, dw, z_exp, ksat_profile) + zi = ssf_water_table_depth(ssf, slope, d, dw, kh_profile, i) # Reciprocal of derivative delta Q/ delta z_i, constrained w.r.t. neff on the basis of the continuity equation - Cn = ssf_celerity(zi, kh_0, slope, theta_e, f, z_exp, ksat_profile) + Cn = ssf_celerity(zi, slope, theta_e, kh_profile, i) # Term of the continuity equation for given Newton-Raphson iteration m # because celerity Cn is depending on zi, the increase or decrease of zi is moved to the recharge term of the continuity equation # then (1./Cn)*ssf_prev can be replaced with (1./Cn)*ssf, and thus celerity and lateral flow rate ssf are then in line @@ -194,7 +204,6 @@ function kinematic_wave_ssf( zi = clamp(zi, 0.0, d) return ssf, zi, exfilt - end end @@ -207,8 +216,21 @@ Kinematic wave for lateral subsurface flow for a single cell and timestep, based Returns lateral subsurface flow `ssf`, water table depth `zi` and exfiltration rate `exfilt`. """ -function kinematic_wave_ssf(ssfin, ssf_prev, zi_prev, r, kh, slope, theta_e, d, dt, dx, dw, ssfmax) - +function kinematic_wave_ssf( + ssfin, + ssf_prev, + zi_prev, + r, + slope, + theta_e, + d, + dt, + dx, + dw, + ssfmax, + kh_profile::KhLayered, + i, +) epsilon = 1.0e-12 max_iters = 3000 @@ -219,7 +241,7 @@ function kinematic_wave_ssf(ssfin, ssf_prev, zi_prev, r, kh, slope, theta_e, d, ssf = (ssf_prev + ssfin) / 2.0 count = 1 # celerity (Cn) - Cn = (slope * kh) / theta_e + Cn = (slope * kh_profile.kh[i]) / theta_e # constant term of the continuity equation for Newton-Raphson c = (dt / dx) * ssfin + (1.0 / Cn) * ssf_prev + r # continuity equation of which solution should be zero @@ -264,14 +286,12 @@ function kinematic_wave_ssf(ssfin, ssf_prev, zi_prev, r, kh, slope, theta_e, d, end """ - accucapacitystate!(material, network, capacity) -> material + accucapacitystate!(material, network, capacity) Transport of material downstream with a limited transport capacity over a directed graph. Mutates the material input. The network is expected to hold a graph and order field, where the graph implements the Graphs interface, and the order is a valid topological ordering such as that returned by `Graphs.topological_sort_by_dfs`. - -Returns the material state after transport. """ function accucapacitystate!(material, network, capacity) (; graph, order) = network @@ -289,7 +309,7 @@ function accucapacitystate!(material, network, capacity) error("bifurcations not supported") end end - return material + return nothing end """ @@ -298,19 +318,19 @@ end Non mutating version of `accucapacitystate!`. """ function accucapacitystate(material, network, capacity) - accucapacitystate!(copy(material), network, capacity) + material = copy(material) + accucapacitystate!(material, network, capacity) + return material end """ - accucapacityflux!(flux, material, network, capacity) -> flux, material + accucapacityflux!(flux, material, network, capacity) Transport of material downstream with a limited transport capacity over a directed graph. Updates the material input, and overwrites the flux input, not using existing values. The network is expected to hold a graph and order field, where the graph implements the Graphs interface, and the order is a valid topological ordering such as that returned by `Graphs.topological_sort_by_dfs`. - -Returns the flux (material leaving each cell), and material (left after transport). """ function accucapacityflux!(flux, material, network, capacity) (; graph, order) = network @@ -329,7 +349,7 @@ function accucapacityflux!(flux, material, network, capacity) error("bifurcations not supported") end end - return flux, material + return nothing end """ @@ -338,7 +358,10 @@ end Non mutating version of `accucapacityflux!`. """ function accucapacityflux(material, network, capacity) - accucapacityflux!(zero(material), copy(material), network, capacity) + flux = zero(material) + material = copy(material) + accucapacityflux!(flux, material, network, capacity) + return flux, material end """ @@ -349,9 +372,9 @@ Lateral snow transport. Transports snow downhill. Mutates `snow` and `snowwater` function lateral_snow_transport!(snow, snowwater, slope, network) snowflux_frac = min.(0.5, slope ./ 5.67) .* min.(1.0, snow ./ 10000.0) maxflux = snowflux_frac .* snow - snow = accucapacitystate!(snow, network, maxflux) - snowwater = accucapacitystate!(snowwater, network, snowwater .* snowflux_frac) - return snow, snowwater + accucapacitystate!(snow, network, maxflux) + accucapacitystate!(snowwater, network, snowwater .* snowflux_frac) + return nothing end """ @@ -373,7 +396,6 @@ function local_inertial_flow( froude_limit, dt, ) - slope = (zs1 - zs0) / length pow_R = cbrt(R * R * R * R) unit = one(hf) @@ -411,15 +433,13 @@ function local_inertial_flow( froude_limit, dt, ) - slope = (zs1 - zs0) / length unit = one(theta) half = oftype(theta, 0.5) pow_hf = cbrt(hf * hf * hf * hf * hf * hf * hf) q = ( - ((theta * q0 + half * (unit - theta) * (qu + qd)) - g * hf * width * dt * slope) / - (unit + g * dt * mannings_n_sq * abs(q0) / (pow_hf * width)) + ((theta * q0 + half * (unit - theta) * (qu + qd)) - g * hf * width * dt * slope) / (unit + g * dt * mannings_n_sq * abs(q0) / (pow_hf * width)) ) # if froude number > 1.0, limit flow if froude_limit diff --git a/src/io.jl b/src/io.jl index 3b7c63499..93ec265a0 100644 --- a/src/io.jl +++ b/src/io.jl @@ -5,12 +5,15 @@ Output data can be written to netCDF or CSV files. For configuration files we use TOML. =# +#TODO (v1.0): check if mapping of variables (input and output) in TOML file should be +#simplified. Direct mapping is now used. + """Turn "a.aa.aaa" into (:a, :aa, :aaa)""" symbols(s) = Tuple(Symbol(x) for x in split(s, '.')) """Turn symbols"a.aa.aaa" into (:a, :aa, :aaa)""" macro symbols_str(s) - Tuple(Symbol(x) for x in split(s, '.')) + return Tuple(Symbol(x) for x in split(s, '.')) end "Get a nested field using a tuple of Symbols" @@ -34,8 +37,8 @@ if it exists. It behaves largely like a distionary, but it overloads `getpropert `setproperty` to support syntax like `config.model.reinit = false`. """ struct Config - dict::Dict{String,Any} # nested key value mapping of all settings - path::Union{String,Nothing} # path to the TOML file, or nothing + dict::Dict{String, Any} # nested key value mapping of all settings + path::Union{String, Nothing} # path to the TOML file, or nothing end Config(path::AbstractString) = Config(TOML.parsefile(path), path) @@ -52,7 +55,8 @@ end function Base.setproperty!(config::Config, f::Symbol, x) dict = Dict(config) - return dict[String(f)] = x + dict[String(f)] = x + return nothing end "Get a value from the Config with either the key or an alias of the key." @@ -127,7 +131,7 @@ function ncvar_name_modifier(var; config = nothing) if length(var[dim_name]) > 1 # if modifier is provided as a list for each dim item indices = [] - for i = 1:length(var[dim_name]) + for i in 1:length(var[dim_name]) index = get_index_dimension(var, config, var[dim_name][i]) @info "NetCDF parameter `$ncname` is modified with scale `$(scale[i])` and offset `$(offset[i])` at index `$index`." push!(indices, index) @@ -178,31 +182,35 @@ function get_at(ds::CFDataset, varname::AbstractString, i) end function get_param_res(model) - Dict( - symbols"vertical.precipitation" => model.lateral.river.reservoir.precipitation, - symbols"vertical.potential_evaporation" => + return Dict( + symbols"vertical.atmospheric_forcing.precipitation" => + model.lateral.river.reservoir.precipitation, + symbols"vertical.atmospheric_forcing.potential_evaporation" => model.lateral.river.reservoir.evaporation, ) end function get_param_lake(model) - Dict( - symbols"vertical.precipitation" => model.lateral.river.lake.precipitation, - symbols"vertical.potential_evaporation" => model.lateral.river.lake.evaporation, + return Dict( + symbols"vertical.atmospheric_forcing.precipitation" => + model.lateral.river.lake.precipitation, + symbols"vertical.atmospheric_forcing.potential_evaporation" => + model.lateral.river.lake.evaporation, ) end -mover_params = (symbols"vertical.precipitation", symbols"vertical.potential_evaporation") +mover_params = ( + symbols"vertical.atmospheric_forcing.precipitation", + symbols"vertical.atmospheric_forcing.potential_evaporation", +) -function load_fixed_forcing(model) +function load_fixed_forcing!(model) (; reader, network, config) = model (; forcing_parameters) = reader do_reservoirs = get(config.model, "reservoirs", false)::Bool do_lakes = get(config.model, "lakes", false)::Bool - mover_params = - (symbols"vertical.precipitation", symbols"vertical.potential_evaporation") reverse_indices = network.land.reverse_indices if do_reservoirs sel_reservoirs = network.reservoir.indices_coverage @@ -237,6 +245,7 @@ function load_fixed_forcing(model) end end end + return nothing end "Get dynamic netCDF input for the given time" @@ -303,7 +312,7 @@ function update_forcing!(model) param_vector .= data_sel end - return model + return nothing end """ @@ -327,6 +336,7 @@ function load_dynamic_input!(model) if haskey(model.config.input, "cyclic") update_cyclic!(model) end + return nothing end "Get cyclic netCDF input for the given time" @@ -355,6 +365,7 @@ function update_cyclic!(model) end end end + return nothing end """ @@ -368,7 +379,7 @@ https://github.com/Alexander-Barth/NCDatasets.jl/issues/106 Note that using this will prevent automatic garbage collection and thus closure of the NCDataset. """ -const nc_handles = Dict{String,NCDataset{Nothing}}() +const nc_handles = Dict{String, NCDataset{Nothing}}() "Safely create a netCDF file, even if it has already been opened for creation" function create_tracked_netcdf(path) @@ -402,7 +413,7 @@ function setup_scalar_netcdf( ds, "time", Float64, - ("time",), + ("time",); attrib = ["units" => time_units, "calendar" => calendar], ) set_extradim_netcdf(ds, extra_dim) @@ -413,7 +424,7 @@ function setup_scalar_netcdf( ds, nc.location_dim, nc.locations, - (nc.location_dim,), + (nc.location_dim,); attrib = ["cf_role" => "timeseries_id"], ) v = param(modelmap, nc.par) @@ -422,7 +433,7 @@ function setup_scalar_netcdf( ds, nc.var, float_type, - (nc.location_dim, "time"), + (nc.location_dim, "time"); attrib = ["_FillValue" => float_type(NaN)], ) elseif eltype(v) <: SVector @@ -432,7 +443,7 @@ function setup_scalar_netcdf( ds, nc.var, float_type, - (nc.location_dim, "time"), + (nc.location_dim, "time"); attrib = ["_FillValue" => float_type(NaN)], ) else @@ -440,7 +451,7 @@ function setup_scalar_netcdf( ds, nc.var, float_type, - (nc.location_dim, extra_dim.name, "time"), + (nc.location_dim, extra_dim.name, "time"); attrib = ["_FillValue" => float_type(NaN)], ) end @@ -456,22 +467,16 @@ function set_extradim_netcdf( ds, extra_dim::NamedTuple{ (:name, :value), - Tuple{String,Vector{T}}, - } where {T<:Union{String,Float64}}, + Tuple{String, Vector{T}}, + } where {T <: Union{String, Float64}}, ) # the axis attribute `Z` is required to import this type of 3D data by Delft-FEWS the # values of this dimension `extra_dim.value` should be of type Float64 if extra_dim.name == "layer" attributes = ["long_name" => "layer_index", "standard_name" => "layer_index", "axis" => "Z"] - elseif extra_dim.name == "classes" - attributes = [ - "long_name" => extra_dim.name, - "standard_name" => extra_dim.name, - "axis" => "Z", - ] end - defVar(ds, extra_dim.name, extra_dim.value, (extra_dim.name,), attrib = attributes) + defVar(ds, extra_dim.name, extra_dim.value, (extra_dim.name,); attrib = attributes) return nothing end @@ -490,7 +495,6 @@ function setup_grid_netcdf( float_type = Float32, deflatelevel = 0, ) - ds = create_tracked_netcdf(path) defDim(ds, "time", Inf) # unlimited if sizeinmetres @@ -498,7 +502,7 @@ function setup_grid_netcdf( ds, "x", ncx, - ("x",), + ("x",); attrib = [ "long_name" => "x coordinate of projection", "standard_name" => "projection_x_coordinate", @@ -511,7 +515,7 @@ function setup_grid_netcdf( ds, "y", ncy, - ("y",), + ("y",); attrib = [ "long_name" => "y coordinate of projection", "standard_name" => "projection_y_coordinate", @@ -526,7 +530,7 @@ function setup_grid_netcdf( ds, "lon", ncx, - ("lon",), + ("lon",); attrib = [ "long_name" => "longitude", "standard_name" => "longitude", @@ -538,7 +542,7 @@ function setup_grid_netcdf( ds, "lat", ncy, - ("lat",), + ("lat",); attrib = [ "long_name" => "latitude", "standard_name" => "latitude", @@ -553,7 +557,7 @@ function setup_grid_netcdf( ds, "time", Float64, - ("time",), + ("time",); attrib = ["units" => time_units, "calendar" => calendar], deflatelevel = deflatelevel, ) @@ -565,7 +569,7 @@ function setup_grid_netcdf( ds, key, float_type, - ("x", "y", "time"), + ("x", "y", "time"); attrib = ["_FillValue" => float_type(NaN)], deflatelevel = deflatelevel, ) @@ -575,7 +579,7 @@ function setup_grid_netcdf( ds, key, float_type, - ("x", "y", extra_dim.name, "time"), + ("x", "y", extra_dim.name, "time"); attrib = ["_FillValue" => float_type(NaN)], deflatelevel = deflatelevel, ) @@ -591,7 +595,7 @@ function setup_grid_netcdf( ds, key, float_type, - ("lon", "lat", "time"), + ("lon", "lat", "time"); attrib = ["_FillValue" => float_type(NaN)], deflatelevel = deflatelevel, ) @@ -601,7 +605,7 @@ function setup_grid_netcdf( ds, key, float_type, - ("lon", "lat", extra_dim.name, "time"), + ("lon", "lat", extra_dim.name, "time"); attrib = ["_FillValue" => float_type(NaN)], deflatelevel = deflatelevel, ) @@ -623,27 +627,27 @@ end struct NCReader{T} dataset::CFDataset dataset_times::Vector{T} - cyclic_dataset::Union{NCDataset,Nothing} - cyclic_times::Dict{Tuple{Symbol,Vararg{Symbol}},Vector{Tuple{Int,Int}}} - forcing_parameters::Dict{Tuple{Symbol,Vararg{Symbol}},NamedTuple} - cyclic_parameters::Dict{Tuple{Symbol,Vararg{Symbol}},NamedTuple} + cyclic_dataset::Union{NCDataset, Nothing} + cyclic_times::Dict{Tuple{Symbol, Vararg{Symbol}}, Vector{Tuple{Int, Int}}} + forcing_parameters::Dict{Tuple{Symbol, Vararg{Symbol}}, NamedTuple} + cyclic_parameters::Dict{Tuple{Symbol, Vararg{Symbol}}, NamedTuple} end struct Writer - dataset::Union{NCDataset,Nothing} # dataset (netCDF) for grid data - parameters::Dict{String,Any} # mapping of netCDF variable names to model parameters (arrays) - nc_path::Union{String,Nothing} # path netCDF file (grid data) - csv_path::Union{String,Nothing} # path of CSV file + dataset::Union{NCDataset, Nothing} # dataset (netCDF) for grid data + parameters::Dict{String, Any} # mapping of netCDF variable names to model parameters (arrays) + nc_path::Union{String, Nothing} # path netCDF file (grid data) + csv_path::Union{String, Nothing} # path of CSV file csv_cols::Vector # model parameter (arrays) and associated reducer function for CSV output csv_io::IO # file handle to CSV file - state_dataset::Union{NCDataset,Nothing} # dataset with model states (netCDF) - state_parameters::Dict{String,Any} # mapping of netCDF variable names to model states (arrays) - state_nc_path::Union{String,Nothing} # path netCDF file with states - dataset_scalar::Union{NCDataset,Nothing} # dataset (netCDF) for scalar data + state_dataset::Union{NCDataset, Nothing} # dataset with model states (netCDF) + state_parameters::Dict{String, Any} # mapping of netCDF variable names to model states (arrays) + state_nc_path::Union{String, Nothing} # path netCDF file with states + dataset_scalar::Union{NCDataset, Nothing} # dataset (netCDF) for scalar data nc_scalar::Vector # model parameter (arrays) and associated reducer function for netCDF scalar output ncvars_dims::Vector # model parameter (String) and associated netCDF variable, location dimension and location name for scalar data - nc_scalar_path::Union{String,Nothing} # path netCDF file (scalar data) - extra_dim::Union{NamedTuple,Nothing} # name and values for extra dimension (to store SVectors) + nc_scalar_path::Union{String, Nothing} # path netCDF file (scalar data) + extra_dim::Union{NamedTuple, Nothing} # name and values for extra dimension (to store SVectors) end function prepare_reader(config) @@ -672,7 +676,7 @@ function prepare_reader(config) if isempty(dynamic_paths) error("No files found with name '$glob_path' in '$glob_dir'") end - dataset = NCDataset(dynamic_paths, aggdim = "time", deferopen = false) + dataset = NCDataset(dynamic_paths; aggdim = "time", deferopen = false) if haskey(dataset["time"].attrib, "_FillValue") @warn "Time dimension contains `_FillValue` attribute, this is not in line with CF conventions." @@ -695,7 +699,7 @@ function prepare_reader(config) do_cyclic = haskey(config.input, "cyclic") # create map from internal location to netCDF variable name for forcing parameters - forcing_parameters = Dict{Tuple{Symbol,Vararg{Symbol}},NamedTuple}() + forcing_parameters = Dict{Tuple{Symbol, Vararg{Symbol}}, NamedTuple}() for par in config.input.forcing fields = symbols(par) ncname, mod = ncvar_name_modifier(param(config.input, fields)) @@ -711,8 +715,8 @@ function prepare_reader(config) # (memory usage)) if do_cyclic == true cyclic_dataset = NCDataset(cyclic_path) - cyclic_parameters = Dict{Tuple{Symbol,Vararg{Symbol}},NamedTuple}() - cyclic_times = Dict{Tuple{Symbol,Vararg{Symbol}},Vector{Tuple{Int,Int}}}() + cyclic_parameters = Dict{Tuple{Symbol, Vararg{Symbol}}, NamedTuple}() + cyclic_times = Dict{Tuple{Symbol, Vararg{Symbol}}, Vector{Tuple{Int, Int}}}() for par in config.input.cyclic fields = symbols(par) ncname, mod = ncvar_name_modifier(param(config.input, fields)) @@ -726,9 +730,9 @@ function prepare_reader(config) @info "Set `$par` using netCDF variable `$ncname` as cyclic parameter, with `$(length(cyclic_nc_times))` timesteps." end else - cyclic_parameters = Dict{Tuple{Symbol,Vararg{Symbol}},NamedTuple}() + cyclic_parameters = Dict{Tuple{Symbol, Vararg{Symbol}}, NamedTuple}() cyclic_dataset = nothing - cyclic_times = Dict{Tuple{Symbol,Vararg{Symbol}},Vector{Tuple{Int,Int}}}() + cyclic_times = Dict{Tuple{Symbol, Vararg{Symbol}}, Vector{Tuple{Int, Int}}}() end # check if there is overlap @@ -759,7 +763,7 @@ function locations_map(ds, mapname, config) config, mapname; optional = false, - type = Union{Int,Missing}, + type = Union{Int, Missing}, allow_missing = true, ) ids = unique(skipmissing(map_2d)) @@ -834,13 +838,13 @@ function flat!(d, path, el::Dict) for (k, v) in pairs(el) flat!(d, string(path, '.', k), v) end - return d + return nothing end function flat!(d, path, el) k = symbols(path) d[k] = el - return d + return nothing end """ @@ -872,7 +876,7 @@ Dict( ``` """ function ncnames(dict) - ncnames_dict = Dict{Tuple{Symbol,Vararg{Symbol}},String}() + ncnames_dict = Dict{Tuple{Symbol, Vararg{Symbol}}, String}() for (k, v) in dict if v isa Dict # ignore top level values (e.g. output.path) flat!(ncnames_dict, k, v) @@ -887,7 +891,7 @@ end Create a Dict that maps parameter netCDF names to arrays in the Model. """ function out_map(ncnames_dict, modelmap) - output_map = Dict{String,Any}() + output_map = Dict{String, Any}() for (par, ncname) in ncnames_dict A = param(modelmap, par) output_map[ncname] = (par = par, vector = A) @@ -895,7 +899,6 @@ function out_map(ncnames_dict, modelmap) return output_map end - function get_reducer_func(col, rev_inds, args...) parameter = col["parameter"] if occursin("reservoir", parameter) @@ -943,12 +946,12 @@ function prepare_writer( calendar, time_units, extra_dim, - sizeinmetres, + sizeinmetres; deflatelevel = deflatelevel, ) else nc_path = nothing - output_map = Dict{String,Any}() + output_map = Dict{String, Any}() ds = nothing end @@ -972,7 +975,7 @@ function prepare_writer( ) else ds_outstate = nothing - state_map = Dict{String,Any}() + state_map = Dict{String, Any}() nc_state_path = nothing end @@ -1037,7 +1040,6 @@ function prepare_writer( csv_io = IOBuffer() end - return Writer( ds, output_map, @@ -1076,7 +1078,7 @@ function write_netcdf_timestep(model, dataset) dataset[nc["name"]][:, time_index] .= v else nlayer = length(first(A)) - for i = 1:nlayer + for i in 1:nlayer v = nt.reducer(getindex.(A, i)) dataset[nc["name"]][:, i, time_index] .= v end @@ -1094,7 +1096,7 @@ function write_netcdf_timestep(model, dataset, parameters) time_index = add_time(dataset, clock.time) - buffer = zeros(Union{Float,Missing}, size(model.network.land.reverse_indices)) + buffer = zeros(Union{Float, Missing}, size(model.network.land.reverse_indices)) for (key, val) in parameters (; par, vector) = val sel = active_indices(network, par) @@ -1108,7 +1110,7 @@ function write_netcdf_timestep(model, dataset, parameters) dataset[key][:, :, time_index] = buffer elseif elemtype <: SVector nlayer = length(first(vector)) - for i = 1:nlayer + for i in 1:nlayer # ensure no other information is written fill!(buffer, missing) buffer[sel] .= getindex.(vector, i) @@ -1213,7 +1215,7 @@ end "Mapping from reducer strings in the TOML to functions" function reducerfunction(reducer::AbstractString) - functionmap = Dict{String,Function}( + functionmap = Dict{String, Function}( "maximum" => maximum, "minimum" => minimum, "mean" => mean, @@ -1244,13 +1246,13 @@ function reducer(col, rev_inds, x_nc, y_nc, config, dataset, fileformat) config, mapname; optional = false, - type = Union{Int,Missing}, + type = Union{Int, Missing}, allow_missing = true, ) @info "Adding scalar output for a map with a reducer function." fileformat param mapname reducer_name ids = unique(skipmissing(map_2d)) # from id to list of internal indices - inds = Dict{Int,Vector{Int}}(id => Vector{Int}() for id in ids) + inds = Dict{Int, Vector{Int}}(id => Vector{Int}() for id in ids) for i in eachindex(map_2d) v = map_2d[i] ismissing(v) && continue @@ -1326,7 +1328,7 @@ function write_csv_row(model) print(io, ',', el) end end - println(io) + return println(io) end "From a time and a calendar, create the right CFTime DateTimeX type" @@ -1346,24 +1348,24 @@ function reset_clock!(clock::Clock, config) clock.time = new_clock.time clock.iteration = new_clock.iteration clock.dt = new_clock.dt - return clock + return nothing end function advance!(clock) clock.iteration += 1 clock.time += clock.dt - return clock + return nothing end function rewind!(clock) clock.iteration -= 1 clock.time -= clock.dt - return clock + return nothing end "Read a rating curve from CSV into a NamedTuple of vectors" function read_sh_csv(path) - data, header = readdlm(path, ',', Float, header = true) + data, header = readdlm(path, ',', Float; header = true) names = vec(uppercase.(header)) idx_h = findfirst(==("H"), names) idx_s = findfirst(==("S"), names) @@ -1377,14 +1379,14 @@ end "Read a specific storage curve from CSV into a NamedTuple of vectors" function read_hq_csv(path) - data = readdlm(path, ',', Float, skipstart = 1) + data = readdlm(path, ',', Float; skipstart = 1) # Q is a matrix with 365 columns, one for each day in the year return (H = data[:, 1], Q = data[:, 2:end]) end # these represent the type of the rating curve and specific storage data -const SH = NamedTuple{(:H, :S),Tuple{Vector{Float},Vector{Float}}} -const HQ = NamedTuple{(:H, :Q),Tuple{Vector{Float},Matrix{Float}}} +const SH = NamedTuple{(:H, :S), Tuple{Vector{Float}, Vector{Float}}} +const HQ = NamedTuple{(:H, :Q), Tuple{Vector{Float}, Matrix{Float}}} is_increasing(v) = last(v) > first(v) @@ -1426,7 +1428,6 @@ using `nc_dim_name`. """ nc_dim(ds::CFDataset, name) = ds[nc_dim_name(ds, name)] - """ internal_dim_name(name::Symbol) @@ -1437,7 +1438,7 @@ function internal_dim_name(name::Symbol) return :x elseif name in (:y, :lat, :latitude) return :y - elseif name in (:time, :layer, :flood_depth, :classes) + elseif name in (:time, :layer, :flood_depth) return name elseif startswith(string(name), "time") return :time @@ -1497,7 +1498,7 @@ consider it increasing, going from the top layer (1) to deeper layers. This is t accepting data that we have accepted before. """ function dim_directions(ds::CFDataset, dim_names) - pairs = Pair{Symbol,Bool}[] + pairs = Pair{Symbol, Bool}[] for d in dim_names if d == :layer && !(haskey(ds, "layer")) inc = true @@ -1522,8 +1523,6 @@ function permute_data(data, dim_names) @assert ndims(data) == length(dim_names) if :layer in dim_names desired_order = (:x, :y, :layer) - elseif :classes in dim_names - desired_order = (:x, :y, :classes) elseif :flood_depth in dim_names desired_order = (:x, :y, :flood_depth) else @@ -1567,12 +1566,6 @@ function reverse_data!(data, dims_increasing) y = dims_increasing.y, flood_depth = dims_increasing.flood_depth, ) - elseif length(dims_increasing) == 3 && haskey(dims_increasing, :classes) - dims_increasing_ordered = ( - x = dims_increasing.x, - y = dims_increasing.y, - classes = dims_increasing.classes, - ) else error("Unsupported number of dimensions") end @@ -1609,7 +1602,7 @@ function read_x_axis(ds::CFDataset)::Vector{Float64} return sort!(Float64.(ds[candidate][:])) end end - error("no x axis found in $(path(ds))") + return error("no x axis found in $(path(ds))") end """ @@ -1625,32 +1618,27 @@ function read_y_axis(ds::CFDataset)::Vector{Float64} return sort!(Float64.(ds[candidate][:])) end end - error("no y axis found in $(path(ds))") + return error("no y axis found in $(path(ds))") end -"Get `index` for dimension name `layer` or `classes` based on `model`" +"Get `index` for dimension name `layer` based on `model`" function get_index_dimension(var, model)::Int (; vertical) = model if haskey(var, "layer") - inds = collect(1:vertical.maxlayers) + inds = collect(1:(vertical.soil.parameters.maxlayers)) index = inds[var["layer"]] - elseif haskey(var, "class") - index = findfirst(x -> x == var["class"], vertical.classes) else error("Unrecognized or missing dimension name to index $(var)") end return index end -"Get `index` for dimension name `layer` or `classes` based on `config` (TOML file)" +"Get `index` for dimension name `layer` based on `config` (TOML file)" function get_index_dimension(var, config::Config, dim_value)::Int if haskey(var, "layer") v = get(config.model, "thicknesslayers", Float[]) - inds = collect(1:length(v)+1) + inds = collect(1:(length(v) + 1)) index = inds[dim_value] - elseif haskey(var, "class") - classes = get(config.model, "classes", "") - index = findfirst(x -> x == dim_value, classes) else error("Unrecognized or missing dimension name to index $(var)") end diff --git a/src/logging.jl b/src/logging.jl index 3a9a3d7e4..eec1873db 100644 --- a/src/logging.jl +++ b/src/logging.jl @@ -46,7 +46,7 @@ function format_message(io::IO, args)::Nothing end "Initialize a logger, which is different if `fews_run` is set in the Config." -function init_logger(config::Config; silent = false)::Tuple{TeeLogger,IOStream} +function init_logger(config::Config; silent = false)::Tuple{TeeLogger, IOStream} loglevel = parse_loglevel(get(config, "loglevel", "info")) path_log = output_path(config, get(config, "path_log", "log.txt")) mkpath(dirname(path_log)) @@ -61,7 +61,7 @@ function init_logger(config::Config; silent = false)::Tuple{TeeLogger,IOStream} # https://github.com/JuliaLogging/LoggingExtras.jl/issues/46#issuecomment-803716480 ConsoleLogger( IOContext(log_handle, :compact => false, :limit => false, :color => false), - loglevel, + loglevel; show_limited = false, ) end diff --git a/src/parameters.jl b/src/parameters.jl new file mode 100644 index 000000000..f2d2d582a --- /dev/null +++ b/src/parameters.jl @@ -0,0 +1,104 @@ +"Struct to store (shared) vegetation parameters" +@get_units @grid_loc @with_kw struct VegetationParameters{T} + # Leaf area index [m² m⁻²] + leaf_area_index::Union{Vector{T}, Nothing} | "m2 m-2" + # Storage woody part of vegetation [mm] + storage_wood::Union{Vector{T}, Nothing} | "mm" + # Extinction coefficient [-] (to calculate canopy gap fraction) + kext::Union{Vector{T}, Nothing} | "-" + # Specific leaf storage [mm] + storage_specific_leaf::Union{Vector{T}, Nothing} | "mm" + # Canopy gap fraction [-] + canopygapfraction::Vector{T} | "-" + # Maximum canopy storage [mm] + cmax::Vector{T} | "mm" + # Rooting depth [mm] + rootingdepth::Vector{T} | "mm" + # Crop coefficient Kc [-] + kc::Vector{T} | "-" +end + +"Initialize (shared) vegetation parameters" +function VegetationParameters(nc, config, inds) + n = length(inds) + rootingdepth = ncread( + nc, + config, + "vertical.vegetation_parameter_set.rootingdepth"; + sel = inds, + defaults = 750.0, + type = Float, + ) + kc = ncread( + nc, + config, + "vertical.vegetation_parameter_set.kc"; + sel = inds, + defaults = 1.0, + type = Float, + ) + if haskey(config.input.vertical.vegetation_parameter_set, "leaf_area_index") + storage_specific_leaf = ncread( + nc, + config, + "vertical.vegetation_parameter_set.storage_specific_leaf"; + optional = false, + sel = inds, + type = Float, + ) + storage_wood = ncread( + nc, + config, + "vertical.vegetation_parameter_set.storage_wood"; + optional = false, + sel = inds, + type = Float, + ) + kext = ncread( + nc, + config, + "vertical.vegetation_parameter_set.kext"; + optional = false, + sel = inds, + type = Float, + ) + vegetation_parameter_set = VegetationParameters(; + leaf_area_index = fill(mv, n), + storage_wood, + kext, + storage_specific_leaf, + canopygapfraction = fill(mv, n), + cmax = fill(mv, n), + rootingdepth, + kc, + ) + else + canopygapfraction = ncread( + nc, + config, + "vertical.vegetation_parameter_set.canopygapfraction"; + sel = inds, + defaults = 0.1, + type = Float, + ) + cmax = ncread( + nc, + config, + "vertical.vegetation_parameter_set.cmax"; + sel = inds, + defaults = 1.0, + type = Float, + ) + vegetation_parameter_set = VegetationParameters(; + leaf_area_index = nothing, + storage_wood = nothing, + kext = nothing, + storage_specific_leaf = nothing, + canopygapfraction, + cmax, + rootingdepth, + kc, + ) + end + return vegetation_parameter_set +end \ No newline at end of file diff --git a/src/reservoir_lake.jl b/src/reservoir_lake.jl index 5060c842d..edc55a91f 100644 --- a/src/reservoir_lake.jl +++ b/src/reservoir_lake.jl @@ -1,5 +1,5 @@ -@get_units @exchange @grid_type @grid_location @with_kw struct SimpleReservoir{T} - dt::T | "s" | 0 | "none" | "none" # Model time step [s] +@get_units @grid_loc @with_kw struct SimpleReservoir{T} + dt::T # Model time step [s] maxvolume::Vector{T} | "m3" # maximum storage (above which water is spilled) [m³] area::Vector{T} | "m2" # reservoir area [m²] maxrelease::Vector{T} | "m3 s-1" # maximum amount that can be released if below spillway [m³ s⁻¹] @@ -22,7 +22,6 @@ end end - function initialize_simple_reservoir(config, nc, inds_riv, nriv, pits, dt) # read only reservoir data if reservoirs true # allow reservoirs only in river cells @@ -132,7 +131,7 @@ function initialize_simple_reservoir(config, nc, inds_riv, nriv, pits, dt) n = length(resarea) @info "Read `$n` reservoir locations." - reservoirs = SimpleReservoir{Float}( + reservoirs = SimpleReservoir{Float}(; dt = dt, demand = resdemand, maxrelease = resmaxrelease, @@ -167,7 +166,7 @@ Update a single reservoir at position `i`. This is called from within the kinematic wave loop, therefore updating only for a single element rather than all at once. """ -function update(res::SimpleReservoir, i, inflow, timestepsecs) +function update!(res::SimpleReservoir, i, inflow, timestepsecs) # limit lake evaporation based on total available volume [m³] precipitation = 0.001 * res.precipitation[i] * (timestepsecs / res.dt) * res.area[i] @@ -201,21 +200,21 @@ function update(res::SimpleReservoir, i, inflow, timestepsecs) res.volume[i] = vol res.actevap[i] += 1000.0 * (actevap / res.area[i]) - return res + return nothing end -@get_units @exchange @grid_type @grid_location @with_kw struct Lake{T} - dt::T | "s" | 0 | "none" | "none" # Model time step [s] +@get_units @grid_loc @with_kw struct Lake{T} + dt::T # Model time step [s] lowerlake_ind::Vector{Int} | "-" # Index of lower lake (linked lakes) area::Vector{T} | "m2" # lake area [m²] - maxstorage::Vector{Union{T,Missing}} | "m3" # lake maximum storage from rating curve 1 [m³] + maxstorage::Vector{Union{T, Missing}} | "m3" # lake maximum storage from rating curve 1 [m³] threshold::Vector{T} | "m" # water level threshold H₀ [m] below that level outflow is zero storfunc::Vector{Int} | "-" # type of lake storage curve, 1: S = AH, 2: S = f(H) from lake data and interpolation outflowfunc::Vector{Int} | "-" # type of lake rating curve, 1: Q = f(H) from lake data and interpolation, 2: General Q = b(H - H₀)ᵉ, 3: Case of Puls Approach Q = b(H - H₀)² b::Vector{T} | "m3/2 s-1 (if e=3/2)" # rating curve coefficient e::Vector{T} | "-" # rating curve exponent - sh::Vector{Union{SH,Missing}} # data for storage curve - hq::Vector{Union{HQ,Missing}} # data for rating curve + sh::Vector{Union{SH, Missing}} # data for storage curve + hq::Vector{Union{HQ, Missing}} # data for rating curve waterlevel::Vector{T} | "m" # waterlevel H [m] of lake inflow::Vector{T} | "m3" # inflow to the lake [m³] storage::Vector{T} | "m3" # storage lake [m³] @@ -363,13 +362,13 @@ function initialize_lake(config, nc, inds_riv, nriv, pits, dt) @info "Read `$n_lakes` lake locations." - sh = Vector{Union{SH,Missing}}(missing, n_lakes) - hq = Vector{Union{HQ,Missing}}(missing, n_lakes) + sh = Vector{Union{SH, Missing}}(missing, n_lakes) + hq = Vector{Union{HQ, Missing}}(missing, n_lakes) lowerlake_ind = fill(0, n_lakes) # lake CSV parameter files are expected in the same directory as path_static path = dirname(input_path(config, config.input.path_static)) - for i = 1:n_lakes + for i in 1:n_lakes lakeloc = lakelocs[i] if linked_lakelocs[i] > 0 lowerlake_ind[i] = only(findall(x -> x == linked_lakelocs[i], lakelocs)) @@ -398,7 +397,7 @@ function initialize_lake(config, nc, inds_riv, nriv, pits, dt) end end n = length(lakearea) - lakes = Lake{Float}( + lakes = Lake{Float}(; dt = dt, lowerlake_ind = lowerlake_ind, area = lakearea, @@ -430,7 +429,6 @@ function initialize_lake(config, nc, inds_riv, nriv, pits, dt) pits end - "Determine the initial storage depending on the storage function" function initialize_storage(storfunc, area, waterlevel, sh) storage = similar(area) @@ -459,7 +457,7 @@ end "Determine the maximum storage for lakes with a rating curve of type 1" function maximum_storage(storfunc, outflowfunc, area, sh, hq) - maxstorage = Vector{Union{Float,Missing}}(missing, length(area)) + maxstorage = Vector{Union{Float, Missing}}(missing, length(area)) # maximum storage is based on the maximum water level (H) value in the H-Q table for i in eachindex(maxstorage) if outflowfunc[i] == 1 @@ -473,7 +471,6 @@ function maximum_storage(storfunc, outflowfunc, area, sh, hq) return maxstorage end - function interpolate_linear(x, xp, fp) if x <= minimum(xp) return minimum(fp) @@ -494,8 +491,7 @@ Update a single lake at position `i`. This is called from within the kinematic wave loop, therefore updating only for a single element rather than all at once. """ -function update(lake::Lake, i, inflow, doy, timestepsecs) - +function update!(lake::Lake, i, inflow, doy, timestepsecs) lo = lake.lowerlake_ind[i] has_lowerlake = lo != 0 @@ -532,7 +528,6 @@ function update(lake::Lake, i, inflow, doy, timestepsecs) ### Linearisation for specific storage/rating curves ### if lake.outflowfunc[i] == 1 || lake.outflowfunc[i] == 2 - diff_wl = has_lowerlake ? lake.waterlevel[i] - lake.waterlevel[lo] : 0.0 storage_input = (lake.storage[i] + precipitation - actevap) / timestepsecs + inflow @@ -592,7 +587,6 @@ function update(lake::Lake, i, inflow, doy, timestepsecs) lake.storage[lo] = lowerlake_storage lake.waterlevel[lo] = lowerlake_waterlevel end - end # update values in place @@ -603,5 +597,5 @@ function update(lake::Lake, i, inflow, doy, timestepsecs) lake.storage[i] = storage lake.actevap[i] += 1000.0 * (actevap / lake.area[i]) - return lake + return nothing end diff --git a/src/sbm.jl b/src/sbm.jl index baab14691..7293ec082 100644 --- a/src/sbm.jl +++ b/src/sbm.jl @@ -1,1326 +1,141 @@ -@get_units @exchange @grid_type @grid_location @with_kw struct SBM{ - T, - N, - M, - P<:Union{Paddy,Nothing}, - NP<:Union{NonPaddy,Nothing}, - D<:Union{NonIrrigationDemand,Nothing}, - L<:Union{NonIrrigationDemand,Nothing}, - I<:Union{NonIrrigationDemand,Nothing}, - A<:Union{AllocationLand,Nothing}, -} - # Model time step [s] - dt::T | "s" | 0 | "none" | "none" - # Maximum number of soil layers - maxlayers::Int | "-" | 0 | "none" | "none" - # number of cells - n::Int | "-" | 0 | "none" | "none" - # Number of soil layers - nlayers::Vector{Int} | "-" - # Number of unsaturated soil layers - n_unsatlayers::Vector{Int} | "-" - # Number of soil layers with vertical hydraulic conductivity value `kv` - nlayers_kv::Vector{Int} | "-" - # Fraction of river [-] - riverfrac::Vector{T} | "-" - # Saturated water content (porosity) [-] - theta_s::Vector{T} | "-" - # Residual water content [-] - theta_r::Vector{T} | "-" - # Vertical hydraulic conductivity [mm Δt⁻¹] at soil surface - kv_0::Vector{T} - # Vertical hydraulic conductivity [mm Δt⁻¹] per soil layer - kv::Vector{SVector{N,T}} | "-" - # Muliplication factor [-] applied to kv_z (vertical flow) - kvfrac::Vector{SVector{N,T}} | "-" - # Air entry pressure [cm] of soil (Brooks-Corey) - hb::Vector{T} | "cm" - # Soil thickness [mm] - soilthickness::Vector{T} | "mm" - # Thickness of soil layers [mm] - act_thickl::Vector{SVector{N,T}} | "mm" - # Cumulative sum of soil layers [mm], starting at soil surface (0) - sumlayers::Vector{SVector{M,T}} | "mm" - # Infiltration capacity of the compacted areas [mm Δt⁻¹] - infiltcappath::Vector{T} - # Soil infiltration capacity [mm Δt⁻¹] - infiltcapsoil::Vector{T} - # Soil infiltration reduction factor (when soil is frozen) [-] - soilinfredu::Vector{T} | "-" - # Maximum leakage [mm Δt⁻¹] from saturated zone - maxleakage::Vector{T} - # Fraction of open water (excluding rivers) [-] - waterfrac::Vector{T} | "-" - # Fraction of compacted area [-] - pathfrac::Vector{T} | "-" - # Rooting depth [mm] - rootingdepth::Vector{T} | "mm" - # Fraction of the root length density in each soil layer [-] - rootfraction::Vector{SVector{N,T}} | "-" - # Soil water pressure head h1 of the root water uptake reduction function (Feddes) [cm] - h1::Vector{T} | "cm" - # Soil water pressure head h2 of the root water uptake reduction function (Feddes) [cm] - h2::Vector{T} | "cm" - # Soil water pressure head h3_high of the root water uptake reduction function (Feddes) [cm] - h3_high::Vector{T} | "cm" - # Soil water pressure head h3_low of the root water uptake reduction function (Feddes) [cm] - h3_low::Vector{T} | "cm" - # Soil water pressure head h4 of the root water uptake reduction function (Feddes) [cm] - h4::Vector{T} | "cm" - # Calculated soil water pressure head h3 of the root water uptake reduction function (Feddes) [cm] - h3::Vector{T} | "cm" - # Root water uptake reduction at soil water pressure head h1 (0.0 or 1.0) [-] - alpha_h1::Vector{T} | "-" - # Controls how roots are linked to water table [-] - rootdistpar::Vector{T} | "-" - # Parameter [mm] controlling capillary rise - cap_hmax::Vector{T} | "mm" - # Coefficient [-] controlling capillary rise - cap_n::Vector{T} | "-" - # Crop coefficient Kc [-] - kc::Vector{T} | "-" - # Brooks-Corey power coefficient [-] for each soil layer - c::Vector{SVector{N,T}} | "-" - # Stemflow [mm Δt⁻¹] - stemflow::Vector{T} - # Throughfall [mm Δt⁻¹] - throughfall::Vector{T} - # A scaling parameter [mm⁻¹] (controls exponential decline of kv_0) - f::Vector{T} | "mm-1" - # Depth [mm] from soil surface for which exponential decline of kv_0 is valid - z_exp::Vector{T} | "mm" - # Depth [mm] from soil surface for which layered profile is valid - z_layered::Vector{T} | "mm" - # Amount of water in the unsaturated store, per layer [mm] - ustorelayerdepth::Vector{SVector{N,T}} | "mm" - # Saturated store [mm] - satwaterdepth::Vector{T} | "mm" - # Pseudo-water table depth [mm] (top of the saturated zone) - zi::Vector{T} | "mm" - # Soilwater capacity [mm] - soilwatercapacity::Vector{T} | "mm" - # Canopy storage [mm] - canopystorage::Vector{T} | "mm" - # Maximum canopy storage [mm] - cmax::Vector{T} | "mm" - # Canopy gap fraction [-] - canopygapfraction::Vector{T} | "-" - # Gash interception model parameter, ratio of the average evaporation from the - # wet canopy [mm Δt⁻¹] and the average precipitation intensity [mm Δt⁻¹] on a saturated canopy - e_r::Vector{T} | "-" - # Precipitation [mm Δt⁻¹] - precipitation::Vector{T} - # Temperature [ᵒC] - temperature::Vector{T} | "°C" - # Potential reference evapotranspiration [mm Δt⁻¹] - potential_evaporation::Vector{T} - # Potential transpiration (after subtracting interception from potential_evaporation) - pottrans::Vector{T} - # Transpiration [mm Δt⁻¹] - transpiration::Vector{T} - # Actual evaporation from unsaturated store [mm Δt⁻¹] - ae_ustore::Vector{T} - # Interception loss by evaporation [mm Δt⁻¹] - interception::Vector{T} - # Soil evaporation from unsaturated and saturated store [mm Δt⁻¹] - soilevap::Vector{T} - # Soil evaporation from saturated store [mm Δt⁻¹] - soilevapsat::Vector{T} - # Actual capillary rise [mm Δt⁻¹] - actcapflux::Vector{T} - # Actual transpiration from saturated store [mm Δt⁻¹] - actevapsat::Vector{T} - # Total actual evapotranspiration [mm Δt⁻¹] - actevap::Vector{T} - # Runoff from river based on riverfrac [mm Δt⁻¹] - runoff_river::Vector{T} - # Runoff from land based on waterfrac [mm Δt⁻¹] - runoff_land::Vector{T} - # Actual evaporation from open water (land) [mm Δt⁻¹] - ae_openw_l::Vector{T} - # Actual evaporation from river [mm Δt⁻¹] - ae_openw_r::Vector{T} - # Net runoff from river [mm Δt⁻¹] - net_runoff_river::Vector{T} - # Water available for infiltration [mm Δt⁻¹] - avail_forinfilt::Vector{T} - # Actual infiltration into the unsaturated zone [mm Δt⁻¹] - actinfilt::Vector{T} - # Actual infiltration non-compacted fraction [mm Δt⁻¹] - actinfiltsoil::Vector{T} - # Actual infiltration compacted fraction [mm Δt⁻¹] - actinfiltpath::Vector{T} - # Actual infiltration (compacted and the non-compacted areas) [mm Δt⁻¹] - infiltsoilpath::Vector{T} - # Infiltration excess water [mm Δt⁻¹] - infiltexcess::Vector{T} - # Water that cannot infiltrate due to saturated soil (saturation excess) [mm Δt⁻¹] - excesswater::Vector{T} - # Water exfiltrating during saturation excess conditions [mm Δt⁻¹] - exfiltsatwater::Vector{T} - # Water exfiltrating from unsaturated store because of change in water table [mm Δt⁻¹] - exfiltustore::Vector{T} - # Excess water for non-compacted fraction [mm Δt⁻¹] - excesswatersoil::Vector{T} - # Excess water for compacted fraction [mm Δt⁻¹] - excesswaterpath::Vector{T} - # Total surface runoff from infiltration and saturation excess (excluding actual open water evaporation) [mm Δt⁻¹] - runoff::Vector{T} - # Net surface runoff (surface runoff - actual open water evaporation) [mm Δt⁻¹] - net_runoff::Vector{T} - # Volumetric water content [-] per soil layer (including theta_r and saturated zone) - vwc::Vector{SVector{N,T}} | "-" - # Volumetric water content [%] per soil layer (including theta_r and saturated zone) - vwc_perc::Vector{SVector{N,T}} | "%" - # Root water storage [mm] in unsaturated and saturated zone (excluding theta_r) - rootstore::Vector{T} | "mm" - # Volumetric water content [-] in root zone (including theta_r and saturated zone) - vwc_root::Vector{T} | "-" - # Volumetric water content [%] in root zone (including theta_r and saturated zone) - vwc_percroot::Vector{T} | "%" - # Amount of available water in the unsaturated zone [mm] - ustoredepth::Vector{T} | "mm" - # Downward flux from unsaturated to saturated zone [mm Δt⁻¹] - transfer::Vector{T} - # Net recharge to saturated store [mm Δt⁻¹] - recharge::Vector{T} - # Actual leakage from saturated store [mm Δt⁻¹] - actleakage::Vector{T} - ### Snow parameters ### - # Degree-day factor [mm ᵒC⁻¹ Δt⁻¹] - cfmax::Vector{T} | "mm ᵒC-1 dt-1" - # Threshold temperature for snowfall [ᵒC] - tt::Vector{T} | "ᵒC" - # Threshold temperature interval length [ᵒC] - tti::Vector{T} | "ᵒC" - # Threshold temperature for snowmelt [ᵒC] - ttm::Vector{T} | "ᵒC" - # Water holding capacity as fraction of current snow pack [-] - whc::Vector{T} | "-" - # Soil temperature smooth factor [-] - w_soil::Vector{T} | "-" - # Controls soil infiltration reduction factor when soil is frozen [-] - cf_soil::Vector{T} | "-" - # Snow storage [mm] - snow::Vector{T} | "mm" - # Liquid water content in the snow pack [mm] - snowwater::Vector{T} | "mm" - # Snow melt + precipitation as rainfall [mm] - rainfallplusmelt::Vector{T} | "mm" - # Threshold temperature for snowfall above glacier [ᵒC] - g_tt::Vector{T} | "ᵒC" - # Degree-day factor [mm ᵒC⁻¹ Δt⁻¹] for glacier - g_cfmax::Vector{T} | "mm ᵒC-1 dt-1" - # Fraction of the snowpack on top of the glacier converted into ice [Δt⁻¹] - g_sifrac::Vector{T} | "dt-1" - # Water within the glacier [mm] - glacierstore::Vector{T} | "mm" - # Fraction covered by a glacier [-] - glacierfrac::Vector{T} | "-" - # Top soil temperature [ᵒC] - tsoil::Vector{T} | "ᵒC" - ## Interception related to leaf_area_index climatology ### - # Specific leaf storage [mm] - sl::Vector{T} | "mm" - # Storage woody part of vegetation [mm] - swood::Vector{T} | "mm" - # Extinction coefficient [-] (to calculate canopy gap fraction) - kext::Vector{T} | "-" - # Leaf area index [m² m⁻²] - leaf_area_index::Vector{T} | "m2 m-2" - # Water level land [mm] - waterlevel_land::Vector{T} | "mm" - # Water level river [mm] - waterlevel_river::Vector{T} | "mm" - # Total water storage (excluding floodplain volume, lakes and reservoirs) [mm] - total_storage::Vector{T} | "mm" - # Water demand structs (of arrays) - paddy::P | "-" | 0 - nonpaddy::NP | "-" | 0 - domestic::D | "-" | 0 - livestock::L | "-" | 0 - industry::I | "-" | 0 - allocation::A | "-" | 0 - - - function SBM{T,N,M,P,NP,D,L,I,A}(args...) where {T,N,M,P,NP,D,L,I,A} - equal_size_vectors(args) - return new(args...) - end +"Land hydrology model with SBM soil model" +@with_kw struct LandHydrologySBM{T, D, A} + atmospheric_forcing::AtmosphericForcing{T} + vegetation_parameter_set::VegetationParameters{T} + interception::AbstractInterceptionModel{T} + snow::AbstractSnowModel{T} + glacier::AbstractGlacierModel{T} + runoff::AbstractRunoffModel{T} + soil::SbmSoilModel + demand::D + allocation::A + dt::T end - -function initialize_canopy(nc, config, inds) +"Initialize land hydrology model with SBM soil model" +function LandHydrologySBM(nc, config, riverfrac, inds) + dt = Second(config.timestepsecs) n = length(inds) - # if leaf area index climatology provided use sl, swood and kext to calculate cmax, e_r and canopygapfraction - if haskey(config.input.vertical, "leaf_area_index") - # TODO confirm if leaf area index climatology is present in the netCDF - sl = ncread( - nc, - config, - "vertical.specific_leaf"; - optional = false, - sel = inds, - type = Float, - ) - swood = ncread( - nc, - config, - "vertical.storage_wood"; - optional = false, - sel = inds, - type = Float, - ) - kext = - ncread(nc, config, "vertical.kext"; optional = false, sel = inds, type = Float) - cmax = fill(mv, n) - e_r = fill(mv, n) - canopygapfraction = fill(mv, n) - else - sl = fill(mv, n) - swood = fill(mv, n) - kext = fill(mv, n) - # cmax, e_r, canopygapfraction only required when leaf area index climatology not provided - cmax = ncread(nc, config, "vertical.cmax"; sel = inds, defaults = 1.0, type = Float) - e_r = - ncread(nc, config, "vertical.eoverr"; sel = inds, defaults = 0.1, type = Float) - canopygapfraction = ncread( - nc, - config, - "vertical.canopygapfraction"; - sel = inds, - defaults = 0.1, - type = Float, - ) - end - return cmax, e_r, canopygapfraction, sl, swood, kext -end - -function initialize_sbm(nc, config, riverfrac, inds) - dt = Second(config.timestepsecs) - config_thicknesslayers = get(config.model, "thicknesslayers", Float[]) - ksat_profile = get(config.input.vertical, "ksat_profile", "exponential")::String - if length(config_thicknesslayers) > 0 - thicknesslayers = SVector(Tuple(push!(Float.(config_thicknesslayers), mv))) - sumlayers = pushfirst(cumsum(thicknesslayers), 0.0) - maxlayers = length(thicknesslayers) # max number of soil layers + atmospheric_forcing = AtmosphericForcing(n) + vegetation_parameter_set = VegetationParameters(nc, config, inds) + if dt >= Hour(23) + interception_model = + GashInterceptionModel(nc, config, inds, vegetation_parameter_set) else - maxlayers = 1 + interception_model = RutterInterceptionModel(vegetation_parameter_set, n) end - n = length(inds) - - cfmax = - ncread( - nc, - config, - "vertical.cfmax"; - sel = inds, - defaults = 3.75653, - type = Float, - ) .* (dt / basetimestep) - tt = ncread(nc, config, "vertical.tt"; sel = inds, defaults = 0.0, type = Float) - tti = ncread(nc, config, "vertical.tti"; sel = inds, defaults = 1.0, type = Float) - ttm = ncread(nc, config, "vertical.ttm"; sel = inds, defaults = 0.0, type = Float) - whc = ncread(nc, config, "vertical.whc"; sel = inds, defaults = 0.1, type = Float) - w_soil = - ncread( - nc, - config, - "vertical.w_soil"; - sel = inds, - defaults = 0.1125, - type = Float, - ) .* (dt / basetimestep) - cf_soil = - ncread(nc, config, "vertical.cf_soil"; sel = inds, defaults = 0.038, type = Float) - # glacier parameters - g_tt = ncread( - nc, - config, - "vertical.g_tt"; - sel = inds, - defaults = 0.0, - type = Float, - fill = 0.0, - ) - g_cfmax = - ncread( - nc, - config, - "vertical.g_cfmax"; - sel = inds, - defaults = 3.0, - type = Float, - fill = 0.0, - ) .* (dt / basetimestep) - g_sifrac = - ncread( - nc, - config, - "vertical.g_sifrac"; - sel = inds, - defaults = 0.001, - type = Float, - fill = 0.0, - ) .* (dt / basetimestep) - glacierfrac = ncread( - nc, - config, - "vertical.glacierfrac"; - sel = inds, - defaults = 0.0, - type = Float, - fill = 0.0, - ) - glacierstore = ncread( - nc, - config, - "vertical.glacierstore"; - sel = inds, - defaults = 5500.0, - type = Float, - fill = 0.0, - ) - # soil parameters - theta_s = - ncread(nc, config, "vertical.theta_s"; sel = inds, defaults = 0.6, type = Float) - theta_r = - ncread(nc, config, "vertical.theta_r"; sel = inds, defaults = 0.01, type = Float) - kv_0 = - ncread(nc, config, "vertical.kv_0"; sel = inds, defaults = 3000.0, type = Float) .* - (dt / basetimestep) - f = ncread(nc, config, "vertical.f"; sel = inds, defaults = 0.001, type = Float) - hb = ncread(nc, config, "vertical.hb"; sel = inds, defaults = -10.0, type = Float) - h1 = ncread(nc, config, "vertical.h1"; sel = inds, defaults = 0.0, type = Float) - h2 = ncread(nc, config, "vertical.h2"; sel = inds, defaults = -100.0, type = Float) - h3_high = - ncread(nc, config, "vertical.h3_high"; sel = inds, defaults = -400.0, type = Float) - h3_low = - ncread(nc, config, "vertical.h3_low"; sel = inds, defaults = -1000.0, type = Float) - h4 = ncread(nc, config, "vertical.h4"; sel = inds, defaults = -15849.0, type = Float) - alpha_h1 = - ncread(nc, config, "vertical.alpha_h1"; sel = inds, defaults = 1.0, type = Float) - soilthickness = ncread( - nc, - config, - "vertical.soilthickness"; - sel = inds, - defaults = 2000.0, - type = Float, - ) - infiltcappath = - ncread( - nc, - config, - "vertical.infiltcappath"; - sel = inds, - defaults = 10.0, - type = Float, - ) .* (dt / basetimestep) - infiltcapsoil = - ncread( - nc, - config, - "vertical.infiltcapsoil"; - sel = inds, - defaults = 100.0, - type = Float, - ) .* (dt / basetimestep) - maxleakage = - ncread( - nc, - config, - "vertical.maxleakage"; - sel = inds, - defaults = 0.0, - type = Float, - ) .* (dt / basetimestep) - - c = ncread( - nc, - config, - "vertical.c"; - sel = inds, - defaults = 10.0, - type = Float, - dimname = :layer, - ) - if size(c, 1) != maxlayers - parname = param(config.input.vertical, "c") - size1 = size(c, 1) - error("$parname needs a layer dimension of size $maxlayers, but is $size1") - end - kvfrac = ncread( - nc, - config, - "vertical.kvfrac"; - sel = inds, - defaults = 1.0, - type = Float, - dimname = :layer, - ) - if size(kvfrac, 1) != maxlayers - parname = param(config.input, "vertical.kvfrac") - size1 = size(kvfrac, 1) - error("$parname needs a layer dimension of size $maxlayers, but is $size1") + modelsnow = get(config.model, "snow", false)::Bool + if modelsnow + snow_model = SnowHbvModel(nc, config, inds, dt) + else + snow_model = NoSnowModel{Float}() end - - # fraction open water and compacted area (land cover) - waterfrac = - ncread(nc, config, "vertical.waterfrac"; sel = inds, defaults = 0.0, type = Float) - pathfrac = - ncread(nc, config, "vertical.pathfrac"; sel = inds, defaults = 0.01, type = Float) - - # vegetation parameters - rootingdepth = ncread( - nc, - config, - "vertical.rootingdepth"; - sel = inds, - defaults = 750.0, - type = Float, - ) - # correct rooting depth for soilthickness - rootingdepth = @. min(0.99 * soilthickness, rootingdepth) - rootdistpar = ncread( - nc, - config, - "vertical.rootdistpar"; - sel = inds, - defaults = -500.0, - type = Float, - ) - cap_hmax = - ncread(nc, config, "vertical.cap_hmax"; sel = inds, defaults = 2000.0, type = Float) - cap_n = ncread(nc, config, "vertical.cap_n"; sel = inds, defaults = 2.0, type = Float) - kc = ncread( - nc, - config, - "vertical.kc"; - alias = "vertical.et_reftopot", - sel = inds, - defaults = 1.0, - type = Float, - ) - if haskey(config.input.vertical, "et_reftopot") + modelglacier = get(config.model, "glacier", false)::Bool + if modelsnow && modelglacier + glacier_bc = SnowStateBC{Float}(; snow_storage = snow_model.variables.snow_storage) + glacier_model = GlacierHbvModel(nc, config, inds, dt, glacier_bc) + elseif modelsnow == false && modelglacier == true @warn string( - "The `et_reftopot` key in `[input.vertical]` is now called ", - "`kc`. Please update your TOML file.", + "Glacier processes can be modelled when snow modelling is enabled. To include ", + "glacier modelling, set `snow` to `true` in the Model section of the TOML file.", ) - end - - cmax, e_r, canopygapfraction, sl, swood, kext = initialize_canopy(nc, config, inds) - - theta_e = theta_s .- theta_r - soilwatercapacity = soilthickness .* theta_e - satwaterdepth = 0.85 .* soilwatercapacity # cold state value for satwaterdepth - zi = max.(0.0, soilthickness .- satwaterdepth ./ theta_e) # cold state value for zi - - # these are filled in the loop below - # TODO see if we can replace this approach - nlayers = zeros(Int, n) - n_unsatlayers = zeros(Int, n) - act_thickl = zeros(Float, maxlayers, n) - s_layers = zeros(Float, maxlayers + 1, n) - - for i = 1:n - if length(config_thicknesslayers) > 0 - act_thickl_, nlayers_ = - set_layerthickness(soilthickness[i], sumlayers, thicknesslayers) - s_layers_ = pushfirst(cumsum(act_thickl_), 0.0) - - nlayers[i] = nlayers_ - act_thickl[:, i] = act_thickl_ - s_layers[:, i] = s_layers_ - _, n_unsatlayers[i] = set_layerthickness(zi[i], sumlayers, thicknesslayers) - else - nlayers[i] = 1 - act_thickl[:, i] = SVector(soilthickness[i]) - s_layers[:, i] = pushfirst(cumsum(SVector(soilthickness[i])), 0.0) - end - end - - if length(config_thicknesslayers) > 0 - # root fraction read from nc file, in case of multiple soil layers and TOML file - # includes "vertical.rootfraction" - if haskey(config.input.vertical, "rootfraction") - rootfraction = ncread( - nc, - config, - "vertical.rootfraction"; - sel = inds, - optional = false, - type = Float, - dimname = :layer, - ) - else - # default root fraction in case of multiple soil layers - rootfraction = zeros(Float, maxlayers, n) - for i = 1:n - if rootingdepth[i] > 0.0 - for k = 1:maxlayers - if (rootingdepth[i] - s_layers[k, i]) >= act_thickl[k, i] - rootfraction[k, i] = act_thickl[k, i] / rootingdepth[i] - else - rootfraction[k, i] = - max(rootingdepth[i] - s_layers[k, i], 0.0) / rootingdepth[i] - end - end - end - end - end + glacier_model = NoGlacierModel{Float}() else - # for the case of 1 soil layer - rootfraction = ones(Float, maxlayers, n) + glacier_model = NoGlacierModel{Float}() end + runoff_model = OpenWaterRunoff(nc, config, inds, riverfrac) - # needed for derived parameters below - act_thickl = svectorscopy(act_thickl, Val{maxlayers}()) - - # copied to array of sarray below - vwc = fill(mv, maxlayers, n) - vwc_perc = fill(mv, maxlayers, n) - sumlayers = svectorscopy(s_layers, Val{maxlayers + 1}()) - - # ksat profiles - if ksat_profile == "exponential" - z_exp = soilthickness - z_layered = fill(mv, n) - kv = fill(mv, (maxlayers, n)) - nlayers_kv = fill(0, n) - elseif ksat_profile == "exponential_constant" - z_exp = - ncread(nc, config, "vertical.z_exp"; optional = false, sel = inds, type = Float) - z_layered = fill(mv, n) - kv = fill(mv, (maxlayers, n)) - nlayers_kv = fill(0, n) - elseif ksat_profile == "layered" || ksat_profile == "layered_exponential" - z_exp = fill(mv, n) - kv = - ncread( - nc, - config, - "vertical.kv"; - sel = inds, - defaults = 1000.0, - type = Float, - dimname = :layer, - ) .* (dt / basetimestep) - if size(kv, 1) != maxlayers - parname = param(config.input.vertical, "kv") - size1 = size(kv, 1) - error("$parname needs a layer dimension of size $maxlayers, but is $size1") - end - if ksat_profile == "layered" - z_layered = soilthickness - nlayers_kv = nlayers - else - z_layered = ncread( - nc, - config, - "vertical.z_layered"; - optional = false, - sel = inds, - type = Float, - ) - nlayers_kv = fill(0, n) - for i in eachindex(nlayers_kv) - layers = @view sumlayers[i][2:nlayers[i]] - _, k = findmin(abs.(z_layered[i] .- layers)) - nlayers_kv[i] = k - z_layered[i] = layers[k] - end - end - else - error("""An unknown "ksat_profile" is specified in the TOML file ($ksat_profile). - This should be "exponential", "exponential_constant", "layered" or - "layered_exponential". - """) - end + soil_model = SbmSoilModel(nc, config, vegetation_parameter_set, inds, dt) + @. vegetation_parameter_set.rootingdepth = min( + soil_model.parameters.soilthickness * 0.99, + vegetation_parameter_set.rootingdepth, + ) - # water demand and irrigation options do_water_demand = haskey(config.model, "water_demand") - domestic = do_water_demand ? get(config.model.water_demand, "domestic", false) : false - industry = do_water_demand ? get(config.model.water_demand, "industry", false) : false - livestock = do_water_demand ? get(config.model.water_demand, "livestock", false) : false - paddy = do_water_demand ? get(config.model.water_demand, "paddy", false) : false - nonpaddy = do_water_demand ? get(config.model.water_demand, "nonpaddy", false) : false - - sbm = SBM( + allocation = + do_water_demand ? AllocationLand(nc, config, inds) : NoAllocationLand{Float}() + demand = do_water_demand ? Demand(nc, config, inds, dt) : NoDemand{Float}() + + args = (demand, allocation) + land_hydrology_model = LandHydrologySBM{Float, typeof.(args)...}(; + atmospheric_forcing = atmospheric_forcing, + vegetation_parameter_set = vegetation_parameter_set, + interception = interception_model, + snow = snow_model, + glacier = glacier_model, + runoff = runoff_model, + soil = soil_model, + demand = demand, + allocation = allocation, dt = tosecond(dt), - maxlayers = maxlayers, - n = n, - nlayers = nlayers, - n_unsatlayers = n_unsatlayers, - nlayers_kv = nlayers_kv, - riverfrac = riverfrac, - theta_s = theta_s, - theta_r = theta_r, - kv_0 = kv_0, - kv = svectorscopy(kv, Val{maxlayers}()), - kvfrac = svectorscopy(kvfrac, Val{maxlayers}()), - hb = hb, - h1 = h1, - h2 = h2, - h3_high = h3_high, - h3_low = h3_low, - h4 = h4, - h3 = fill(mv, n), - alpha_h1 = alpha_h1, - soilthickness = soilthickness, - act_thickl = act_thickl, - sumlayers = sumlayers, - infiltcappath = infiltcappath, - infiltcapsoil = infiltcapsoil, - soilinfredu = fill(Float(1), n), - maxleakage = maxleakage, - waterfrac = max.(waterfrac .- riverfrac, Float(0.0)), - pathfrac = pathfrac, - rootingdepth = rootingdepth, - rootfraction = svectorscopy(rootfraction, Val{maxlayers}()), - rootdistpar = rootdistpar, - cap_hmax = cap_hmax, - cap_n = cap_n, - kc = kc, - c = svectorscopy(c, Val{maxlayers}()), - stemflow = fill(mv, n), - throughfall = fill(mv, n), - f = f, - z_exp = z_exp, - z_layered = z_layered, - ustorelayerdepth = zero(act_thickl), - satwaterdepth = satwaterdepth, - zi = zi, - soilwatercapacity = soilwatercapacity, - canopystorage = zeros(Float, n), - cmax = cmax, - canopygapfraction = canopygapfraction, - e_r = e_r, - precipitation = fill(mv, n), - temperature = fill(mv, n), - potential_evaporation = fill(mv, n), - pottrans = fill(mv, n), - transpiration = fill(mv, n), - ae_ustore = fill(mv, n), - interception = fill(mv, n), - soilevap = fill(mv, n), - soilevapsat = fill(mv, n), - actcapflux = fill(mv, n), - actevapsat = fill(mv, n), - actevap = fill(mv, n), - runoff_river = fill(mv, n), - runoff_land = fill(mv, n), - ae_openw_l = fill(mv, n), - ae_openw_r = fill(mv, n), - avail_forinfilt = fill(mv, n), - actinfilt = fill(mv, n), - actinfiltsoil = fill(mv, n), - actinfiltpath = fill(mv, n), - infiltsoilpath = fill(mv, n), - infiltexcess = fill(mv, n), - excesswater = fill(mv, n), - exfiltsatwater = fill(mv, n), - exfiltustore = fill(mv, n), - excesswatersoil = fill(mv, n), - excesswaterpath = fill(mv, n), - runoff = fill(mv, n), - net_runoff = fill(mv, n), - net_runoff_river = fill(mv, n), - vwc = svectorscopy(vwc, Val{maxlayers}()), - vwc_perc = svectorscopy(vwc_perc, Val{maxlayers}()), - rootstore = fill(mv, n), - vwc_root = fill(mv, n), - vwc_percroot = fill(mv, n), - ustoredepth = fill(mv, n), - transfer = fill(mv, n), - recharge = fill(mv, n), - actleakage = fill(mv, n), - # snow parameters - cfmax = cfmax, - tt = tt, - tti = tti, - ttm = ttm, - whc = whc, - w_soil = w_soil, - cf_soil = cf_soil, - snow = zeros(Float, n), - snowwater = zeros(Float, n), - rainfallplusmelt = fill(mv, n), - tsoil = fill(Float(10.0), n), - # glacier parameters - g_tt = g_tt, - g_sifrac = g_sifrac, - g_cfmax = g_cfmax, - glacierstore = glacierstore, - glacierfrac = glacierfrac, - # Interception related to climatology (leaf_area_index) - sl = sl, - swood = swood, - kext = kext, - leaf_area_index = fill(mv, n), - # water level land and river domain - waterlevel_land = fill(mv, n), - waterlevel_river = zeros(Float, n), #set to zero to account for cells outside river domain - total_storage = zeros(Float, n), # Set the total water storage from initialized values - # water demand - paddy = paddy ? initialize_paddy(nc, config, inds, dt) : nothing, - nonpaddy = nonpaddy ? initialize_nonpaddy(nc, config, inds, dt) : nothing, - domestic = domestic ? initialize_domestic_demand(nc, config, inds, dt) : nothing, - industry = industry ? initialize_industry_demand(nc, config, inds, dt) : nothing, - livestock = livestock ? initialize_livestock_demand(nc, config, inds, dt) : nothing, - allocation = do_water_demand ? initialize_allocation_land(nc, config, inds) : - nothing, ) - - return sbm - -end - - -function update_until_snow(sbm::SBM, config) - - do_lai = haskey(config.input.vertical, "leaf_area_index") - modelglacier = get(config.model, "glacier", false)::Bool - modelsnow = get(config.model, "snow", false)::Bool - - threaded_foreach(1:sbm.n, basesize = 1000) do i - if do_lai - cmax = sbm.sl[i] * sbm.leaf_area_index[i] + sbm.swood[i] - canopygapfraction = exp(-sbm.kext[i] * sbm.leaf_area_index[i]) - canopyfraction = 1.0 - canopygapfraction - ewet = canopyfraction * sbm.potential_evaporation[i] * sbm.kc[i] - e_r = - sbm.precipitation[i] > 0.0 ? - min(0.25, ewet / max(0.0001, canopyfraction * sbm.precipitation[i])) : 0.0 - else - cmax = sbm.cmax[i] - canopygapfraction = sbm.canopygapfraction[i] - e_r = sbm.e_r[i] - end - - canopy_potevap = - sbm.kc[i] * sbm.potential_evaporation[i] * (1.0 - canopygapfraction) - if Second(sbm.dt) >= Hour(23) - throughfall, interception, stemflow, canopystorage = rainfall_interception_gash( - cmax, - e_r, - canopygapfraction, - sbm.precipitation[i], - sbm.canopystorage[i], - canopy_potevap, - ) - pottrans = max(0.0, canopy_potevap - interception) # now in mm - else - netinterception, throughfall, stemflow, leftover, interception, canopystorage = - rainfall_interception_modrut( - sbm.precipitation[i], - canopy_potevap, - sbm.canopystorage[i], - canopygapfraction, - cmax, - ) - pottrans = max(0.0, leftover) # now in mm - end - - if modelsnow - tsoil = sbm.tsoil[i] + sbm.w_soil[i] * (sbm.temperature[i] - sbm.tsoil[i]) - snow, snowwater, snowmelt, rainfallplusmelt, snowfall = snowpack_hbv( - sbm.snow[i], - sbm.snowwater[i], - throughfall + stemflow, - sbm.temperature[i], - sbm.tti[i], - sbm.tt[i], - sbm.ttm[i], - sbm.cfmax[i], - sbm.whc[i], - ) - end - - h3 = feddes_h3(sbm.h3_high[i], sbm.h3_low[i], pottrans, Second(sbm.dt)) - - # update the outputs and states - sbm.e_r[i] = e_r - sbm.cmax[i] = cmax - sbm.canopygapfraction[i] = canopygapfraction - sbm.canopystorage[i] = canopystorage - sbm.interception[i] = interception - sbm.stemflow[i] = stemflow - sbm.throughfall[i] = throughfall - sbm.pottrans[i] = pottrans - sbm.h3[i] = h3 - if modelsnow - sbm.snow[i] = snow - sbm.snowwater[i] = snowwater - sbm.tsoil[i] = tsoil - sbm.rainfallplusmelt[i] = rainfallplusmelt - end - end + return land_hydrology_model end -function update_until_recharge(sbm::SBM, config) - - # start dummy variables (should be generated from model reader and from Config.jl TOML) - soilinfreduction = get(config.model, "soilinfreduction", false)::Bool - modelglacier = get(config.model, "glacier", false)::Bool - modelsnow = get(config.model, "snow", false)::Bool - transfermethod = get(config.model, "transfermethod", false)::Bool - ust = get(config.model, "whole_ust_available", false)::Bool # should be removed from optional setting and code? - ksat_profile = get(config.input.vertical, "ksat_profile", "exponential")::String - - threaded_foreach(1:sbm.n, basesize = 250) do i - if modelsnow - rainfallplusmelt = sbm.rainfallplusmelt[i] - if modelglacier - # Run Glacier module and add the snowpack on-top of it. - # Estimate the fraction of snow turned into ice (HBV-light). - # Estimate glacier melt. - - snow, _, glacierstore, glaciermelt = glacier_hbv( - sbm.glacierfrac[i], - sbm.glacierstore[i], - sbm.snow[i], - sbm.temperature[i], - sbm.g_tt[i], - sbm.g_cfmax[i], - sbm.g_sifrac[i], - Second(sbm.dt), - ) - # Convert to mm per grid cell and add to snowmelt - glaciermelt = glaciermelt * sbm.glacierfrac[i] - rainfallplusmelt = rainfallplusmelt + glaciermelt - - end - else - rainfallplusmelt = sbm.stemflow[i] + sbm.throughfall[i] - end - - avail_forinfilt = rainfallplusmelt - ustoredepth = sum(@view sbm.ustorelayerdepth[i][1:sbm.nlayers[i]]) - - runoff_river = min(1.0, sbm.riverfrac[i]) * avail_forinfilt - runoff_land = min(1.0, sbm.waterfrac[i]) * avail_forinfilt - if !isnothing(sbm.paddy) || !isnothing(sbm.nonpaddy) - avail_forinfilt = avail_forinfilt + sbm.allocation.irri_alloc[i] - end - avail_forinfilt = max(avail_forinfilt - runoff_river - runoff_land, 0.0) - - ae_openw_r = min( - sbm.waterlevel_river[i] * sbm.riverfrac[i], - sbm.riverfrac[i] * sbm.potential_evaporation[i], - ) - ae_openw_l = min( - sbm.waterlevel_land[i] * sbm.waterfrac[i], - sbm.waterfrac[i] * sbm.potential_evaporation[i], - ) - - # evap available for soil evaporation - soilevap_fraction = max( - sbm.canopygapfraction[i] - sbm.riverfrac[i] - sbm.waterfrac[i] - - sbm.glacierfrac[i], - 0.0, - ) - potsoilevap = soilevap_fraction * sbm.potential_evaporation[i] - - if !isnothing(sbm.paddy) && sbm.paddy.irrigation_areas[i] - evap_paddy_water = min(sbm.paddy.h[i], potsoilevap) - sbm.paddy.h[i] -= evap_paddy_water - potsoilevap -= evap_paddy_water - avail_forinfilt += sbm.paddy.h[i] # allow infiltration of paddy water - else - evap_paddy_water = 0.0 - end - - # Calculate the initial capacity of the unsaturated store - ustorecapacity = sbm.soilwatercapacity[i] - sbm.satwaterdepth[i] - ustoredepth - - # Calculate the infiltration flux into the soil column - infiltsoilpath, - infiltsoil, - infiltpath, - soilinf, - pathinf, - infiltexcess, - soilinfredu = infiltration( - avail_forinfilt, - sbm.pathfrac[i], - sbm.cf_soil[i], - sbm.tsoil[i], - sbm.infiltcapsoil[i], - sbm.infiltcappath[i], - ustorecapacity, - modelsnow, - soilinfreduction, +"Update land hydrology model with SBM soil model for a single timestep" +function update!(model::LandHydrologySBM, lateral, network, config) + do_water_demand = haskey(config.model, "water_demand")::Bool + (; + glacier, + snow, + interception, + runoff, + soil, + demand, + allocation, + atmospheric_forcing, + dt, + ) = model + + update!(interception, atmospheric_forcing) + + update_boundary_conditions!(snow, (; interception)) + update!(snow, atmospheric_forcing) + + # lateral snow transport + if get(config.model, "masswasting", false)::Bool + lateral_snow_transport!( + snow.variables.snow_storage, + snow.variables.snow_water, + network.land.slope, + network.land, ) - - - usl, n_usl = set_layerthickness(sbm.zi[i], sbm.sumlayers[i], sbm.act_thickl[i]) - z = cumsum(usl) - usld = sbm.ustorelayerdepth[i] - - ast = 0.0 - soilevapunsat = 0.0 - if n_usl > 0 - # Using the surface infiltration rate, calculate the flow rate between the - # different soil layers that contain unsaturated storage assuming gravity - # based flow only, estimate the gravity based flux rate to the saturated zone - # (ast) and the updated unsaturated storage for each soil layer. - if transfermethod && sbm.maxlayers == 1 - ustorelayerdepth = sbm.ustorelayerdepth[i][1] + infiltsoilpath - kv_z = hydraulic_conductivity_at_depth(sbm, sbm.zi[i], i, 1, ksat_profile) - ustorelayerdepth, ast = unsatzone_flow_sbm( - ustorelayerdepth, - sbm.soilwatercapacity[i], - sbm.satwaterdepth[i], - kv_z, - usl[1], - sbm.theta_s[i], - sbm.theta_r[i], - ) - usld = setindex(usld, ustorelayerdepth, 1) - else - for m = 1:n_usl - l_sat = usl[m] * (sbm.theta_s[i] - sbm.theta_r[i]) - kv_z = hydraulic_conductivity_at_depth(sbm, z[m], i, m, ksat_profile) - ustorelayerdepth = - m == 1 ? sbm.ustorelayerdepth[i][m] + infiltsoilpath : - sbm.ustorelayerdepth[i][m] + ast - ustorelayerdepth, ast = - unsatzone_flow_layer(ustorelayerdepth, kv_z, l_sat, sbm.c[i][m]) - usld = setindex(usld, ustorelayerdepth, m) - end - end - - # then evapotranspiration from layers - # Calculate saturation deficit - saturationdeficit = sbm.soilwatercapacity[i] - sbm.satwaterdepth[i] - - # First calculate the evaporation of unsaturated storage into the - # atmosphere from the upper layer. - if sbm.maxlayers == 1 - soilevapunsat = - potsoilevap * min(1.0, saturationdeficit / sbm.soilwatercapacity[i]) - else - # In case only the most upper soil layer contains unsaturated storage - if n_usl == 1 - # Check if groundwater level lies below the surface - soilevapunsat = - potsoilevap * - min(1.0, usld[1] / (sbm.zi[i] * (sbm.theta_s[i] - sbm.theta_r[i]))) - else - # In case first layer contains no saturated storage - soilevapunsat = - potsoilevap * - min(1.0, usld[1] / (usl[1] * ((sbm.theta_s[i] - sbm.theta_r[i])))) - end - end - # Ensure that the unsaturated evaporation rate does not exceed the - # available unsaturated moisture - soilevapunsat = min(soilevapunsat, usld[1]) - # Update the additional atmospheric demand - potsoilevap = potsoilevap - soilevapunsat - usld = setindex(usld, usld[1] - soilevapunsat, 1) - end - transfer = ast - - if sbm.maxlayers == 1 - soilevapsat = 0.0 - else - if n_usl == 0 || n_usl == 1 - soilevapsat = - potsoilevap * - min(1.0, (sbm.act_thickl[i][1] - sbm.zi[i]) / sbm.act_thickl[i][1]) - soilevapsat = min( - soilevapsat, - (sbm.act_thickl[i][1] - sbm.zi[i]) * (sbm.theta_s[i] - sbm.theta_r[i]), - ) - else - soilevapsat = 0.0 - end - end - soilevap = soilevapunsat + soilevapsat - satwaterdepth = sbm.satwaterdepth[i] - soilevapsat - - # actual transpiration, first from ustore, potential transpiration is partitioned over - # depth based on the rootfraction - actevapustore = 0.0 - rootfraction_unsat = 0.0 - for k = 1:n_usl - vwc = max(usld[k] / usl[k], Float(0.0000001)) - head = head_brooks_corey( - vwc, - sbm.theta_s[i], - sbm.theta_r[i], - sbm.c[i][k], - sbm.hb[i], - ) - alpha = rwu_reduction_feddes( - head, - sbm.h1[i], - sbm.h2[i], - sbm.h3[i], - sbm.h4[i], - sbm.alpha_h1[i], - ) - # availcap is fraction of soil layer containing roots - # if `ust` is `true`, the whole unsaturated store is available for transpiration - if ust - availcap = usld[k] * 0.99 - else - availcap = - min(1.0, max(0.0, (sbm.rootingdepth[i] - sbm.sumlayers[i][k]) / usl[k])) - end - maxextr = usld[k] * availcap - # the rootfraction is valid for the root length in a soil layer, if zi decreases the root length - # the rootfraction needs to be adapted - if k == n_usl && sbm.zi[i] < sbm.rootingdepth[i] - rootlength = - min(sbm.act_thickl[i][k], sbm.rootingdepth[i] - sbm.sumlayers[i][k]) - rootfraction_act = sbm.rootfraction[i][k] * (usl[k] / rootlength) - else - rootfraction_act = sbm.rootfraction[i][k] - end - actevapustore_layer = min(alpha * rootfraction_act * sbm.pottrans[i], maxextr) - rootfraction_unsat = rootfraction_unsat + rootfraction_act - ustorelayerdepth = usld[k] - actevapustore_layer - actevapustore = actevapustore + actevapustore_layer - usld = setindex(usld, ustorelayerdepth, k) - end - - # transpiration from saturated store - wetroots = scurve(sbm.zi[i], sbm.rootingdepth[i], Float(1.0), sbm.rootdistpar[i]) - alpha = rwu_reduction_feddes( - Float(0.0), - sbm.h1[i], - sbm.h2[i], - sbm.h3[i], - sbm.h4[i], - sbm.alpha_h1[i], - ) - # include remaining root fraction if rooting depth is below water table zi - if sbm.zi[i] >= sbm.rootingdepth[i] - f_roots = wetroots - restevap = sbm.pottrans[i] - actevapustore - else - f_roots = wetroots * (1.0 - rootfraction_unsat) - restevap = sbm.pottrans[i] - end - actevapsat = min(restevap * f_roots * alpha, satwaterdepth) - satwaterdepth = satwaterdepth - actevapsat - - # check soil moisture balance per layer - du = 0.0 - for k = n_usl:-1:1 - du = max(0.0, usld[k] - usl[k] * (sbm.theta_s[i] - sbm.theta_r[i])) - usld = setindex(usld, usld[k] - du, k) - if k > 1 - usld = setindex(usld, usld[k-1] + du, k - 1) - end - end - - actinfilt = infiltsoilpath - du - excesswater = avail_forinfilt - infiltsoilpath - infiltexcess + du - - # Separation between compacted and non compacted areas (correction with the satflow du) - # This is required for D-Emission/Delwaq - if infiltsoil + infiltpath > 0.0 - actinfiltsoil = infiltsoil - du * infiltsoil / (infiltpath + infiltsoil) - actinfiltpath = infiltpath - du * infiltpath / (infiltpath + infiltsoil) - else - actinfiltsoil = 0.0 - actinfiltpath = 0.0 - end - excesswatersoil = max(soilinf - actinfiltsoil, 0.0) - excesswaterpath = max(pathinf - actinfiltpath, 0.0) - - actcapflux = 0.0 - if n_usl > 0 - ksat = hydraulic_conductivity_at_depth(sbm, sbm.zi[i], i, n_usl, ksat_profile) - ustorecapacity = - sbm.soilwatercapacity[i] - satwaterdepth - sum(@view usld[1:sbm.nlayers[i]]) - maxcapflux = max(0.0, min(ksat, actevapustore, ustorecapacity, satwaterdepth)) - - if sbm.zi[i] > sbm.rootingdepth[i] - capflux = - maxcapflux * pow( - 1.0 - min(sbm.zi[i], sbm.cap_hmax[i]) / (sbm.cap_hmax[i]), - sbm.cap_n[i], - ) - else - capflux = 0.0 - end - - netcapflux = capflux - for k = n_usl:-1:1 - toadd = min( - netcapflux, - max(usl[k] * (sbm.theta_s[i] - sbm.theta_r[i]) - usld[k], 0.0), - ) - usld = setindex(usld, usld[k] + toadd, k) - netcapflux = netcapflux - toadd - actcapflux = actcapflux + toadd - end - end - deepksat = hydraulic_conductivity_at_depth( - sbm, - sbm.soilthickness[i], - i, - sbm.nlayers[i], - ksat_profile, - ) - deeptransfer = min(satwaterdepth, deepksat) - actleakage = max(0.0, min(sbm.maxleakage[i], deeptransfer)) - - # recharge (mm) for saturated zone - recharge = (transfer - actcapflux - actleakage - actevapsat - soilevapsat) - transpiration = actevapsat + actevapustore - actevap = - soilevap + - transpiration + - ae_openw_r + - ae_openw_l + - sbm.interception[i] + - evap_paddy_water - - # update the outputs and states - sbm.n_unsatlayers[i] = n_usl - sbm.net_runoff_river[i] = runoff_river - ae_openw_r - sbm.avail_forinfilt[i] = avail_forinfilt - sbm.actinfilt[i] = actinfilt - sbm.infiltexcess[i] = infiltexcess - sbm.recharge[i] = recharge - sbm.transpiration[i] = transpiration - sbm.soilevap[i] = soilevap - sbm.soilevapsat[i] = soilevapsat - sbm.ae_openw_r[i] = ae_openw_r - sbm.ae_openw_l[i] = ae_openw_l - sbm.runoff_land[i] = runoff_land - sbm.runoff_river[i] = runoff_river - sbm.actevapsat[i] = actevapsat - sbm.actevap[i] = actevap - sbm.ae_ustore[i] = actevapustore - sbm.ustorelayerdepth[i] = usld - sbm.transfer[i] = transfer - sbm.actcapflux[i] = actcapflux - sbm.actleakage[i] = actleakage - sbm.actinfiltsoil[i] = actinfiltsoil - sbm.actinfiltpath[i] = actinfiltpath - sbm.excesswater[i] = excesswater - sbm.excesswatersoil[i] = excesswatersoil - sbm.excesswaterpath[i] = excesswaterpath - sbm.rainfallplusmelt[i] = rainfallplusmelt - sbm.infiltsoilpath[i] = infiltsoilpath - sbm.satwaterdepth[i] = satwaterdepth - sbm.soilinfredu[i] = soilinfredu - if modelsnow - if modelglacier - sbm.snow[i] = snow - sbm.glacierstore[i] = glacierstore - end - end end -end - -function update_after_subsurfaceflow(sbm::SBM, zi, exfiltsatwater) - - threaded_foreach(1:sbm.n, basesize = 1000) do i - usl, n_usl = set_layerthickness(zi[i], sbm.sumlayers[i], sbm.act_thickl[i]) - # exfiltration from ustore - usld = sbm.ustorelayerdepth[i] - exfiltustore = 0.0 - for k = sbm.n_unsatlayers[i]:-1:1 - if k <= n_usl - exfiltustore = max(0, usld[k] - usl[k] * (sbm.theta_s[i] - sbm.theta_r[i])) - else - exfiltustore = usld[k] - end - usld = setindex(usld, usld[k] - exfiltustore, k) - if k > 1 - usld = setindex(usld, usld[k-1] + exfiltustore, k - 1) - end - end - - ustoredepth = sum(@view usld[1:n_usl]) - if !isnothing(sbm.paddy) && sbm.paddy.irrigation_areas[i] - paddy_h_add = - exfiltustore + - exfiltsatwater[i] + - sbm.excesswater[i] + - sbm.runoff_land[i] + - sbm.infiltexcess[i] - runoff = max(paddy_h_add - sbm.paddy.h_max[i], 0.0) - sbm.paddy.h[i] = paddy_h_add - runoff - else - runoff = - exfiltustore + - exfiltsatwater[i] + - sbm.excesswater[i] + - sbm.runoff_land[i] + - sbm.infiltexcess[i] - end + update!(glacier, atmospheric_forcing) - # volumetric water content per soil layer and root zone - vwc = sbm.vwc[i] - vwc_perc = sbm.vwc_perc[i] - for k = 1:sbm.nlayers[i] - if k <= n_usl - vwc = setindex( - vwc, - ( - usld[k] + - (sbm.act_thickl[i][k] - usl[k]) * (sbm.theta_s[i] - sbm.theta_r[i]) - ) / sbm.act_thickl[i][k] + sbm.theta_r[i], - k, - ) - else - vwc = setindex(vwc, sbm.theta_s[i], k) - end - vwc_perc = setindex(vwc_perc, (vwc[k] / sbm.theta_s[i]) * 100.0, k) - end + update_boundary_conditions!(runoff, (; glacier, snow, interception), lateral, network) + update!(runoff, atmospheric_forcing) - rootstore_unsat = 0 - for k = 1:n_usl - rootstore_unsat = - rootstore_unsat + - min(1.0, (max(0.0, sbm.rootingdepth[i] - sbm.sumlayers[i][k]) / usl[k])) * - usld[k] - end - - rootstore_sat = - max(0.0, sbm.rootingdepth[i] - zi[i]) * (sbm.theta_s[i] - sbm.theta_r[i]) - rootstore = rootstore_sat + rootstore_unsat - vwc_root = rootstore / sbm.rootingdepth[i] + sbm.theta_r[i] - vwc_percroot = (vwc_root / sbm.theta_s[i]) * 100.0 - - satwaterdepth = (sbm.soilthickness[i] - zi[i]) * (sbm.theta_s[i] - sbm.theta_r[i]) - - # update the outputs and states - sbm.n_unsatlayers[i] = n_usl - sbm.ustorelayerdepth[i] = usld - sbm.ustoredepth[i] = ustoredepth - sbm.satwaterdepth[i] = satwaterdepth - sbm.exfiltsatwater[i] = exfiltsatwater[i] - sbm.exfiltustore[i] = exfiltustore - sbm.runoff[i] = runoff - sbm.net_runoff[i] = runoff - sbm.ae_openw_l[i] - sbm.vwc[i] = vwc - sbm.vwc_perc[i] = vwc_perc - sbm.rootstore[i] = rootstore - sbm.vwc_root[i] = vwc_root - sbm.vwc_percroot[i] = vwc_percroot - sbm.zi[i] = zi[i] + if do_water_demand + (; potential_transpiration) = soil.boundary_conditions + (; h3_high, h3_low) = soil.parameters + potential_transpiration .= get_potential_transpiration(interception) + @. soil.variables.h3 = feddes_h3(h3_high, h3_low, potential_transpiration, dt) end + update_water_demand!(demand, soil) + update_water_allocation!(allocation, demand, lateral, network, dt) + + soil_fraction!(soil, runoff, glacier) + update_boundary_conditions!( + soil, + atmospheric_forcing, + (; interception, runoff, demand, allocation), + ) + + update!(soil, atmospheric_forcing, (; snow, runoff, demand), config, dt) + @. soil.variables.actevap += interception.variables.interception_rate + return nothing end """ Update the total water storage per cell at the end of a timestep. Takes the following parameters: -- sbm: - The vertical concept (SBM struct) +- model: + The land hydrology model with the SBM soil model `LandHydrologySBM` - river_network: The indices of the river cells in relation to the active cells, i.e. model.network.index_river - area: @@ -1330,146 +145,44 @@ Takes the following parameters: - land_routing: The land routing struct, i.e. model.lateral.land """ -function update_total_water_storage( - sbm::SBM, +function update_total_water_storage!( + model::LandHydrologySBM, river_network, area, river_routing, land_routing, ) + (; interception, snow, glacier, runoff, soil, demand) = model + (; total_storage, ustoredepth, satwaterdepth) = soil.variables + (; riverfrac) = runoff.parameters + # Set the total storage to zero - fill!(sbm.total_storage, 0) + fill!(total_storage, 0) # Burn the river routing values for (i, index_river) in enumerate(river_network) - sbm.total_storage[index_river] = ( + total_storage[index_river] = ( (river_routing.h_av[i] * river_routing.width[i] * river_routing.dl[i]) / (area[index_river]) * 1000 # Convert to mm ) end - # Chunk the data for parallel computing - threaded_foreach(1:sbm.n, basesize = 1000) do i + # Add storage from interception, snow and glacier models + total_storage .+= + get_snow_storage(snow) .+ get_snow_water(snow) .+ + get_glacier_store(glacier) .* get_glacier_fraction(glacier) .+ + interception.variables.canopy_storage .+ get_water_depth(demand.paddy) - # Paddy water depth - paddy_h = isnothing(sbm.paddy) ? 0.0 : sbm.paddy.h[i] - - # Cumulate per vertical type - # Maybe re-categorize in the future - surface = ( - sbm.glacierstore[i] * sbm.glacierfrac[i] + - sbm.snow[i] + - sbm.snowwater[i] + - sbm.canopystorage[i] + - paddy_h - ) - sub_surface = sbm.ustoredepth[i] + sbm.satwaterdepth[i] + # Chunk the data for parallel computing + n = length(ustoredepth) + threaded_foreach(1:n; basesize = 1000) do i + sub_surface = ustoredepth[i] + satwaterdepth[i] lateral = ( - land_routing.h_av[i] * (1 - sbm.riverfrac[i]) * 1000 # convert to mm + land_routing.h_av[i] * (1 - riverfrac[i]) * 1000 # convert to mm ) # Add everything to the total water storage - sbm.total_storage[i] += (surface + sub_surface + lateral) - end -end - -""" - update_water_demand(sbm::SBM) - -Update water demand for vertical `SBM` concept for a single timestep. Water demand is -computed for sectors `industry`, `domestic` and `livestock`, and `paddy` rice fields and -`nonpaddy` (other crop) fields. - -Gross water demand for irrigation `irri_demand_gross` and non-irrigation -`nonirri_demand_gross`, and total gross water demand `total_gross_demand` are updated as -part of `SBM` water allocation (`allocation`) -""" -function update_water_demand(sbm::SBM) - for i = 1:sbm.n - - industry_dem = update_non_irrigation_demand(sbm.industry, i) - domestic_dem = update_non_irrigation_demand(sbm.domestic, i) - livestock_dem = update_non_irrigation_demand(sbm.livestock, i) - - irri_dem_gross = 0.0 - if !isnothing(sbm.nonpaddy) && sbm.nonpaddy.irrigation_areas[i] - if sbm.nonpaddy.irrigation_trigger[i] - usl, _ = set_layerthickness(sbm.zi[i], sbm.sumlayers[i], sbm.act_thickl[i]) - for k = 1:sbm.n_unsatlayers[i] - # compute water demand only for root zone through root fraction per layer - rootfrac = min( - 1.0, - (max(0.0, sbm.rootingdepth[i] - sbm.sumlayers[i][k]) / usl[k]), - ) - # vwc_f and vwc_h3 can be precalculated. - vwc_fc = vwc_brooks_corey( - -100.0, - sbm.hb[i], - sbm.theta_s[i], - sbm.theta_r[i], - sbm.c[i][k], - ) - vwc_h3 = vwc_brooks_corey( - sbm.h3[i], - sbm.hb[i], - sbm.theta_s[i], - sbm.theta_r[i], - sbm.c[i][k], - ) - depletion = - (vwc_fc * usl[k]) - - (sbm.ustorelayerdepth[i][k] + sbm.theta_r[i] * usl[k]) - depletion *= rootfrac - raw = (vwc_fc - vwc_h3) * usl[k] # readily available water - raw *= rootfrac - - # check if maximum irrigation rate has been applied at the previous time step. - max_irri_rate_applied = - sbm.nonpaddy.demand_gross[i] == - sbm.nonpaddy.maximum_irrigation_rate[i] - if depletion >= raw # start irrigation - irri_dem_gross += depletion - # add depletion to irrigation gross demand when the maximum irrigation rate has been - # applied at the previous time step (to get volumetric water content at field capacity) - elseif depletion > 0.0 && max_irri_rate_applied # continue irrigation - irri_dem_gross += depletion - end - end - # limit irrigation demand to infiltration capacity - infiltration_capacity = - sbm.soilinfredu[i] * (1.0 - sbm.pathfrac[i]) * sbm.infiltcapsoil[i] - irri_dem_gross = min(irri_dem_gross, infiltration_capacity) - irri_dem_gross /= sbm.nonpaddy.irrigation_efficiency[i] - # limit irrigation demand to the maximum irrigation rate - irri_dem_gross = - min(irri_dem_gross, sbm.nonpaddy.maximum_irrigation_rate[i]) - else - irri_dem_gross = 0.0 - end - sbm.nonpaddy.demand_gross[i] = irri_dem_gross - elseif !isnothing(sbm.paddy) && sbm.paddy.irrigation_areas[i] - if sbm.paddy.irrigation_trigger[i] - # check if maximum irrigation rate has been applied at the previous time step. - max_irri_rate_applied = - sbm.paddy.demand_gross[i] == sbm.paddy.maximum_irrigation_rate[i] - # start irrigation - if sbm.paddy.h[i] < sbm.paddy.h_min[i] - irr_depth_paddy = sbm.paddy.h_opt[i] - sbm.paddy.h[i] - elseif sbm.paddy.h[i] < sbm.paddy.h_opt[i] && max_irri_rate_applied # continue irrigation - irr_depth_paddy = sbm.paddy.h_opt[i] - sbm.paddy.h[i] - else - irr_depth_paddy = 0.0 - end - irri_dem_gross += irr_depth_paddy / sbm.paddy.irrigation_efficiency[i] - # limit irrigation demand to the maximum irrigation rate - irri_dem_gross = min(irri_dem_gross, sbm.paddy.maximum_irrigation_rate[i]) - end - sbm.paddy.demand_gross[i] = irri_dem_gross - end - # update gross water demands - sbm.allocation.irri_demand_gross[i] = irri_dem_gross - sbm.allocation.nonirri_demand_gross[i] = industry_dem + domestic_dem + livestock_dem - sbm.allocation.total_gross_demand[i] = - irri_dem_gross + industry_dem + domestic_dem + livestock_dem + total_storage[i] += (sub_surface + lateral) end -end + return nothing +end \ No newline at end of file diff --git a/src/sbm_gwf_model.jl b/src/sbm_gwf_model.jl index 2e4913c83..c8b52595d 100644 --- a/src/sbm_gwf_model.jl +++ b/src/sbm_gwf_model.jl @@ -2,7 +2,7 @@ initialize_sbm_gwf_model(config::Config) Initial part of the sbm_gwf model concept. The model contains: - - the vertical SBM concept + - the land hydrology model with the SBM soil model - unconfined aquifer with groundwater flow in four directions (adjacent cells) - the following surface routing options: - 1-D kinematic wave for river flow and 1-D kinematic wave for overland flow @@ -65,7 +65,7 @@ function initialize_sbm_gwf_model(config::Config) # read x, y coordinates and calculate cell length [m] y_nc = read_y_axis(nc) x_nc = read_x_axis(nc) - y = permutedims(repeat(y_nc, outer = (1, length(x_nc))))[inds] + y = permutedims(repeat(y_nc; outer = (1, length(x_nc))))[inds] cellength = abs(mean(diff(x_nc))) sizeinmetres = get(config.model, "sizeinmetres", false)::Bool @@ -76,7 +76,7 @@ function initialize_sbm_gwf_model(config::Config) nriv = length(inds_riv) # initialize vertical SBM concept - sbm = initialize_sbm(nc, config, riverfrac, inds) + lhm = LandHydrologySBM(nc, config, riverfrac, inds) # reservoirs pits = zeros(Bool, modelsize_2d) @@ -122,11 +122,11 @@ function initialize_sbm_gwf_model(config::Config) inds_allocation_areas = Vector{Int}[] inds_riv_allocation_areas = Vector{Int}[] if do_water_demand - areas = unique(sbm.allocation.areas) + areas = unique(lhm.allocation.parameters.areas) for a in areas - area_index = findall(x -> x == a, sbm.allocation.areas) + area_index = findall(==(a), lhm.allocation.parameters.areas) push!(inds_allocation_areas, area_index) - area_riv_index = findall(x -> x == a, sbm.allocation.areas[index_river]) + area_riv_index = findall(==(a), lhm.allocation.parameters.areas[index_river]) push!(inds_riv_allocation_areas, area_riv_index) end end @@ -240,14 +240,14 @@ function initialize_sbm_gwf_model(config::Config) get(config.input.lateral.subsurface, "conductivity_profile", "uniform")::String connectivity = Connectivity(inds, rev_inds, xl, yl) - initial_head = altitude .- sbm.zi / 1000.0 # cold state for groundwater head based on SBM zi + initial_head = altitude .- lhm.soil.variables.zi / 1000.0 # cold state for groundwater head based on SBM zi initial_head[index_river] = altitude[index_river] if do_constanthead initial_head[constant_head.index] = constant_head.head end - bottom = altitude .- sbm.soilthickness ./ Float(1000.0) + bottom = altitude .- lhm.soil.parameters.soilthickness ./ Float(1000.0) area = xl .* yl volume = @. (min(altitude, initial_head) - bottom) * area * specific_yield # total volume than can be released @@ -344,7 +344,12 @@ function initialize_sbm_gwf_model(config::Config) drain = () end - gwf = GroundwaterFlow(aquifer, connectivity, constant_head, aquifer_boundaries) + gwf = GroundwaterFlow{Float}(; + aquifer, + connectivity, + constanthead = constant_head, + boundaries = aquifer_boundaries, + ) # map GroundwaterFlow and its boundaries if do_drains @@ -391,7 +396,7 @@ function initialize_sbm_gwf_model(config::Config) end modelmap = - (vertical = sbm, lateral = (subsurface = subsurface_map, land = olf, river = rf)) + (vertical = lhm, lateral = (subsurface = subsurface_map, land = olf, river = rf)) indices_reverse = ( land = rev_inds, river = rev_inds_riv, @@ -405,8 +410,8 @@ function initialize_sbm_gwf_model(config::Config) indices_reverse, x_nc, y_nc, - nc, - extra_dim = (name = "layer", value = Float64.(1:sbm.maxlayers)), + nc; + extra_dim = (name = "layer", value = Float64.(1:(lhm.soil.parameters.maxlayers))), ) close(nc) @@ -511,55 +516,28 @@ function initialize_sbm_gwf_model(config::Config) config, (; land, river, reservoir, lake, drain, index_river, frac_toriver), (subsurface = subsurface_map, land = olf, river = rf), - sbm, + lhm, clock, reader, writer, SbmGwfModel(), ) - model = set_states(model) + set_states!(model) return model end - "update the sbm_gwf model for a single timestep" -function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmGwfModel} - (; lateral, vertical, network, config) = model +function update!(model::Model{N, L, V, R, W, T}) where {N, L, V, R, W, T <: SbmGwfModel} + (; lateral, vertical, network, clock, config) = model + (; soil, runoff, demand) = vertical do_water_demand = haskey(config.model, "water_demand") inds_riv = network.index_river aquifer = lateral.subsurface.flow.aquifer - constanthead = lateral.subsurface.flow.constanthead - - # extract water levels h_av [m] from the land and river domains - # this is used to limit open water evaporation - vertical.waterlevel_land .= lateral.land.h_av .* 1000.0 - vertical.waterlevel_river[inds_riv] .= lateral.river.h_av .* 1000.0 - - # vertical sbm concept is updated until snow state, after that (optional) - # snow transport is possible - update_until_snow(vertical, config) - - # lateral snow transport - if get(config.model, "masswasting", false)::Bool - lateral_snow_transport!( - vertical.snow, - vertical.snowwater, - network.land.slope, - network.land, - ) - end - # optional water demand and allocation - if do_water_demand - update_water_demand(vertical) - update_water_allocation(model) - end - - # update vertical sbm concept until recharge [mm] - update_until_recharge(vertical, config) + update!(vertical, lateral, network, config) # set river stage (groundwater) to average h from kinematic wave lateral.subsurface.river.stage .= lateral.river.h_av .+ lateral.subsurface.river.bottom @@ -571,40 +549,27 @@ function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmGwfModel} dt_sbm = (vertical.dt / tosecond(basetimestep)) # vertical.dt is in seconds (Float64) if dt_gw < dt_sbm @warn( - "stable time step dt $dt_gw for groundwater flow is smaller than sbm dt $dt_sbm" + "stable time step dt $dt_gw for groundwater flow is smaller than `LandHydrologySBM` model dt $dt_sbm" ) end - Q = zeros(vertical.n) - # exchange of recharge between vertical sbm concept and groundwater flow domain + Q = zeros(lateral.subsurface.flow.connectivity.ncell) + # exchange of recharge between SBM soil model and groundwater flow domain # recharge rate groundwater is required in units [m d⁻¹] - @. lateral.subsurface.recharge.rate = vertical.recharge / 1000.0 * (1.0 / dt_sbm) + @. lateral.subsurface.recharge.rate = soil.variables.recharge / 1000.0 * (1.0 / dt_sbm) if do_water_demand @. lateral.subsurface.recharge.rate -= - vertical.allocation.act_groundwater_abst / 1000.0 * (1.0 / dt_sbm) + vertical.allocation.variables.act_groundwater_abst / 1000.0 * (1.0 / dt_sbm) end # update groundwater domain - update(lateral.subsurface.flow, Q, dt_sbm, conductivity_profile) - - # determine excess water depth [m] (exfiltwater) in groundwater domain (head > surface) - # and reset head - exfiltwater = (aquifer.head .- min.(aquifer.head, aquifer.top)) .* storativity(aquifer) - aquifer.head .= min.(aquifer.head, aquifer.top) - - # Adjust for constant head boundary of groundwater domain - exfiltwater[constanthead.index] .= 0 - aquifer.head[constanthead.index] .= constanthead.head - - # update vertical sbm concept (runoff, ustorelayerdepth and satwaterdepth) - update_after_subsurfaceflow( - vertical, - (network.land.altitude .- aquifer.head) .* 1000.0, # zi [mm] in vertical concept SBM - exfiltwater .* 1000.0, - ) + update!(lateral.subsurface.flow, Q, dt_sbm, conductivity_profile) - ssf_toriver = zeros(vertical.n) + # update SBM soil model (runoff, ustorelayerdepth and satwaterdepth) + update!(soil, (; runoff, demand, subsurface = lateral.subsurface.flow)) + + ssf_toriver = zeros(length(soil.variables.zi)) ssf_toriver[inds_riv] = -lateral.subsurface.river.flux ./ lateral.river.dt - surface_routing(model, ssf_toriver = ssf_toriver) + surface_routing!(model; ssf_toriver = ssf_toriver) - return model + return nothing end diff --git a/src/sbm_model.jl b/src/sbm_model.jl index 0ad9054f0..cd14aa7dc 100644 --- a/src/sbm_model.jl +++ b/src/sbm_model.jl @@ -5,7 +5,6 @@ Initial part of the SBM model concept. Reads the input settings and data as defi Config object. Will return a Model that is ready to run. """ function initialize_sbm_model(config::Config) - model_type = config.model.type::String @info "Initialize model variables for model type `$model_type`." @@ -63,18 +62,18 @@ function initialize_sbm_model(config::Config) # read x, y coordinates and calculate cell length [m] y_nc = read_y_axis(nc) x_nc = read_x_axis(nc) - y = permutedims(repeat(y_nc, outer = (1, length(x_nc))))[inds] + y = permutedims(repeat(y_nc; outer = (1, length(x_nc))))[inds] cellength = abs(mean(diff(x_nc))) sizeinmetres = get(config.model, "sizeinmetres", false)::Bool xl, yl = cell_lengths(y, cellength, sizeinmetres) riverfrac = river_fraction(river, riverlength, riverwidth, xl, yl) + lhm = LandHydrologySBM(nc, config, riverfrac, inds) + inds_riv, rev_inds_riv = active_indices(river_2d, 0) nriv = length(inds_riv) - sbm = initialize_sbm(nc, config, riverfrac, inds) - # reservoirs pits = zeros(Bool, modelsize_2d) if do_reservoirs @@ -123,23 +122,34 @@ function initialize_sbm_model(config::Config) type = Float, ) - # unit for lateral subsurface flow component is [m³ d⁻¹], sbm.kv_0 [mm Δt⁻¹] - kh_0 = khfrac .* sbm.kv_0 .* 0.001 .* (basetimestep / dt) - f = sbm.f .* 1000.0 - zi = sbm.zi .* 0.001 - soilthickness = sbm.soilthickness .* 0.001 - z_exp = sbm.z_exp .* 0.001 - - ssf = LateralSSF{Float}( - kh_0 = kh_0, - f = f, - kh = fill(mv, n), + (; theta_s, theta_r, soilthickness) = lhm.soil.parameters + (; zi) = lhm.soil.variables + ssf_soilthickness = soilthickness .* 0.001 + ssf_zi = zi .* 0.001 + + kh_profile_type = get(config.input.vertical, "ksat_profile", "exponential")::String + if kh_profile_type == "exponential" + (; kv_0, f) = lhm.soil.parameters.kv_profile + kh_0 = khfrac .* kv_0 .* 0.001 .* (basetimestep / dt) + kh_profile = KhExponential(kh_0, f .* 1000.0) + elseif kh_profile_type == "exponential_constant" + (; z_exp) = lhm.soil.parameters.kv_profile + (; kv_0, f) = lhm.soil.parameters.kv_profile.exponential + kh_0 = khfrac .* kv_0 .* 0.001 .* (basetimestep / dt) + exp_profile = KhExponential(kh_0, f .* 1000.0) + kh_profile = KhExponentialConstant(exp_profile, z_exp .* 0.001) + elseif kh_profile_type == "layered" || kh_profile_type == "layered_exponential" + kh_profile = KhLayered(fill(mv, n)) + end + + # unit for lateral subsurface flow component is [m³ d⁻¹], kv_0 [mm Δt⁻¹] + ssf = LateralSSF{Float, typeof(kh_profile)}(; + kh_profile = kh_profile, khfrac = khfrac, - zi = zi, - z_exp = z_exp, - soilthickness = soilthickness, - theta_s = sbm.theta_s, - theta_r = sbm.theta_r, + zi = ssf_zi, + soilthickness = ssf_soilthickness, + theta_s, + theta_r, dt = dt / basetimestep, slope = landslope, dl = dl, @@ -150,21 +160,19 @@ function initialize_sbm_model(config::Config) ssfin = fill(mv, n), ssfmax = fill(mv, n), to_river = zeros(n), - volume = (sbm.theta_s .- sbm.theta_r) .* (soilthickness .- zi) .* (xl .* yl), + volume = (theta_s .- theta_r) .* (ssf_soilthickness .- ssf_zi) .* (xl .* yl), ) # update variables `ssf`, `ssfmax` and `kh` (layered profile) based on ksat_profile - ksat_profile = get(config.input.vertical, "ksat_profile", "exponential")::String - if ksat_profile == "exponential" - initialize_lateralssf_exp!(ssf::LateralSSF) - elseif ksat_profile == "exponential_constant" - initialize_lateralssf_exp_const!(ssf::LateralSSF) - elseif ksat_profile == "layered" || ksat_profile == "layered_exponential" - initialize_lateralssf_layered!(ssf::LateralSSF, sbm::SBM, ksat_profile) + if kh_profile_type == "exponential" || kh_profile_type == "exponential_constant" + initialize_lateralssf!(ssf, kh_profile) + elseif kh_profile_type == "layered" || kh_profile_type == "layered_exponential" + (; kv_profile) = lhm.soil.parameters + initialize_lateralssf!(ssf, lhm.soil, kv_profile, tosecond(dt)) end else # when the SBM model is coupled (BMI) to a groundwater model, the following # variables are expected to be exchanged from the groundwater model. - ssf = GroundwaterExchange{Float}( + ssf = GroundwaterExchange{Float}(; dt = dt / basetimestep, exfiltwater = fill(mv, n), zi = fill(mv, n), @@ -187,11 +195,12 @@ function initialize_sbm_model(config::Config) inds_allocation_areas = Vector{Int}[] inds_riv_allocation_areas = Vector{Int}[] if do_water_demand - areas = unique(sbm.allocation.areas) + areas = unique(lhm.allocation.parameters.areas) for a in areas - area_index = findall(x -> x == a, sbm.allocation.areas) + area_index = findall(x -> x == a, lhm.allocation.parameters.areas) push!(inds_allocation_areas, area_index) - area_riv_index = findall(x -> x == a, sbm.allocation.areas[index_river]) + area_riv_index = + findall(x -> x == a, lhm.allocation.parameters.areas[index_river]) push!(inds_riv_allocation_areas, area_riv_index) end end @@ -312,21 +321,22 @@ function initialize_sbm_model(config::Config) end end - modelmap = (vertical = sbm, lateral = (subsurface = ssf, land = olf, river = rf)) + modelmap = (vertical = lhm, lateral = (subsurface = ssf, land = olf, river = rf)) indices_reverse = ( land = rev_inds, river = rev_inds_riv, reservoir = isempty(reservoir) ? nothing : reservoir.reverse_indices, lake = isempty(lake) ? nothing : lake.reverse_indices, ) + (; maxlayers) = lhm.soil.parameters writer = prepare_writer( config, modelmap, indices_reverse, x_nc, y_nc, - nc, - extra_dim = (name = "layer", value = Float64.(1:sbm.maxlayers)), + nc; + extra_dim = (name = "layer", value = Float64.(1:(maxlayers))), ) close(nc) @@ -418,112 +428,76 @@ function initialize_sbm_model(config::Config) config, (; land, river, reservoir, lake, index_river, frac_toriver), (subsurface = ssf, land = olf, river = rf), - sbm, + lhm, clock, reader, writer, SbmModel(), ) - model = set_states(model) + set_states!(model) @info "Initialized model" return model end "update SBM model for a single timestep" -function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmModel} - +function update!(model::Model{N, L, V, R, W, T}) where {N, L, V, R, W, T <: SbmModel} (; lateral, vertical, network, config) = model do_water_demand = haskey(config.model, "water_demand") - ksat_profile = get(config.input.vertical, "ksat_profile", "exponential")::String + (; kv_profile) = vertical.soil.parameters - model = update_until_recharge(model) - # exchange of recharge between vertical sbm concept and subsurface flow domain - lateral.subsurface.recharge .= vertical.recharge ./ 1000.0 + update_until_recharge!(model) + # exchange of recharge between SBM soil model and subsurface flow domain + lateral.subsurface.recharge .= vertical.soil.variables.recharge ./ 1000.0 if do_water_demand - @. lateral.subsurface.recharge -= vertical.allocation.act_groundwater_abst / 1000.0 + @. lateral.subsurface.recharge -= + vertical.allocation.variables.act_groundwater_abst / 1000.0 end lateral.subsurface.recharge .*= lateral.subsurface.dw - lateral.subsurface.zi .= vertical.zi ./ 1000.0 + lateral.subsurface.zi .= vertical.soil.variables.zi ./ 1000.0 # update lateral subsurface flow domain (kinematic wave) - if (ksat_profile == "layered") || (ksat_profile == "layered_exponential") - for i in eachindex(lateral.subsurface.kh) - lateral.subsurface.kh[i] = - kh_layered_profile(vertical, lateral.subsurface.khfrac[i], i, ksat_profile) - end - end - update(lateral.subsurface, network.land, network.frac_toriver, ksat_profile) - model = update_after_subsurfaceflow(model) - model = update_total_water_storage(model) + kh_layered_profile!(vertical.soil, lateral.subsurface, kv_profile, vertical.dt) + update!(lateral.subsurface, network.land, network.frac_toriver) + update_after_subsurfaceflow!(model) + update_total_water_storage!(model) + return nothing end """ - update_until_recharge(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmModel} + update_until_recharge!(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmModel} Update SBM model until recharge for a single timestep. This function is also accessible through BMI, to couple the SBM model to an external groundwater model. """ -function update_until_recharge(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmModel} +function update_until_recharge!( + model::Model{N, L, V, R, W, T}, +) where {N, L, V, R, W, T <: SbmModel} (; lateral, vertical, network, config) = model - - do_water_demand = haskey(config.model, "water_demand") - - inds_riv = network.index_river - - # extract water levels h_av [m] from the land and river domains - # this is used to limit open water evaporation - vertical.waterlevel_land .= lateral.land.h_av .* 1000.0 - vertical.waterlevel_river[inds_riv] .= lateral.river.h_av .* 1000.0 - - # vertical sbm concept is updated until snow state, after that (optional) - # snow transport is possible - update_until_snow(vertical, config) - - # lateral snow transport - if get(config.model, "masswasting", false)::Bool - lateral_snow_transport!( - vertical.snow, - vertical.snowwater, - network.land.slope, - network.land, - ) - end - - # optional water demand and allocation - if do_water_demand - update_water_demand(vertical) - update_water_allocation(model) - end - - # update vertical sbm concept until recharge [mm] to the saturated store - update_until_recharge(vertical, config) - - return model + update!(vertical, lateral, network, config) + return nothing end """ - update_after_subsurfaceflow(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmModel} + update_after_subsurfaceflow!(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmModel} Update SBM model after subsurface flow for a single timestep. This function is also accessible through BMI, to couple the SBM model to an external groundwater model. """ -function update_after_subsurfaceflow( - model::Model{N,L,V,R,W,T}, -) where {N,L,V,R,W,T<:SbmModel} +function update_after_subsurfaceflow!( + model::Model{N, L, V, R, W, T}, +) where {N, L, V, R, W, T <: SbmModel} (; lateral, vertical) = model + (; soil, runoff, demand) = vertical + (; subsurface) = lateral - # update vertical sbm concept (runoff, ustorelayerdepth and satwaterdepth) - update_after_subsurfaceflow( - vertical, - lateral.subsurface.zi * 1000.0, - lateral.subsurface.exfiltwater * 1000.0, - ) + # update SBM soil model (runoff, ustorelayerdepth and satwaterdepth) + update!(soil, (; runoff, demand, subsurface)) ssf_toriver = lateral.subsurface.to_river ./ tosecond(basetimestep) - surface_routing(model, ssf_toriver = ssf_toriver) + surface_routing!(model; ssf_toriver = ssf_toriver) - return model + return nothing end """ @@ -531,24 +505,26 @@ Update of the total water storage at the end of each timestep per model cell. This is done here at model level. """ -function update_total_water_storage(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmModel} +function update_total_water_storage!( + model::Model{N, L, V, R, W, T}, +) where {N, L, V, R, W, T <: SbmModel} (; lateral, vertical, network) = model # Update the total water storage based on vertical states # TODO Maybe look at routing in the near future - update_total_water_storage( + update_total_water_storage!( vertical, network.index_river, network.land.area, lateral.river, lateral.land, ) - return model + return nothing end -function set_states( - model::Model{N,L,V,R,W,T}, -) where {N,L,V,R,W,T<:Union{SbmModel,SbmGwfModel}} +function set_states!( + model::Model{N, L, V, R, W, T}, +) where {N, L, V, R, W, T <: Union{SbmModel, SbmGwfModel}} (; lateral, vertical, network, config) = model reinit = get(config.model, "reinit", true)::Bool @@ -563,21 +539,16 @@ function set_states( nriv = length(network.river.indices) instate_path = input_path(config, config.state.path_input) @info "Set initial conditions from state file `$instate_path`." - if T <: SbmModel - @warn string( - "The unit of `ssf` (lateral subsurface flow) is now m3 d-1. Please update your", - " input state file if it was produced with a Wflow version up to v0.5.2.", - ) - end - set_states(instate_path, model; type = Float, dimname = :layer) - # update zi for vertical sbm + set_states!(instate_path, model; type = Float, dimname = :layer) + # update zi for SBM soil model zi = max.( 0.0, - vertical.soilthickness .- - vertical.satwaterdepth ./ (vertical.theta_s .- vertical.theta_r), + vertical.soil.parameters.soilthickness .- + vertical.soil.variables.satwaterdepth ./ + (vertical.soil.parameters.theta_s .- vertical.soil.parameters.theta_r), ) - vertical.zi .= zi + vertical.soil.variables.zi .= zi if land_routing == "kinematic-wave" # make sure land cells with zero flow width are set to zero q and h for i in eachindex(lateral.land.width) @@ -588,12 +559,6 @@ function set_states( end lateral.land.volume .= lateral.land.h .* lateral.land.width .* lateral.land.dl elseif land_routing == "local-inertial" - @warn string( - "The reference level for the water depth `h` and `h_av` of overland flow ", - "(local inertial model) for cells containing a river has changed from river", - " bed elevation `zb` to cell elevation `z`. Please update the input state", - " file if it was produced with Wflow version v0.5.2.", - ) for i in eachindex(lateral.land.volume) if lateral.land.rivercells[i] j = network.land.index_river[i] @@ -632,5 +597,5 @@ function set_states( else @info "Set initial conditions from default values." end - return model + return nothing end diff --git a/src/sediment.jl b/src/sediment.jl index 13a919114..ec5c8f8d1 100644 --- a/src/sediment.jl +++ b/src/sediment.jl @@ -1,7 +1,7 @@ ### Soil erosion ### -@get_units @exchange @grid_type @grid_location @with_kw struct LandSediment{T} +@get_units @grid_loc @with_kw struct LandSediment{T} # number of cells - n::Int | "-" | 0 | "none" | "none" + n::Int | "-" | "none" ### Soil erosion part ### # length of cells in y direction [m] yl::Vector{T} | "m" @@ -99,6 +99,52 @@ end end +# This function is not used by SBM (was part of sbm.jl) +function initialize_canopy(nc, config, inds) + n = length(inds) + # if leaf area index climatology provided use sl, swood and kext to calculate cmax, e_r and canopygapfraction + if haskey(config.input.vertical, "leaf_area_index") + # TODO confirm if leaf area index climatology is present in the netCDF + sl = ncread( + nc, + config, + "vertical.specific_leaf"; + optional = false, + sel = inds, + type = Float, + ) + swood = ncread( + nc, + config, + "vertical.storage_wood"; + optional = false, + sel = inds, + type = Float, + ) + kext = + ncread(nc, config, "vertical.kext"; optional = false, sel = inds, type = Float) + cmax = fill(mv, n) + e_r = fill(mv, n) + canopygapfraction = fill(mv, n) + else + sl = fill(mv, n) + swood = fill(mv, n) + kext = fill(mv, n) + # cmax, e_r, canopygapfraction only required when leaf area index climatology not provided + cmax = ncread(nc, config, "vertical.cmax"; sel = inds, defaults = 1.0, type = Float) + e_r = + ncread(nc, config, "vertical.eoverr"; sel = inds, defaults = 0.1, type = Float) + canopygapfraction = ncread( + nc, + config, + "vertical.canopygapfraction"; + sel = inds, + defaults = 0.1, + type = Float, + ) + end + return cmax, e_r, canopygapfraction, sl, swood, kext +end function initialize_landsed(nc, config, river, riverfrac, xl, yl, inds) # Initialize parameters for the soil loss part @@ -189,7 +235,7 @@ function initialize_landsed(nc, config, river, riverfrac, xl, yl, inds) fsilt = 0.13 .* psilt ./ 100 fsand = 0.01 .* psand .* (1 .- 0.01 .* pclay) .^ (2.4) fsagg = 0.28 .* (0.01 .* pclay .- 0.25) .+ 0.5 - for i = 1:n + for i in 1:n if pclay[i] > 50.0 fsagg[i] = 0.57 elseif pclay[i] < 25 @@ -232,7 +278,7 @@ function initialize_landsed(nc, config, river, riverfrac, xl, yl, inds) wbcover = wbcover .+ lakecoverage_2d end - eros = LandSediment{Float}( + eros = LandSediment{Float}(; n = n, yl = yl, xl = xl, @@ -300,14 +346,14 @@ function initialize_landsed(nc, config, river, riverfrac, xl, yl, inds) end # Soil erosion -function update_until_ols(eros::LandSediment, config) +function update_until_ols!(eros::LandSediment, config) # Options from config do_lai = haskey(config.input.vertical, "leaf_area_index") rainerosmethod = get(config.model, "rainerosmethod", "answers")::String dt = Second(config.timestepsecs) ts = Float(dt.value) - for i = 1:eros.n + for i in 1:(eros.n) ### Splash / Rainfall erosion ### # ANSWERS method @@ -392,18 +438,17 @@ function update_until_ols(eros::LandSediment, config) eros.erossagg[i] = soilloss * eros.fsagg[i] eros.eroslagg[i] = soilloss * eros.flagg[i] end - + return nothing end ### Sediment transport capacity in overland flow ### -function update_until_oltransport(ols::LandSediment, config::Config) - +function update_until_oltransport!(ols::LandSediment, config::Config) do_river = get(config.model, "runrivermodel", false)::Bool tcmethod = get(config.model, "landtransportmethod", "yalinpart")::String dt = Second(config.timestepsecs) ts = Float(dt.value) - for i = 1:ols.n + for i in 1:(ols.n) sinslope = sin(atan(ols.slope[i])) if !do_river @@ -441,7 +486,7 @@ function update_until_oltransport(ols::LandSediment, config::Config) ols.TCsagg[i] = TCsagg ols.TClagg[i] = TClagg end - + return nothing end function tc_govers(ols::LandSediment, i::Int, sinslope::Float, ts::Float)::Float @@ -591,9 +636,9 @@ function tc_yalinpart(ols::LandSediment, i::Int, sinslope::Float, ts::Float) end ### Sediment transport in overland flow ### -@get_units @exchange @grid_type @grid_location @with_kw struct OverlandFlowSediment{T} +@get_units @grid_loc @with_kw struct OverlandFlowSediment{T} # number of cells - n::Int | "-" | 0 | "none" | "none" + n::Int | "-" | "none" # Filter with river cells rivcell::Vector{T} | "-" # Total eroded soil [ton Δt⁻¹] @@ -627,14 +672,12 @@ end inlandsagg::Vector{T} | "t dt-1" inlandlagg::Vector{T} | "t dt-1" - function OverlandFlowSediment{T}(args...) where {T} equal_size_vectors(args) return new(args...) end end - function partial_update!(inland, rivcell, eroded) no_erosion = zero(eltype(eroded)) for i in eachindex(inland) @@ -643,7 +686,7 @@ function partial_update!(inland, rivcell, eroded) return inland end -function update(ols::OverlandFlowSediment, network, config) +function update!(ols::OverlandFlowSediment, network, config) do_river = get(config.model, "runrivermodel", false)::Bool tcmethod = get(config.model, "landtransportmethod", "yalinpart")::String zeroarr = fill(0.0, ols.n) @@ -675,14 +718,15 @@ function update(ols::OverlandFlowSediment, network, config) accucapacityflux!(ols.olsed, ols.soilloss, network, ols.TCsed) ols.inlandsed .= ifelse.(ols.rivcell .== 1, ols.soilloss, zeroarr) end + return nothing end ### River transport and processes ### -@get_units @exchange @grid_type @grid_location @with_kw struct RiverSediment{T} +@get_units @grid_loc @with_kw struct RiverSediment{T} # number of cells - n::Int | "-" | 0 | "none" | "none" + n::Int | "-" | "none" # Timestep [s] - dt::T | "s" | 0 | "none" | "none" + dt::T | "s" # River geometry (slope [-], length [m], width [m]) sl::Vector{T} | "m" dl::Vector{T} | "m" @@ -782,7 +826,6 @@ end # end end - function initialize_riversed(nc, config, riverwidth, riverlength, inds_riv) # Initialize river parameters nriv = length(inds_riv) @@ -1012,7 +1055,7 @@ function initialize_riversed(nc, config, riverwidth, riverlength, inds_riv) ck = zeros(Float, nriv) dk = zeros(Float, nriv) if tcmethodriv == "kodatie" - for i = 1:nriv + for i in 1:nriv if d50riv[i] <= 0.05 ak[i] = 281.4 bk[i] = 2.622 @@ -1050,7 +1093,7 @@ function initialize_riversed(nc, config, riverwidth, riverlength, inds_riv) kdbank = @. Float(0.2 * TCrbank^(-0.5) * 1e-6) kdbed = @. Float(0.2 * TCrbed^(-0.5) * 1e-6) - rs = RiverSediment( + rs = RiverSediment(; n = nriv, dt = Float(dt.value), # Parameters @@ -1132,7 +1175,7 @@ function initialize_riversed(nc, config, riverwidth, riverlength, inds_riv) return rs end -function update(rs::RiverSediment, network, config) +function update!(rs::RiverSediment, network, config) (; graph, order) = network tcmethod = get(config.model, "rivtransportmethod", "bagnold")::String @@ -1572,7 +1615,6 @@ function update(rs::RiverSediment, network, config) rs.SSconc[v] = SS * toconc rs.Bedconc[v] = Bed * toconc - end - + return nothing end diff --git a/src/sediment_model.jl b/src/sediment_model.jl index 2c5d5ebd6..cf2b78eaf 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -5,7 +5,6 @@ Initial part of the sediment model concept. Reads the input settings and data as Config object. Will return a Model that is ready to run. """ function initialize_sediment_model(config::Config) - model_type = config.model.type::String @info "Initialize model variables for model type `$model_type`." @@ -47,7 +46,7 @@ function initialize_sediment_model(config::Config) # read x, y coordinates and calculate cell length [m] y_nc = read_y_axis(nc) x_nc = read_x_axis(nc) - y = permutedims(repeat(y_nc, outer = (1, length(x_nc))))[inds] + y = permutedims(repeat(y_nc; outer = (1, length(x_nc))))[inds] cellength = abs(mean(diff(x_nc))) sizeinmetres = get(config.model, "sizeinmetres", false)::Bool @@ -61,7 +60,7 @@ function initialize_sediment_model(config::Config) # # lateral part sediment in overland flow rivcell = float(river) - ols = OverlandFlowSediment{Float}( + ols = OverlandFlowSediment{Float}(; n = n, rivcell = rivcell, soilloss = fill(mv, n), @@ -148,17 +147,17 @@ function initialize_sediment_model(config::Config) SedimentModel(), ) - model = set_states(model) + set_states!(model) @info "Initialized model" return model end -function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SedimentModel} +function update!(model::Model{N, L, V, R, W, T}) where {N, L, V, R, W, T <: SedimentModel} (; lateral, vertical, network, config) = model - update_until_ols(vertical, config) - update_until_oltransport(vertical, config) + update_until_ols!(vertical, config) + update_until_oltransport!(vertical, config) lateral.land.soilloss .= vertical.soilloss lateral.land.erosclay .= vertical.erosclay @@ -174,7 +173,7 @@ function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SedimentModel} lateral.land.TCsagg .= vertical.TCsagg lateral.land.TClagg .= vertical.TClagg - update(lateral.land, network.land, config) + update!(lateral.land, network.land, config) do_river = get(config.model, "runrivermodel", false)::Bool @@ -186,22 +185,24 @@ function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SedimentModel} lateral.river.inlandsagg .= lateral.land.inlandsagg[inds_riv] lateral.river.inlandlagg .= lateral.land.inlandlagg[inds_riv] - update(lateral.river, network.river, config) + update!(lateral.river, network.river, config) end - return model + return nothing end -function set_states(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SedimentModel} +function set_states!( + model::Model{N, L, V, R, W, T}, +) where {N, L, V, R, W, T <: SedimentModel} # read and set states in model object if reinit=false (; config) = model reinit = get(config.model, "reinit", true)::Bool if reinit == false instate_path = input_path(config, config.state.path_input) @info "Set initial conditions from state file `$instate_path`." - set_states(instate_path, model; type = Float) + set_states!(instate_path, model; type = Float) else @info "Set initial conditions from default values." end - return model + return nothing end diff --git a/src/snow/snow.jl b/src/snow/snow.jl new file mode 100644 index 000000000..a60521b13 --- /dev/null +++ b/src/snow/snow.jl @@ -0,0 +1,178 @@ +abstract type AbstractSnowModel{T} end + +"Struct for storing snow model variables" +@get_units @grid_loc @with_kw struct SnowVariables{T} + # Snow storage [mm] + snow_storage::Vector{T} | "mm" + # Liquid water content in the snow pack [mm] + snow_water::Vector{T} | "mm" + # Snow water equivalent (SWE) [mm] + swe::Vector{T} | "mm" + # Snow melt [mm Δt⁻¹] + snow_melt::Vector{T} + # Runoff from snowpack [mm Δt⁻¹] + runoff::Vector{T} +end + +"Initialize snow model variables" +function SnowVariables(T::Type{<:AbstractFloat}, n::Int) + return SnowVariables{T}(; + snow_storage = fill(0.0, n), + snow_water = fill(0.0, n), + swe = fill(mv, n), + runoff = fill(mv, n), + snow_melt = fill(mv, n), + ) +end + +"Struct for storing snow model boundary conditions" +@get_units @grid_loc @with_kw struct SnowBC{T} + # Effective precipitation [mm Δt⁻¹] + effective_precip::Vector{T} + # Snow precipitation [mm Δt⁻¹] + snow_precip::Vector{T} + # Liquid precipitation [mm Δt⁻¹] + liquid_precip::Vector{T} +end + +"Initialize snow model boundary conditions" +function SnowBC(T::Type{<:AbstractFloat}, n::Int) + return SnowBC{T}(; + effective_precip = fill(mv, n), + snow_precip = fill(mv, n), + liquid_precip = fill(mv, n), + ) +end + +"Struct for storing snow HBV model parameters" +@get_units @grid_loc @with_kw struct SnowHbvParameters{T} + # Degree-day factor [mm ᵒC⁻¹ Δt⁻¹] + cfmax::Vector{T} | "mm ᵒC-1 dt-1" + # Threshold temperature for snowfall [ᵒC] + tt::Vector{T} | "ᵒC" + # Threshold temperature interval length [ᵒC] + tti::Vector{T} | "ᵒC" + # Threshold temperature for snowmelt [ᵒC] + ttm::Vector{T} | "ᵒC" + # Water holding capacity as fraction of current snow pack [-] + whc::Vector{T} | "-" +end + +"Snow HBV model" +@with_kw struct SnowHbvModel{T} <: AbstractSnowModel{T} + boundary_conditions::SnowBC{T} + parameters::SnowHbvParameters{T} + variables::SnowVariables{T} +end + +struct NoSnowModel{T} <: AbstractSnowModel{T} end + +"Initialize snow HBV model parameters" +function SnowHbvParameters(nc, config, inds, dt) + cfmax = + ncread( + nc, + config, + "vertical.snow.parameters.cfmax"; + sel = inds, + defaults = 3.75653, + type = Float, + ) .* (dt / basetimestep) + tt = ncread( + nc, + config, + "vertical.snow.parameters.tt"; + sel = inds, + defaults = 0.0, + type = Float, + ) + tti = ncread( + nc, + config, + "vertical.snow.parameters.tti"; + sel = inds, + defaults = 1.0, + type = Float, + ) + ttm = ncread( + nc, + config, + "vertical.snow.parameters.ttm"; + sel = inds, + defaults = 0.0, + type = Float, + ) + whc = ncread( + nc, + config, + "vertical.snow.parameters.whc"; + sel = inds, + defaults = 0.1, + type = Float, + ) + snow_hbv_params = + SnowHbvParameters(; cfmax = cfmax, tt = tt, tti = tti, ttm = ttm, whc = whc) + return snow_hbv_params +end + +"Initialize snow HBV model" +function SnowHbvModel(nc, config, inds, dt) + n = length(inds) + params = SnowHbvParameters(nc, config, inds, dt) + vars = SnowVariables(Float, n) + bc = SnowBC(Float, n) + model = SnowHbvModel(; boundary_conditions = bc, parameters = params, variables = vars) + return model +end + +"Update boundary condition (effective precipitation provided by an interception model) of a snow model for a single timestep" +function update_boundary_conditions!(model::AbstractSnowModel, external_models::NamedTuple) + (; effective_precip) = model.boundary_conditions + (; interception) = external_models + @. effective_precip = + interception.variables.throughfall + interception.variables.stemflow + return nothing +end + +function update_boundary_conditions!(model::NoSnowModel, external_models::NamedTuple) + return nothing +end + +"Update snow HBV model for a single timestep" +function update!(model::SnowHbvModel, atmospheric_forcing::AtmosphericForcing) + (; temperature) = atmospheric_forcing + (; snow_storage, snow_water, swe, snow_melt, runoff) = model.variables + (; effective_precip, snow_precip, liquid_precip) = model.boundary_conditions + (; tt, tti, ttm, cfmax, whc) = model.parameters + + n = length(temperature) + threaded_foreach(1:n; basesize = 1000) do i + snow_precip[i], liquid_precip[i] = + precipitation_hbv(effective_precip[i], temperature[i], tti[i], tt[i]) + end + threaded_foreach(1:n; basesize = 1000) do i + snow_storage[i], snow_water[i], swe[i], snow_melt[i], runoff[i] = snowpack_hbv( + snow_storage[i], + snow_water[i], + snow_precip[i], + liquid_precip[i], + temperature[i], + ttm[i], + cfmax[i], + whc[i], + ) + end + return nothing +end + +function update!(model::NoSnowModel, atmospheric_forcing::AtmosphericForcing) + return nothing +end + +# wrapper methods +get_runoff(model::NoSnowModel) = 0.0 +get_runoff(model::AbstractSnowModel) = model.variables.runoff +get_snow_storage(model::NoSnowModel) = 0.0 +get_snow_storage(model::AbstractSnowModel) = model.variables.snow_storage +get_snow_water(model::NoSnowModel) = 0.0 +get_snow_water(model::AbstractSnowModel) = model.variables.snow_water \ No newline at end of file diff --git a/src/snow/snow_process.jl b/src/snow/snow_process.jl new file mode 100644 index 000000000..b61b0af88 --- /dev/null +++ b/src/snow/snow_process.jl @@ -0,0 +1,89 @@ + +""" + snowpack_hbv(snow, snowwater, snow_precip, liquid_precip, temperature, ttm, cfmax, whc; cfr = 0.05) + +HBV type snowpack modeling using a temperature degree factor. +The refreezing efficiency factor `cfr` is set to 0.05. + +# Arguments +- `snow` (snow storage) +- `snowwater` (liquid water content in the snow pack) +- `snow_precip` (snow precipitation) +- `liquid_precip` (liquid precipitation) +- `ttm` (melting threshold) +- `cfmax` (degree day factor, rate of snowmelt) +- `whc` (water holding capacity of snow) +- `cfr` refreeing efficiency constant in refreezing of liquied water in snow + +# Output +- `snow` +- `snowwater` +- `swe` (snow water equivalent) +- `runoff` +""" +function snowpack_hbv( + snow, + snowwater, + snow_precip, + liquid_precip, + temperature, + ttm, + cfmax, + whc; + cfr = 0.05, +) + # potential snow melt, based on temperature + potsnowmelt = temperature > ttm ? cfmax * (temperature - ttm) : 0.0 + # potential refreezing, based on temperature + potrefreezing = temperature < ttm ? cfmax * cfr * (ttm - temperature) : 0.0 + # actual refreezing + refreezing = temperature < ttm ? min(potrefreezing, snowwater) : 0.0 + + # no landuse correction here + snowmelt = min(potsnowmelt, snow) # actual snow melt + snow = snow + snow_precip + refreezing - snowmelt # dry snow content + snowwater = snowwater - refreezing # free water content in snow + maxsnowwater = snow * whc # max water in the snow + snowwater = snowwater + snowmelt + liquid_precip # add all water and potentially supersaturate the snowpack + runoff = max(snowwater - maxsnowwater, 0.0) # rain + surpluss snowwater + snowwater = snowwater - runoff + swe = snowwater + snow # snow water equivalent + + return snow, snowwater, swe, snowmelt, runoff +end + +""" + precipitation_hbv(precipitation, temperature, tti, tt; rfcf = 1.0, sfcf = 1.0) + +HBV type precipitation routine to separate precipitation in snow precipitation and liquid precipitation. +All correction factors (RFCF and SFCF) are set to 1. + +# Arguments +- `precipitation` +- `temperature` (threshold temperature for snowfall) +- `tti` (snowfall threshold interval length) +- `tt` (threshold temperature for snowfall) +- `rfcf` correction factor for liquid precipitation (rainfall) +- `sfcf` correction factor for snow precipitation (snowfall) + +# Output +- `snow_precip` +- `liquid_precip` +""" +function precipitation_hbv(precipitation, temperature, tti, tt; rfcf = 1.0, sfcf = 1.0) + # fraction of precipitation which falls as rain + rainfrac = if iszero(tti) + Float(temperature > tt) + else + frac = (temperature - (tt - tti / 2.0)) / tti + min(frac, 1.0) + end + rainfrac = max(rainfrac, 0.0) + + # fraction of precipitation which falls as snow + snowfrac = 1.0 - rainfrac + # different correction for liquid_precip and snow_precip + snow_precip = snowfrac * sfcf * precipitation # snow_precip depth + liquid_precip = rainfrac * rfcf * precipitation # liquid_precip depth + return snow_precip, liquid_precip +end \ No newline at end of file diff --git a/src/soil/soil.jl b/src/soil/soil.jl new file mode 100644 index 000000000..157869c2c --- /dev/null +++ b/src/soil/soil.jl @@ -0,0 +1,1302 @@ +abstract type AbstractSoilModel{T} end + +"Struct for storing SBM soil model variables" +@get_units @grid_loc @with_kw struct SbmSoilVariables{T, N} + # Calculated soil water pressure head h3 of the root water uptake reduction function (Feddes) [cm] + h3::Vector{T} | "cm" + # Unsaturated store capacity [mm] + ustorecapacity::Vector{T} | "mm" + # Amount of water in the unsaturated store, per layer [mm] + ustorelayerdepth::Vector{SVector{N, T}} | "mm" + # Thickness of unsaturated zone, per layer [mm] + ustorelayerthickness::Vector{SVector{N, T}} | "mm" + # Saturated store [mm] + satwaterdepth::Vector{T} | "mm" + # Pseudo-water table depth [mm] (top of the saturated zone) + zi::Vector{T} | "mm" + # Number of unsaturated soil layers + n_unsatlayers::Vector{Int} | "-" + # Transpiration [mm Δt⁻¹] + transpiration::Vector{T} + # Actual evaporation from unsaturated store [mm Δt⁻¹] + ae_ustore::Vector{T} + # Soil evaporation from unsaturated and saturated store [mm Δt⁻¹] + soilevap::Vector{T} + # Soil evaporation from saturated store [mm Δt⁻¹] + soilevapsat::Vector{T} + # Actual capillary rise [mm Δt⁻¹] + actcapflux::Vector{T} + # Actual transpiration from saturated store [mm Δt⁻¹] + actevapsat::Vector{T} + # Total actual evapotranspiration [mm Δt⁻¹] + actevap::Vector{T} + # Actual infiltration into the unsaturated zone [mm Δt⁻¹] + actinfilt::Vector{T} + # Actual infiltration non-compacted fraction [mm Δt⁻¹] + actinfiltsoil::Vector{T} + # Actual infiltration compacted fraction [mm Δt⁻¹] + actinfiltpath::Vector{T} + # Actual infiltration (compacted and the non-compacted areas) [mm Δt⁻¹] + infiltsoilpath::Vector{T} + # Infiltration excess water [mm Δt⁻¹] + infiltexcess::Vector{T} + # Water that cannot infiltrate due to saturated soil (saturation excess) [mm Δt⁻¹] + excesswater::Vector{T} + # Water exfiltrating during saturation excess conditions [mm Δt⁻¹] + exfiltsatwater::Vector{T} + # Water exfiltrating from unsaturated store because of change in water table [mm Δt⁻¹] + exfiltustore::Vector{T} + # Excess water for non-compacted fraction [mm Δt⁻¹] + excesswatersoil::Vector{T} + # Excess water for compacted fraction [mm Δt⁻¹] + excesswaterpath::Vector{T} + # Total surface runoff from infiltration and saturation excess (excluding actual open water evaporation) [mm Δt⁻¹] + runoff::Vector{T} + # Net surface runoff (surface runoff - actual open water evaporation) [mm Δt⁻¹] + net_runoff::Vector{T} + # Volumetric water content [-] per soil layer (including theta_r and saturated zone) + vwc::Vector{SVector{N, T}} | "-" + # Volumetric water content [%] per soil layer (including theta_r and saturated zone) + vwc_perc::Vector{SVector{N, T}} | "%" + # Root water storage [mm] in unsaturated and saturated zone (excluding theta_r) + rootstore::Vector{T} | "mm" + # Volumetric water content [-] in root zone (including theta_r and saturated zone) + vwc_root::Vector{T} | "-" + # Volumetric water content [%] in root zone (including theta_r and saturated zone) + vwc_percroot::Vector{T} | "%" + # Amount of available water in the unsaturated zone [mm] + ustoredepth::Vector{T} | "mm" + # Downward flux from unsaturated to saturated zone [mm Δt⁻¹] + transfer::Vector{T} + # Net recharge to saturated store [mm Δt⁻¹] + recharge::Vector{T} + # Actual leakage from saturated store [mm Δt⁻¹] + actleakage::Vector{T} + # Total water storage (excluding floodplain volume, lakes and reservoirs) [mm] + total_storage::Vector{T} | "mm" + # Top soil temperature [ᵒC] + tsoil::Vector{T} | "ᵒC" + # Soil infiltration reduction factor (when soil is frozen) [-] + f_infiltration_reduction::Vector{T} | "-" +end + +"Initialize SBM soil model variables" +function SbmSoilVariables(n, parameters) + (; + soilthickness, + maxlayers, + act_thickl, + sumlayers, + soilwatercapacity, + theta_s, + theta_r, + ) = parameters + satwaterdepth = 0.85 .* soilwatercapacity # cold state value for satwaterdepth + zi = @. max(0.0, soilthickness - satwaterdepth / (theta_s - theta_r)) + ustorelayerthickness = set_layerthickness.(zi, sumlayers, act_thickl) + n_unsatlayers = number_of_active_layers.(ustorelayerthickness) + + vwc = fill(mv, maxlayers, n) + vwc_perc = fill(mv, maxlayers, n) + + vars = SbmSoilVariables(; + ustorelayerdepth = zero(act_thickl), + ustorecapacity = soilwatercapacity .- satwaterdepth, + ustorelayerthickness, + satwaterdepth, + zi, + n_unsatlayers, + transpiration = fill(mv, n), + h3 = fill(mv, n), + ae_ustore = fill(mv, n), + soilevap = fill(mv, n), + soilevapsat = fill(mv, n), + actcapflux = fill(mv, n), + actevapsat = fill(mv, n), + actevap = fill(mv, n), + f_infiltration_reduction = fill(Float(1), n), + actinfilt = fill(mv, n), + actinfiltsoil = fill(mv, n), + actinfiltpath = fill(mv, n), + infiltsoilpath = fill(mv, n), + infiltexcess = fill(mv, n), + excesswater = fill(mv, n), + exfiltsatwater = fill(mv, n), + exfiltustore = fill(mv, n), + excesswatersoil = fill(mv, n), + excesswaterpath = fill(mv, n), + runoff = fill(mv, n), + net_runoff = fill(mv, n), + vwc = svectorscopy(vwc, Val{maxlayers}()), + vwc_perc = svectorscopy(vwc_perc, Val{maxlayers}()), + rootstore = fill(mv, n), + vwc_root = fill(mv, n), + vwc_percroot = fill(mv, n), + ustoredepth = fill(mv, n), + transfer = fill(mv, n), + recharge = fill(mv, n), + actleakage = fill(mv, n), + tsoil = fill(Float(10.0), n), + total_storage = zeros(Float, n), # Set the total water storage from initialized values + ) + return vars +end + +"Struct for storing SBM soil model boundary conditions" +@get_units @grid_loc @with_kw struct SbmSoilBC{T} + # Water flux at the soil surface [mm Δt⁻¹] + water_flux_surface::Vector{T} + # Potential transpiration rate [mm Δt⁻¹] + potential_transpiration::Vector{T} + # Potential soil evaporation rate [mm Δt⁻¹] + potential_soilevaporation::Vector{T} +end + +"Initialize SBM soil model boundary conditions" +function SbmSoilBC( + n; + water_flux_surface::Vector{T} = fill(mv, n), + potential_transpiration::Vector{T} = fill(mv, n), + potential_soilevaporation::Vector{T} = fill(mv, n), +) where {T} + return SbmSoilBC{T}(; + water_flux_surface = water_flux_surface, + potential_transpiration = potential_transpiration, + potential_soilevaporation = potential_soilevaporation, + ) +end + +"Exponential depth profile of vertical hydraulic conductivity at the soil surface" +@get_units @grid_loc struct KvExponential{T} + # Vertical hydraulic conductivity [mm Δt⁻¹] at soil surface + kv_0::Vector{T} + # A scaling parameter [mm⁻¹] (controls exponential decline of kv_0) + f::Vector{T} | "mm-1" +end + +"Exponential constant depth profile of vertical hydraulic conductivity" +@get_units @grid_loc struct KvExponentialConstant{T} + exponential::KvExponential{T} + # Depth [mm] from soil surface for which exponential decline of kv_0 is valid + z_exp::Vector{T} | "mm" +end + +"Layered depth profile of vertical hydraulic conductivity" +@get_units @grid_loc struct KvLayered{T, N} + # Vertical hydraulic conductivity [mm Δt⁻¹] per soil layer + kv::Vector{SVector{N, T}} +end + +"Layered exponential depth profile of vertical hydraulic conductivity" +@get_units @grid_loc struct KvLayeredExponential{T, N} + # A scaling parameter [mm⁻¹] (controls exponential decline of kv_0) + f::Vector{T} | "mm-1" + # Vertical hydraulic conductivity [mm Δt⁻¹] per soil layer + kv::Vector{SVector{N, T}} + # Number of soil layers with vertical hydraulic conductivity value `kv` + nlayers_kv::Vector{Int} | "-" + # Depth [mm] from soil surface for which layered profile is valid + z_layered::Vector{T} | "mm" +end + +"Initialize SBM soil model hydraulic conductivity depth profile" +function sbm_kv_profiles(nc, config, inds, kv_0, f, maxlayers, nlayers, sumlayers, dt) + kv_profile_type = get(config.input.vertical, "ksat_profile", "exponential")::String + n = length(inds) + if kv_profile_type == "exponential" + kv_profile = KvExponential(kv_0, f) + elseif kv_profile_type == "exponential_constant" + z_exp = ncread( + nc, + config, + "vertical.soil.parameters.z_exp"; + optional = false, + sel = inds, + type = Float, + ) + exp_profile = KvExponential(kv_0, f) + kv_profile = KvExponentialConstant(exp_profile, z_exp) + elseif kv_profile_type == "layered" || kv_profile_type == "layered_exponential" + kv = + ncread( + nc, + config, + "vertical.soil.parameters.kv"; + sel = inds, + defaults = 1000.0, + type = Float, + dimname = :layer, + ) .* (dt / basetimestep) + if size(kv, 1) != maxlayers + parname = param(config.input.vertical.soil.parameter, "kv") + size1 = size(kv, 1) + error("$parname needs a layer dimension of size $maxlayers, but is $size1") + end + if kv_profile_type == "layered" + kv_profile = KvLayered(svectorscopy(kv, Val{maxlayers}())) + else + z_layered = ncread( + nc, + config, + "vertical.soil.parameters.z_layered"; + optional = false, + sel = inds, + type = Float, + ) + nlayers_kv = fill(0, n) + for i in eachindex(nlayers_kv) + layers = @view sumlayers[i][2:nlayers[i]] + _, k = findmin(abs.(z_layered[i] .- layers)) + nlayers_kv[i] = k + z_layered[i] = layers[k] + end + kv_profile = KvLayeredExponential( + f, + svectorscopy(kv, Val{maxlayers}()), + nlayers_kv, + z_layered, + ) + end + else + error("""An unknown "ksat_profile" is specified in the TOML file ($ksat_profile). + This should be "exponential", "exponential_constant", "layered" or + "layered_exponential". + """) + end + return kv_profile +end + +"Struct for storing SBM soil model parameters" +@get_units @grid_loc @with_kw struct SbmSoilParameters{T, N, M, Kv} + # Maximum number of soil layers [-] + maxlayers::Int + # Number of soil layers + nlayers::Vector{Int} | "-" + # Saturated water content (porosity) [-] + theta_s::Vector{T} | "-" + # Residual water content [-] + theta_r::Vector{T} | "-" + # Soilwater capacity [mm] + soilwatercapacity::Vector{T} | "mm" + # Muliplication factor [-] applied to kv_z (vertical flow) + kvfrac::Vector{SVector{N, T}} | "-" + # Air entry pressure [cm] of soil (Brooks-Corey) + hb::Vector{T} | "cm" + # Soil thickness [mm] + soilthickness::Vector{T} | "mm" + # Thickness of soil layers [mm] + act_thickl::Vector{SVector{N, T}} | "mm" + # Cumulative sum of soil layers [mm], starting at soil surface (0) + sumlayers::Vector{SVector{M, T}} | "mm" + # Infiltration capacity of the compacted areas [mm Δt⁻¹] + infiltcappath::Vector{T} + # Soil infiltration capacity [mm Δt⁻¹] + infiltcapsoil::Vector{T} + # Maximum leakage [mm Δt⁻¹] from saturated zone + maxleakage::Vector{T} + # Parameter [mm] controlling capillary rise + cap_hmax::Vector{T} | "mm" + # Coefficient [-] controlling capillary rise + cap_n::Vector{T} | "-" + # Brooks-Corey power coefficient [-] for each soil layer + c::Vector{SVector{N, T}} | "-" + # Soil temperature smooth factor [-] + w_soil::Vector{T} | "-" + # Controls soil infiltration reduction factor when soil is frozen [-] + cf_soil::Vector{T} | "-" + # Fraction of compacted area [-] + pathfrac::Vector{T} | "-" + # Controls how roots are linked to water table [-] + rootdistpar::Vector{T} | "-" + # Fraction of the root length density in each soil layer [-] + rootfraction::Vector{SVector{N, T}} | "-" + # Soil water pressure head h1 of the root water uptake reduction function (Feddes) [cm] + h1::Vector{T} | "cm" + # Soil water pressure head h2 of the root water uptake reduction function (Feddes) [cm] + h2::Vector{T} | "cm" + # Soil water pressure head h3_high of the root water uptake reduction function (Feddes) [cm] + h3_high::Vector{T} | "cm" + # Soil water pressure head h3_low of the root water uptake reduction function (Feddes) [cm] + h3_low::Vector{T} | "cm" + # Soil water pressure head h4 of the root water uptake reduction function (Feddes) [cm] + h4::Vector{T} | "cm" + # Root water uptake reduction at soil water pressure head h1 (0.0 or 1.0) [-] + alpha_h1::Vector{T} | "-" + # Soil fraction [-] + soil_fraction::Vector{T} | "-" + # Vertical hydraulic conductivity profile type + kv_profile::Kv + # Vegetation parameter set + vegetation_parameter_set::VegetationParameters{T} +end + +"Initialize SBM soil model parameters" +function SbmSoilParameters(nc, config, vegetation_parameter_set, inds, dt) + config_thicknesslayers = get(config.model, "thicknesslayers", Float[]) + if length(config_thicknesslayers) > 0 + thicknesslayers = SVector(Tuple(push!(Float.(config_thicknesslayers), mv))) + cum_depth_layers = pushfirst(cumsum(thicknesslayers), 0.0) + maxlayers = length(thicknesslayers) # max number of soil layers + else + thicknesslayers = SVector.(soilthickness) + cum_depth_layers = pushfirst(cumsum(thicknesslayers), 0.0) + maxlayers = 1 + end + w_soil = + ncread( + nc, + config, + "vertical.soil.parameters.w_soil"; + sel = inds, + defaults = 0.1125, + type = Float, + ) .* (dt / basetimestep) + cf_soil = ncread( + nc, + config, + "vertical.soil.parameters.cf_soil"; + sel = inds, + defaults = 0.038, + type = Float, + ) + # soil parameters + theta_s = ncread( + nc, + config, + "vertical.soil.parameters.theta_s"; + sel = inds, + defaults = 0.6, + type = Float, + ) + theta_r = ncread( + nc, + config, + "vertical.soil.parameters.theta_r"; + sel = inds, + defaults = 0.01, + type = Float, + ) + kv_0 = + ncread( + nc, + config, + "vertical.soil.parameters.kv_0"; + sel = inds, + defaults = 3000.0, + type = Float, + ) .* (dt / basetimestep) + f = ncread( + nc, + config, + "vertical.soil.parameters.f"; + sel = inds, + defaults = 0.001, + type = Float, + ) + hb = ncread( + nc, + config, + "vertical.soil.parameters.hb"; + sel = inds, + defaults = -10.0, + type = Float, + ) + h1 = ncread( + nc, + config, + "vertical.soil.parameters.h1"; + sel = inds, + defaults = 0.0, + type = Float, + ) + h2 = ncread( + nc, + config, + "vertical.soil.parameters.h2"; + sel = inds, + defaults = -100.0, + type = Float, + ) + h3_high = ncread( + nc, + config, + "vertical.soil.parameters.h3_high"; + sel = inds, + defaults = -400.0, + type = Float, + ) + h3_low = ncread( + nc, + config, + "vertical.soil.parameters.h3_low"; + sel = inds, + defaults = -1000.0, + type = Float, + ) + h4 = ncread( + nc, + config, + "vertical.soil.parameters.h4"; + sel = inds, + defaults = -15849.0, + type = Float, + ) + alpha_h1 = ncread( + nc, + config, + "vertical.soil.parameters.alpha_h1"; + sel = inds, + defaults = 1.0, + type = Float, + ) + soilthickness = ncread( + nc, + config, + "vertical.soil.parameters.soilthickness"; + sel = inds, + defaults = 2000.0, + type = Float, + ) + infiltcappath = + ncread( + nc, + config, + "vertical.soil.parameters.infiltcappath"; + sel = inds, + defaults = 10.0, + type = Float, + ) .* (dt / basetimestep) + infiltcapsoil = + ncread( + nc, + config, + "vertical.soil.parameters.infiltcapsoil"; + sel = inds, + defaults = 100.0, + type = Float, + ) .* (dt / basetimestep) + maxleakage = + ncread( + nc, + config, + "vertical.soil.parameters.maxleakage"; + sel = inds, + defaults = 0.0, + type = Float, + ) .* (dt / basetimestep) + + c = ncread( + nc, + config, + "vertical.soil.parameters.c"; + sel = inds, + defaults = 10.0, + type = Float, + dimname = :layer, + ) + if size(c, 1) != maxlayers + parname = param(config.input.vertical, "c") + size1 = size(c, 1) + error("$parname needs a layer dimension of size $maxlayers, but is $size1") + end + kvfrac = ncread( + nc, + config, + "vertical.soil.parameters.kvfrac"; + sel = inds, + defaults = 1.0, + type = Float, + dimname = :layer, + ) + if size(kvfrac, 1) != maxlayers + parname = param(config.input, "vertical.soil.parameters.kvfrac") + size1 = size(kvfrac, 1) + error("$parname needs a layer dimension of size $maxlayers, but is $size1") + end + # fraction compacted area + pathfrac = ncread( + nc, + config, + "vertical.soil.parameters.pathfrac"; + sel = inds, + defaults = 0.01, + type = Float, + ) + + # vegetation parameters + rootdistpar = ncread( + nc, + config, + "vertical.soil.parameters.rootdistpar"; + sel = inds, + defaults = -500.0, + type = Float, + ) + cap_hmax = ncread( + nc, + config, + "vertical.soil.parameters.cap_hmax"; + sel = inds, + defaults = 2000.0, + type = Float, + ) + cap_n = ncread( + nc, + config, + "vertical.soil.parameters.cap_n"; + sel = inds, + defaults = 2.0, + type = Float, + ) + + act_thickl = set_layerthickness.(soilthickness, (cum_depth_layers,), (thicknesslayers,)) + sumlayers = @. pushfirst(cumsum(act_thickl), 0.0) + nlayers = number_of_active_layers.(act_thickl) + + if length(config_thicknesslayers) > 0 + # root fraction read from nc file, in case of multiple soil layers and TOML file + # includes "vertical.rootfraction" + if haskey(config.input.vertical.soil.parameters, "rootfraction") + rootfraction = ncread( + nc, + config, + "vertical.soil.parameters.rootfraction"; + sel = inds, + optional = false, + type = Float, + dimname = :layer, + ) + else + n = length(inds) + (; rootingdepth) = vegetation_parameter_set + # default root fraction in case of multiple soil layers + rootfraction = zeros(Float, maxlayers, n) + for i in 1:n + if rootingdepth[i] > 0.0 + for k in 1:maxlayers + if (rootingdepth[i] - sumlayers[i][k]) >= act_thickl[i][k] + rootfraction[k, i] = act_thickl[i][k] / rootingdepth[i] + else + rootfraction[k, i] = + max(rootingdepth[i] - sumlayers[i][k], 0.0) / + rootingdepth[i] + end + end + end + end + end + else + # for the case of 1 soil layer + rootfraction = ones(Float, maxlayers, n) + end + + kv_profile = + sbm_kv_profiles(nc, config, inds, kv_0, f, maxlayers, nlayers, sumlayers, dt) + + soilwatercapacity = @. soilthickness * (theta_s - theta_r) + + n = length(inds) + sbm_params = SbmSoilParameters(; + maxlayers, + nlayers, + soilwatercapacity, + theta_s, + theta_r, + kvfrac = svectorscopy(kvfrac, Val{maxlayers}()), + hb, + h1, + h2, + h3_high, + h3_low, + h4, + alpha_h1, + soilthickness, + act_thickl, + sumlayers, + infiltcappath, + infiltcapsoil, + maxleakage, + pathfrac, + rootdistpar, + rootfraction = svectorscopy(rootfraction, Val{maxlayers}()), + cap_hmax, + cap_n, + c = svectorscopy(c, Val{maxlayers}()), + w_soil, + cf_soil, + soil_fraction = fill(mv, n), + kv_profile, + vegetation_parameter_set, + ) + return sbm_params +end + +"SBM soil model" +@with_kw struct SbmSoilModel{T, N, M, Kv} <: AbstractSoilModel{T} + boundary_conditions::SbmSoilBC{T} + parameters::SbmSoilParameters{T, N, M, Kv} + variables::SbmSoilVariables{T, N} +end + +"Initialize SBM soil model" +function SbmSoilModel(nc, config, vegetation_parameter_set, inds, dt) + n = length(inds) + params = SbmSoilParameters(nc, config, vegetation_parameter_set, inds, dt) + vars = SbmSoilVariables(n, params) + bc = SbmSoilBC(n) + model = SbmSoilModel(; boundary_conditions = bc, parameters = params, variables = vars) + return model +end + +"Return soil fraction" +function soil_fraction!(soil, runoff, glacier) + (; canopygapfraction) = soil.parameters.vegetation_parameter_set + (; soil_fraction) = soil.parameters + (; waterfrac, riverfrac) = runoff.parameters + glacier_fraction = get_glacier_fraction(glacier) + + @. soil_fraction = + max(canopygapfraction - waterfrac - riverfrac - glacier_fraction, 0.0) + return nothing +end + +"Update boundary conditions of the SBM soil model for a single timestep" +function update_boundary_conditions!( + model::SbmSoilModel, + atmospheric_forcing::AtmosphericForcing, + external_models::NamedTuple, +) + (; interception, runoff, demand, allocation) = external_models + (; potential_transpiration, water_flux_surface, potential_soilevaporation) = + model.boundary_conditions + + potential_transpiration .= get_potential_transpiration(interception) + + @. potential_soilevaporation = + model.parameters.soil_fraction * atmospheric_forcing.potential_evaporation + evaporation!(demand.paddy, potential_soilevaporation) + potential_soilevaporation .= potential_soilevaporation .- get_evaporation(demand.paddy) + + water_flux_surface .= + max.( + runoff.boundary_conditions.water_flux_surface .+ + get_irrigation_allocated(allocation) .- runoff.variables.runoff_river .- + runoff.variables.runoff_land .+ get_water_depth(demand.paddy), + 0.0, + ) + return nothing +end + +"Update soil temperature of the SBM soil model for a single timestep" +function soil_temperature!(model::SbmSoilModel, snow::AbstractSnowModel, temperature) + v = model.variables + p = model.parameters + @. v.tsoil = soil_temperature(v.tsoil, p.w_soil, temperature) + return nothing +end + +soil_temperature!(model::SbmSoilModel, snow::NoSnowModel, temperature) = nothing + +"Update total available water in the unsaturated zone of the SBM soil model for a single timestep" +function ustoredepth!(model::SbmSoilModel) + v = model.variables + p = model.parameters + for i in eachindex(v.ustorelayerdepth) + v.ustoredepth[i] = sum(@view v.ustorelayerdepth[i][1:p.nlayers[i]]) + end + return nothing +end + +"Update the infiltration reduction factor of the SBM soil model for a single timestep" +function infiltration_reduction_factor!( + model::SbmSoilModel; + modelsnow = false, + soilinfreduction = false, +) + v = model.variables + p = model.parameters + + n = length(v.tsoil) + threaded_foreach(1:n; basesize = 1000) do i + v.f_infiltration_reduction[i] = infiltration_reduction_factor( + v.tsoil[i], + p.cf_soil[i]; + modelsnow = modelsnow, + soilinfreduction = soilinfreduction, + ) + end + return nothing +end + +""" + infiltration!(model::SbmSoilModel) + +Update the infiltration rate `infiltsoilpath` and infiltration excess water rate +`infiltexcess` of the SBM soil model for a single timestep. +""" +function infiltration!(model::SbmSoilModel) + v = model.variables + p = model.parameters + (; water_flux_surface) = model.boundary_conditions + + n = length(v.infiltsoilpath) + threaded_foreach(1:n; basesize = 1000) do i + v.infiltsoilpath[i], v.infiltexcess[i] = infiltration( + water_flux_surface[i], + p.pathfrac[i], + p.infiltcapsoil[i], + p.infiltcappath[i], + v.ustorecapacity[i], + v.f_infiltration_reduction[i], + ) + end + return nothing +end + +""" + unsaturated_zone_flow!(model::SbmSoilModel; transfermethod = false) + +Update unsaturated storage `ustorelayerdepth` and the `transfer` of water from the +unsaturated to the saturated store of the SBM soil model for a single timestep, based on the +original Topog_SBM formulation (`transfermethod = true` and one soil layer) or the +Brooks-Corey approach (`transfermethod = false` and one or multiple soil layers). +""" +function unsaturated_zone_flow!(model::SbmSoilModel; transfermethod = false) + v = model.variables + p = model.parameters + + n = length(v.transfer) + threaded_foreach(1:n; basesize = 250) do i + if v.n_unsatlayers[i] > 0 + if transfermethod && p.maxlayers == 1 + # original Topog_SBM formulation + ustorelayerdepth = v.ustorelayerdepth[i][1] + v.infiltsoilpath[i] + kv_z = + hydraulic_conductivity_at_depth(p.kv_profile, p.kvfrac, v.zi[i], i, 1) + ustorelayerdepth, v.transfer[i] = unsatzone_flow_sbm( + ustorelayerdepth, + p.soilwatercapacity[i], + v.satwaterdepth[i], + kv_z, + v.ustorelayerthickness[1], + p.theta_s[i], + p.theta_r[i], + ) + v.ustorelayerdepth[i] = setindex(v.ustorelayerdepth[i], ustorelayerdepth, 1) + else + # Brooks-Corey approach + z = cumsum(v.ustorelayerthickness[i]) + flow_rate = 0.0 + for m in 1:v.n_unsatlayers[i] + l_sat = v.ustorelayerthickness[i][m] * (p.theta_s[i] - p.theta_r[i]) + kv_z = + hydraulic_conductivity_at_depth(p.kv_profile, p.kvfrac, z[m], i, m) + ustorelayerdepth = if m == 1 + v.ustorelayerdepth[i][m] + v.infiltsoilpath[i] + else + v.ustorelayerdepth[i][m] + flow_rate + end + ustorelayerdepth, flow_rate = + unsatzone_flow_layer(ustorelayerdepth, kv_z, l_sat, p.c[i][m]) + v.ustorelayerdepth[i] = + setindex(v.ustorelayerdepth[i], ustorelayerdepth, m) + end + v.transfer[i] = flow_rate + end + else + v.transfer[i] = 0.0 + end + end + return nothing +end + +""" + soil_evaporation!(model::SbmSoilModel) + +Update soil evaporation from the saturated store `soilevapsat` and the total soil +evaporation from the unsaturated and saturated store `soilevap` of the SBM soil model for a +single timestep. Also unsaturated storage `ustorelayerdepth` and the saturated store +`satwaterdepth` are updated. +""" +function soil_evaporation!(model::SbmSoilModel) + (; potential_soilevaporation) = model.boundary_conditions + v = model.variables + p = model.parameters + + n = length(potential_soilevaporation) + threaded_foreach(1:n; basesize = 1000) do i + potsoilevap = potential_soilevaporation[i] + # First calculate the evaporation of unsaturated storage into the + # atmosphere from the upper layer. + if p.maxlayers == 1 + saturationdeficit = p.soilwatercapacity[i] - v.satwaterdepth[i] + soilevapunsat = + potsoilevap * min(1.0, saturationdeficit / p.soilwatercapacity[i]) + else + soilevapunsat = soil_evaporation_unsatured_store( + potsoilevap, + v.ustorelayerdepth[i][1], + v.ustorelayerthickness[i][1], + v.n_unsatlayers[i], + v.zi[i], + p.theta_s[i] - p.theta_r[i], + ) + end + # Ensure that the unsaturated evaporation rate does not exceed the + # available unsaturated moisture + soilevapunsat = min(soilevapunsat, v.ustorelayerdepth[i][1]) + # Update the additional atmospheric demand + potsoilevap = potsoilevap - soilevapunsat + v.ustorelayerdepth[i] = + setindex(v.ustorelayerdepth[i], v.ustorelayerdepth[i][1] - soilevapunsat, 1) + + if p.maxlayers == 1 + soilevapsat = 0.0 + else + soilevapsat = soil_evaporation_satured_store( + potsoilevap, + v.n_unsatlayers[i], + p.act_thickl[i][1], + v.zi[i], + p.theta_s[i] - p.theta_r[i], + ) + end + v.soilevapsat[i] = soilevapsat + v.soilevap[i] = soilevapunsat + soilevapsat + v.satwaterdepth[i] = v.satwaterdepth[i] - soilevapsat + end + return nothing +end + +""" + transpiration!(model::SbmSoilModel, dt; ust = false) + +Update total `transpiration`, transpiration from the unsaturated store `ae_ustore` and +saturated store `actevapsat` of the SBM soil model for a single timestep. Also unsaturated +storage `ustorelayerdepth` and the saturated store `satwaterdepth` are updated. +""" +function transpiration!(model::SbmSoilModel, dt; ust = false) + (; potential_transpiration) = model.boundary_conditions + v = model.variables + p = model.parameters + + rootingdepth = get_rootingdepth(model) + n = length(rootingdepth) + threaded_foreach(1:n; basesize = 250) do i + actevapustore = 0.0 + rootfraction_unsat = 0.0 + v.h3[i] = feddes_h3(p.h3_high[i], p.h3_low[i], potential_transpiration[i], dt) + for k in 1:v.n_unsatlayers[i] + vwc = max( + v.ustorelayerdepth[i][k] / v.ustorelayerthickness[i][k], + Float(0.0000001), + ) + head = head_brooks_corey(vwc, p.theta_s[i], p.theta_r[i], p.c[i][k], p.hb[i]) + alpha = rwu_reduction_feddes( + head, + p.h1[i], + p.h2[i], + v.h3[i], + p.h4[i], + p.alpha_h1[i], + ) + + if ust + availcap = ustorelayerdepth[i][k] * 0.99 + else + availcap = min( + 1.0, + max( + 0.0, + (rootingdepth[i] - p.sumlayers[i][k]) / + v.ustorelayerthickness[i][k], + ), + ) + end + maxextr = v.ustorelayerdepth[i][k] * availcap + # the rootfraction is valid for the root length in a soil layer, if zi decreases the root length + # the rootfraction needs to be adapted + if k == v.n_unsatlayers[i] && v.zi[i] < rootingdepth[i] + rootlength = min(p.act_thickl[i][k], rootingdepth[i] - p.sumlayers[i][k]) + rootfraction_act = + p.rootfraction[i][k] * (v.ustorelayerthickness[i][k] / rootlength) + else + rootfraction_act = p.rootfraction[i][k] + end + actevapustore_layer = + min(alpha * rootfraction_act * potential_transpiration[i], maxextr) + rootfraction_unsat = rootfraction_unsat + rootfraction_act + ustorelayerdepth = v.ustorelayerdepth[i][k] - actevapustore_layer + actevapustore = actevapustore + actevapustore_layer + v.ustorelayerdepth[i] = setindex(v.ustorelayerdepth[i], ustorelayerdepth, k) + end + + # transpiration from saturated store + wetroots = scurve(v.zi[i], rootingdepth[i], Float(1.0), p.rootdistpar[i]) + alpha = rwu_reduction_feddes( + Float(0.0), + p.h1[i], + p.h2[i], + v.h3[i], + p.h4[i], + p.alpha_h1[i], + ) + # include remaining root fraction if rooting depth is below water table zi + if v.zi[i] >= rootingdepth[i] + f_roots = wetroots + restevap = potential_transpiration[i] - actevapustore + else + f_roots = wetroots * (1.0 - rootfraction_unsat) + restevap = potential_transpiration[i] + end + actevapsat = min(restevap * f_roots * alpha, v.satwaterdepth[i]) + + v.ae_ustore[i] = actevapustore + v.actevapsat[i] = actevapsat + v.satwaterdepth[i] = v.satwaterdepth[i] - actevapsat + v.transpiration[i] = actevapustore + actevapsat + end + return nothing +end + +""" + actual_infiltration!(model::SbmSoilModel) + +Update the actual infiltration rate `actinfilt` of the SBM soil model for a single timestep. + +A soil water balance check is performed. Unsaturated storage that exceeds the maximum +storage per unsaturated soil layer is transferred to the layer above (or surface), from the +bottom to the top unsaturated soil layer. The resulting excess water `ustoredepth_excess` is +subtracted from the infiltration rate `infiltsoilpath`. +""" +function actual_infiltration!(model::SbmSoilModel) + v = model.variables + p = model.parameters + + n = length(v.actinfilt) + threaded_foreach(1:n; basesize = 1000) do i + # check soil moisture balance per layer + ustoredepth_excess = 0.0 + for k in v.n_unsatlayers[i]:-1:1 + ustoredepth_excess = max( + 0.0, + v.ustorelayerdepth[i][k] - + v.ustorelayerthickness[i][k] * (p.theta_s[i] - p.theta_r[i]), + ) + v.ustorelayerdepth[i] = setindex( + v.ustorelayerdepth[i], + v.ustorelayerdepth[i][k] - ustoredepth_excess, + k, + ) + if k > 1 + v.ustorelayerdepth[i] = setindex( + v.ustorelayerdepth[i], + v.ustorelayerdepth[i][k - 1] + ustoredepth_excess, + k - 1, + ) + end + end + + v.actinfilt[i] = v.infiltsoilpath[i] - ustoredepth_excess + end + return nothing +end + +""" + actual_infiltration_soil_path!(model::SbmSoilModel) + +Update the actual infiltration rate for soil `actinfiltsoil` and paved area `actinfiltpath` +of the SBM soil model for a single timestep. +""" +function actual_infiltration_soil_path!(model::SbmSoilModel) + v = model.variables + p = model.parameters + (; water_flux_surface) = model.boundary_conditions + + n = length(water_flux_surface) + threaded_foreach(1:n; basesize = 1000) do i + v.actinfiltsoil[i], v.actinfiltpath[i] = actual_infiltration_soil_path( + water_flux_surface[i], + v.actinfilt[i], + p.pathfrac[i], + p.infiltcapsoil[i], + p.infiltcappath[i], + v.f_infiltration_reduction[i], + ) + end + return nothing +end + +""" + capillary_flux!(model::SbmSoilModel) + +Update the capillary flux `actcapflux` of the SBM soil model for a single timestep. +""" +function capillary_flux!(model::SbmSoilModel) + v = model.variables + p = model.parameters + rootingdepth = get_rootingdepth(model) + + n = length(rootingdepth) + threaded_foreach(1:n; basesize = 1000) do i + if v.n_unsatlayers[i] > 0 + ksat = hydraulic_conductivity_at_depth( + p.kv_profile, + p.kvfrac, + v.zi[i], + i, + v.n_unsatlayers[i], + ) + maxcapflux = + max(0.0, min(ksat, v.ae_ustore[i], v.ustorecapacity[i], v.satwaterdepth[i])) + + if v.zi[i] > rootingdepth[i] + capflux = + maxcapflux * + pow(1.0 - min(v.zi[i], p.cap_hmax[i]) / (p.cap_hmax[i]), p.cap_n[i]) + else + capflux = 0.0 + end + + netcapflux = capflux + actcapflux = 0.0 + for k in v.n_unsatlayers[i]:-1:1 + toadd = min( + netcapflux, + max( + v.ustorelayerthickness[i][k] * (p.theta_s[i] - p.theta_r[i]) - + v.ustorelayerdepth[i][k], + 0.0, + ), + ) + v.ustorelayerdepth[i] = + setindex(v.ustorelayerdepth[i], v.ustorelayerdepth[i][k] + toadd, k) + netcapflux = netcapflux - toadd + actcapflux = actcapflux + toadd + end + v.actcapflux[i] = actcapflux + else + v.actcapflux[i] = 0.0 + end + end + return nothing +end + +""" + leakage!(model::SbmSoilModel) + +Update the actual leakage rate `actleakage` of the SBM soil model for a single timestep. +""" +function leakage!(model::SbmSoilModel) + v = model.variables + p = model.parameters + + n = length(v.actleakage) + threaded_foreach(1:n; basesize = 1000) do i + deepksat = hydraulic_conductivity_at_depth( + p.kv_profile, + p.kvfrac, + p.soilthickness[i], + i, + p.nlayers[i], + ) + deeptransfer = min(v.satwaterdepth[i], deepksat) + v.actleakage[i] = max(0.0, min(p.maxleakage[i], deeptransfer)) + end + return nothing +end + +""" + update!( + model::SbmSoilModel, + atmospheric_forcing::AtmosphericForcing, + external_models::NamedTuple, + config, + dt, + ) + +Update the SBM soil model (infiltration, unsaturated zone flow, soil evaporation and +transpiration, capillary flux and leakage) for a single timestep. +""" +function update!( + model::SbmSoilModel, + atmospheric_forcing::AtmosphericForcing, + external_models::NamedTuple, + config, + dt, +) + soilinfreduction = get(config.model, "soilinfreduction", false)::Bool + modelsnow = get(config.model, "snow", false)::Bool + transfermethod = get(config.model, "transfermethod", false)::Bool + ust = get(config.model, "whole_ust_available", false)::Bool # should be removed from optional setting and code? + + (; snow, runoff, demand) = external_models + (; temperature) = atmospheric_forcing + (; water_flux_surface) = model.boundary_conditions + v = model.variables + p = model.parameters + + ustoredepth!(model) + @. v.ustorecapacity = p.soilwatercapacity - v.satwaterdepth - v.ustoredepth + @. v.ustorelayerthickness = set_layerthickness(v.zi, p.sumlayers, p.act_thickl) + @. v.n_unsatlayers = number_of_active_layers(v.ustorelayerthickness) + + # infiltration + soil_temperature!(model, snow, temperature) + infiltration_reduction_factor!( + model; + modelsnow = modelsnow, + soilinfreduction = soilinfreduction, + ) + infiltration!(model) + # unsaturated zone flow + unsaturated_zone_flow!(model; transfermethod = transfermethod) + # soil evaporation and transpiration + soil_evaporation!(model) + transpiration!(model, dt; ust = ust) + # actual infiltration and excess water + actual_infiltration!(model) + @. v.excesswater = water_flux_surface - v.actinfilt - v.infiltexcess + actual_infiltration_soil_path!(model) + @. v.excesswatersoil = + max(water_flux_surface * (1.0 - p.pathfrac) - v.actinfiltsoil, 0.0) + @. v.excesswaterpath = max(water_flux_surface * p.pathfrac - v.actinfiltpath, 0.0) + # recompute the unsaturated store and ustorecapacity (for capillary flux) + ustoredepth!(model) + @. v.ustorecapacity = p.soilwatercapacity - v.satwaterdepth - v.ustoredepth + # capillary flux and leakage + capillary_flux!(model) + leakage!(model) + # recharge rate to the saturated store + @. v.recharge = + (v.transfer - v.actcapflux - v.actleakage - v.actevapsat - v.soilevapsat) + # total actual evapotranspiration + v.actevap .= + v.soilevap .+ v.transpiration .+ runoff.variables.ae_openw_r .+ + runoff.variables.ae_openw_l .+ get_evaporation(demand.paddy) + return nothing +end + +""" + update!(model::SbmSoilModel, external_models::NamedTuple) + +Update the SBM soil model for a single timestep based on the update of a subsurface flow +model, resulting in a change in water table depth and an exfiltration rate `exfiltwater`. + +The water table depth `zi`, unsaturated storage `ustorelayerdepth`, water exfiltrating from +the unsaturated store `exfiltustore`, land `runoff` and `net_runoff`, the saturated store +`satwaterdepth` and the water exfiltrating during saturation excess conditions +`exfiltsatwater` are updated. Addionally, volumetric water content per soil layer and for +the root zone are updated. +""" +function update!(model::SbmSoilModel, external_models::NamedTuple) + (; runoff, demand, subsurface) = external_models + (; runoff_land, ae_openw_l) = runoff.variables + p = model.parameters + v = model.variables + + zi = get_water_depth(subsurface) * 1000.0 + exfiltsatwater = get_exfiltwater(subsurface) * 1000.0 + rootingdepth = get_rootingdepth(model) + + n = length(model.variables.zi) + threaded_foreach(1:n; basesize = 1000) do i + ustorelayerthickness = set_layerthickness(zi[i], p.sumlayers[i], p.act_thickl[i]) + n_unsatlayers = number_of_active_layers(ustorelayerthickness) + # exfiltration from ustore + ustorelayerdepth = v.ustorelayerdepth[i] + exfiltustore = 0.0 + for k in v.n_unsatlayers[i]:-1:1 + if k <= n_unsatlayers + exfiltustore = max( + 0, + ustorelayerdepth[k] - + ustorelayerthickness[k] * (p.theta_s[i] - p.theta_r[i]), + ) + else + exfiltustore = ustorelayerdepth[k] + end + ustorelayerdepth = + setindex(ustorelayerdepth, ustorelayerdepth[k] - exfiltustore, k) + if k > 1 + ustorelayerdepth = setindex( + ustorelayerdepth, + ustorelayerdepth[k - 1] + exfiltustore, + k - 1, + ) + end + end + + ustoredepth = sum(@view ustorelayerdepth[1:n_unsatlayers]) + sbm_runoff = + exfiltustore + + exfiltsatwater[i] + + v.excesswater[i] + + runoff_land[i] + + v.infiltexcess[i] + + # volumetric water content per soil layer and root zone + vwc = v.vwc[i] + vwc_perc = v.vwc_perc[i] + for k in 1:p.nlayers[i] + if k <= n_unsatlayers + vwc = setindex( + vwc, + ( + ustorelayerdepth[k] + + (p.act_thickl[i][k] - ustorelayerthickness[k]) * + (p.theta_s[i] - p.theta_r[i]) + ) / p.act_thickl[i][k] + p.theta_r[i], + k, + ) + else + vwc = setindex(vwc, p.theta_s[i], k) + end + vwc_perc = setindex(vwc_perc, (vwc[k] / p.theta_s[i]) * 100.0, k) + end + + rootstore_unsat = 0 + for k in 1:n_unsatlayers + rootstore_unsat = + rootstore_unsat + + min( + 1.0, + ( + max(0.0, rootingdepth[i] - p.sumlayers[i][k]) / + ustorelayerthickness[k] + ), + ) * ustorelayerdepth[k] + end + + rootstore_sat = max(0.0, rootingdepth[i] - zi[i]) * (p.theta_s[i] - p.theta_r[i]) + rootstore = rootstore_sat + rootstore_unsat + vwc_root = rootstore / rootingdepth[i] + p.theta_r[i] + vwc_percroot = (vwc_root / p.theta_s[i]) * 100.0 + + satwaterdepth = (p.soilthickness[i] - zi[i]) * (p.theta_s[i] - p.theta_r[i]) + + # update the outputs and states + v.n_unsatlayers[i] = n_unsatlayers + v.ustorelayerdepth[i] = ustorelayerdepth + v.ustoredepth[i] = ustoredepth + v.satwaterdepth[i] = satwaterdepth + v.exfiltsatwater[i] = exfiltsatwater[i] + v.exfiltustore[i] = exfiltustore + v.runoff[i] = sbm_runoff + v.vwc[i] = vwc + v.vwc_perc[i] = vwc_perc + v.rootstore[i] = rootstore + v.vwc_root[i] = vwc_root + v.vwc_percroot[i] = vwc_percroot + v.zi[i] = zi[i] + end + # update runoff and net_runoff (the runoff rate depends on the presence of paddy fields + # and the h_max parameter of a paddy field) + update_runoff!(demand.paddy, v.runoff) + @. v.net_runoff = v.runoff - ae_openw_l + return nothing +end + +# wrapper method +get_rootingdepth(model::SbmSoilModel) = + model.parameters.vegetation_parameter_set.rootingdepth \ No newline at end of file diff --git a/src/soil/soil_process.jl b/src/soil/soil_process.jl new file mode 100644 index 000000000..845e235c4 --- /dev/null +++ b/src/soil/soil_process.jl @@ -0,0 +1,294 @@ +""" + infiltration( + potential_infiltration, + pathfrac, + infiltcapsoil, + infiltcappath, + ustorecapacity, + f_infiltration_reduction, + ) + +Soil infiltration based on infiltration capacity soil `infiltcapsoil`, infiltration capacity +paved area `infiltcappath` and unsaturated store capacity `ustorecapacity`. The infiltration +capacity of the soil and paved area can be reduced with the infiltration reduction factor +`f_infiltration_reduction`. +""" +function infiltration( + potential_infiltration, + pathfrac, + infiltcapsoil, + infiltcappath, + ustorecapacity, + f_infiltration_reduction, +) + # First determine if the soil infiltration capacity can deal with the amount of water + # split between infiltration in undisturbed soil and paved areas (path). + soilinf = potential_infiltration * (1.0 - pathfrac) + pathinf = potential_infiltration * pathfrac + + max_infiltsoil = min(infiltcapsoil * f_infiltration_reduction, soilinf) + max_infiltpath = min(infiltcappath * f_infiltration_reduction, pathinf) + infiltsoilpath = min(max_infiltpath + max_infiltsoil, max(0.0, ustorecapacity)) + + infiltexcess = (soilinf - max_infiltsoil) + (pathinf - max_infiltpath) + + return infiltsoilpath, infiltexcess +end + +""" + unsatzone_flow_layer(usd, kv_z, l_sat, c) + +Assuming a unit head gradient, the transfer of water from an unsaturated store layer `usd` +is controlled by the vertical saturated hydraulic conductivity `kv_z` (bottom layer or water +table), the effective saturation degree of the layer (ratio `usd` and `l_sat`), and a +Brooks-Corey power coefficient `c`. +""" +function unsatzone_flow_layer(usd, kv_z, l_sat, c) + if usd <= 0.0 + return 0.0, 0.0 + end + sum_ast = 0.0 + # first transfer soil water > maximum soil water capacity layer (iteration is not + # required because of steady theta (usd)) + st = kv_z * min(pow(usd / l_sat, c), 1.0) + st_sat = max(0.0, usd - l_sat) + usd -= min(st, st_sat) + sum_ast = sum_ast + min(st, st_sat) + ast = max(min(st - min(st, st_sat), usd), 0.0) + # number of iterations (to reduce "overshooting") based on fixed maximum change in soil + # water per iteration step (0.2 mm / model timestep) + its = Int(cld(ast, 0.2)) + for _ in 1:its + st = (kv_z / its) * min(pow(usd / l_sat, c), 1.0) + ast = min(st, usd) + usd -= ast + sum_ast += ast + end + + return usd, sum_ast +end + +""" + unsatzone_flow_sbm( + ustorelayerdepth, + soilwatercapacity, + satwaterdepth, + kv_z, + usl, + theta_s, + theta_r, + ) + +The transfer of water from the unsaturated store `ustorelayerdepth` to the saturated store +`satwaterdepth` is controlled by the vertical saturated hydraulic conductivity `kv_z` at the +water table and the ratio between `ustorelayerdepth` and the saturation deficit +(`soilwatercapacity` minus `satwaterdepth`). This is the original Topog_SBM vertical +transfer formulation. + +""" +function unsatzone_flow_sbm( + ustorelayerdepth, + soilwatercapacity, + satwaterdepth, + kv_z, + usl, + theta_s, + theta_r, +) + sd = soilwatercapacity - satwaterdepth + if sd <= 0.00001 + ast = 0.0 + else + st = kv_z * min(ustorelayerdepth, usl * (theta_s - theta_r)) / sd + ast = min(st, ustorelayerdepth) + ustorelayerdepth = ustorelayerdepth - ast + end + + return ustorelayerdepth, ast +end + +""" + vwc_brooks_corey(h, hb, theta_s, theta_r, c) + +Return volumetric water content based on the Brooks-Corey soil hydraulic model. +""" +function vwc_brooks_corey(h, hb, theta_s, theta_r, c) + if h < hb + par_lambda = 2.0 / (c - 3.0) + vwc = (theta_s - theta_r) * pow(hb / h, par_lambda) + theta_r + else + vwc = theta_s + end + return vwc +end + +""" + head_brooks_corey(vwc, theta_s, theta_r, c, hb) + +Return soil water pressure head based on the Brooks-Corey soil hydraulic model. +""" +function head_brooks_corey(vwc, theta_s, theta_r, c, hb) + par_lambda = 2.0 / (c - 3.0) + # Note that in the original formula, theta_r is extracted from vwc, but theta_r is not + # part of the numerical vwc calculation + h = hb / (pow(((vwc) / (theta_s - theta_r)), (1.0 / par_lambda))) + h = min(h, hb) + return h +end + +""" + feddes_h3(h3_high, h3_low, tpot, Δt) + +Return soil water pressure head `h3` of Feddes root water uptake reduction function. +""" +function feddes_h3(h3_high, h3_low, tpot, Δt) + # value of h3 is a function of potential transpiration [mm/d] + tpot_daily = tpot * (tosecond(basetimestep) / Δt) + if (tpot_daily >= 0.0) && (tpot_daily <= 1.0) + h3 = h3_low + elseif (tpot_daily > 1.0) && (tpot_daily < 5.0) + h3 = h3_high + ((h3_low - h3_high) * (5.0 - tpot_daily)) / (5.0 - 1.0) + else + h3 = h3_high + end + return h3 +end + +""" + rwu_reduction_feddes(h, h1, h2, h3, h4, alpha_h1) + +Root water uptake reduction factor based on Feddes. +""" +function rwu_reduction_feddes(h, h1, h2, h3, h4, alpha_h1) + # root water uptake reduction coefficient alpha (see also Feddes et al., 1978) + if alpha_h1 == 0.0 + if (h <= h4) || (h > h1) + alpha = 0.0 + elseif (h > h2) && (h <= h1) + alpha = (h - h1) / (h2 - h1) + elseif (h >= h3) && (h <= h2) + alpha = 1.0 + elseif (h >= h4) && (h < h3) + alpha = (h - h4) / (h3 - h4) + end + else + if h <= h4 + alpha = 0.0 + elseif h >= h3 + alpha = 1.0 + elseif (h >= h4) && (h < h3) + alpha = (h - h4) / (h3 - h4) + end + end + return alpha +end + +""" + soil_temperature(tsoil, w_soil, temperature)) + +Return the near surface soil temperature `tsoil` based on the near surface soil temperature +`tsoil_prev` at the previous timestep, and the difference between air `temperature` and near +surface soil temperature `tsoil_prev` at the previous timestep, weighted with the weighting +coefficient `w_soil` (Wigmosta et al., 2009). +""" +function soil_temperature(tsoil_prev, w_soil, temperature) + tsoil = tsoil_prev + w_soil * (temperature - tsoil_prev) + return tsoil +end + +""" + infiltration_reduction_factor( + tsoil, + cf_soil; + modelsnow = false, + soilinfreduction = false, + ) + +When both `modelsnow` and `soilinfreduction` are `true` an infiltration reduction factor +`f_infiltration_reduction` is computed. The infiltration reduction factor is based on the +near surface soil temperature `tsoil`, parameter `cf_soil` and a s-curve to make a smooth +transition of `f_infiltration_reduction` as a function of `tsoil` and `cf_soil`. Otherwise, +`f_infiltration_reduction` is set to 1.0. +""" +function infiltration_reduction_factor( + tsoil, + cf_soil; + modelsnow = false, + soilinfreduction = false, +) + if modelsnow && soilinfreduction + bb = 1.0 / (1.0 - cf_soil) + f_infiltration_reduction = scurve(tsoil, Float(0.0), bb, Float(8.0)) + cf_soil + else + f_infiltration_reduction = 1.0 + end + return f_infiltration_reduction +end + +"Return soil evaporation from the unsaturated store" +function soil_evaporation_unsatured_store( + potential_soilevaporation, + ustorelayerdepth, + ustorelayerthickness, + n_unsatlayers, + zi, + theta_effective, +) + if n_unsatlayers == 0 + soilevapunsat = 0.0 + elseif n_unsatlayers == 1 + # Check if groundwater level lies below the surface + soilevapunsat = + potential_soilevaporation * min(1.0, ustorelayerdepth / (zi * theta_effective)) + else + # In case first layer contains no saturated storage + soilevapunsat = + potential_soilevaporation * + min(1.0, ustorelayerdepth / (ustorelayerthickness * (theta_effective))) + end + return soilevapunsat +end + +"Return soil evaporation from the saturated store" +function soil_evaporation_satured_store( + potential_soilevaporation, + n_unsatlayers, + layerthickness, + zi, + theta_effective, +) + if n_unsatlayers == 0 || n_unsatlayers == 1 + soilevapsat = + potential_soilevaporation * min(1.0, (layerthickness - zi) / layerthickness) + soilevapsat = min(soilevapsat, (layerthickness - zi) * theta_effective) + else + soilevapsat = 0.0 + end + return soilevapsat +end + +"Return actual infiltration rate for soil `actinfiltsoil` and paved area `actinfiltpath`" +function actual_infiltration_soil_path( + potential_infiltration, + actinfilt, + pathfrac, + infiltcapsoil, + infiltcappath, + f_infiltration_reduction, +) + soilinf = potential_infiltration * (1.0 - pathfrac) + pathinf = potential_infiltration * pathfrac + if actinfilt > 0.0 + max_infiltsoil = min(infiltcapsoil * f_infiltration_reduction, soilinf) + max_infiltpath = min(infiltcappath * f_infiltration_reduction, pathinf) + + actinfiltsoil = actinfilt * max_infiltsoil / (max_infiltpath + max_infiltsoil) + actinfiltpath = actinfilt * max_infiltpath / (max_infiltpath + max_infiltsoil) + + else + actinfiltsoil = 0.0 + actinfiltpath = 0.0 + end + + return actinfiltsoil, actinfiltpath +end \ No newline at end of file diff --git a/src/states.jl b/src/states.jl index d33427886..4cd991654 100644 --- a/src/states.jl +++ b/src/states.jl @@ -1,60 +1,73 @@ """ - get_vertical_states(model_type; snow = false, glacier = false) + get_snow_states(model_type::AbstractString) -Function to extract all required vertical states, given a certain model type. Passes the snow -and glacier options only for the `sbm` model_type. Returns a tuple with the required states -(internal names as symbols) +Extract required snow model states, given a certain `model_type`. Returns a tuple with the +required states (internal names as symbols). """ -function get_vertical_states(model_type::AbstractString; snow = false, glacier = false) +function get_snow_states(model_type::AbstractString) if model_type == "sbm" || model_type == "sbm_gwf" - if snow && glacier - vertical_states = ( - :satwaterdepth, - :snow, - :tsoil, - :ustorelayerdepth, - :snowwater, - :canopystorage, - :glacierstore, - ) - elseif snow - vertical_states = ( - :satwaterdepth, - :snow, - :tsoil, - :ustorelayerdepth, - :snowwater, - :canopystorage, - ) + states = (:snow_storage, :snow_water) + elseif model_type == "sediment" + states = () + else + throw(ArgumentError("Unknown model_type provided (`$model_type`)")) + end + return states +end + +""" + get_glacier_states(model_type::AbstractString) + +Extract required glacier model states, given a certain `model_type`. Returns a tuple with +the required states (internal names as symbols). +""" +function get_glacier_states(model_type::AbstractString) + if model_type == "sbm" || model_type == "sbm_gwf" + states = (:glacier_store,) + elseif model_type == "sediment" + states = () + else + throw(ArgumentError("Unknown model_type provided (`$model_type`)")) + end + return states +end + +""" + get_interception_states(model_type::AbstractString) + +Extract required interception model states, given a certain `model_type`. Returns a tuple +with the required states (internal names as symbols). +""" +function get_interception_states(model_type::AbstractString) + if model_type == "sbm" || model_type == "sbm_gwf" + states = (:canopy_storage,) + elseif model_type == "sediment" + states = () + else + throw(ArgumentError("Unknown model_type provided (`$model_type`)")) + end + return states +end + +""" + get_soil_states(model_type::AbstractString; snow = false) + +Extract required soil model states, given a certain `model_type` and whether `snow` is +modelled. Returns a tuple with the required states (internal names as symbols). +""" +function get_soil_states(model_type::AbstractString; snow = false) + if model_type == "sbm" || model_type == "sbm_gwf" + if snow + states = (:satwaterdepth, :tsoil, :ustorelayerdepth) else - vertical_states = (:satwaterdepth, :ustorelayerdepth, :canopystorage) + states = (:satwaterdepth, :ustorelayerdepth) end - elseif model_type == "hbv" - vertical_states = ( - :soilmoisture, - :snow, - :snowwater, - :upperzonestorage, - :lowerzonestorage, - :interceptionstorage, - ) - elseif model_type == "flextopo" - vertical_states = ( - :snow, - :snowwater, - :interceptionstorage, - :hortonpondingstorage, - :hortonrunoffstorage, - :rootzonestorage, - :faststorage, - :slowstorage, - ) elseif model_type == "sediment" - vertical_states = () + states = () else throw(ArgumentError("Unknown model_type provided (`$model_type`)")) end - return vertical_states + return states end """ @@ -76,8 +89,9 @@ function add_to_required_states(required_states::Tuple, key_entry::Tuple, states return required_states end -add_to_required_states(required_states::Tuple, key_entry::Tuple, states::Nothing) = - required_states +function add_to_required_states(required_states::Tuple, key_entry::Tuple, states::Nothing) + return required_states +end """ extract_required_states(config::Config) @@ -100,10 +114,21 @@ function extract_required_states(config::Config) do_paddy = false if haskey(config.model, "water_demand") do_paddy = get(config.model.water_demand, "paddy", false)::Bool - end + end # Extract required stated based on model configuration file - vertical_states = get_vertical_states(model_type; snow = do_snow, glacier = do_glaciers) + if do_snow + snow_states = get_snow_states(model_type) + else + snow_states = () + end + if do_snow && do_glaciers + glacier_states = get_glacier_states(model_type) + else + glacier_states = () + end + interception_states = get_interception_states(model_type) + soil_states = get_soil_states(model_type; snow = do_snow) # Subsurface states if model_type == "sbm_gwf" @@ -175,8 +200,21 @@ function extract_required_states(config::Config) # Build required states in a tuple, similar to the keys in the output of # `ncnames(config.state)` required_states = () - # Add vertical states to dict - required_states = add_to_required_states(required_states, (:vertical,), vertical_states) + # Add snow, glacier, interception and sbm soil states to dict + required_states = + add_to_required_states(required_states, (:vertical, :snow, :variables), snow_states) + required_states = add_to_required_states( + required_states, + (:vertical, :glacier, :variables), + glacier_states, + ) + required_states = add_to_required_states( + required_states, + (:vertical, :interception, :variables), + interception_states, + ) + required_states = + add_to_required_states(required_states, (:vertical, :soil, :variables), soil_states) # Add subsurface states to dict if model_type == "sbm_gwf" key_entry = (:lateral, :subsurface, :flow, :aquifer) @@ -208,7 +246,7 @@ function extract_required_states(config::Config) # Add paddy states to dict required_states = add_to_required_states( required_states, - (:vertical, :paddy), + (:vertical, :demand, :paddy, :variables), paddy_states, ) return required_states diff --git a/src/subdomains.jl b/src/subdomains.jl index 7428a1856..8317c9616 100644 --- a/src/subdomains.jl +++ b/src/subdomains.jl @@ -82,7 +82,7 @@ subbasins without upstream neighbor and distance < `max_dist`) to distance 0 (`o function subbasins_order(g, outlet, max_dist) order = Vector{Vector{Int}}(undef, max_dist + 1) order[1] = [outlet] - for i = 1:max_dist + for i in 1:max_dist v = Vector{Int}() for n in order[i] ups_nodes = inneighbors(g, n) @@ -90,14 +90,14 @@ function subbasins_order(g, outlet, max_dist) append!(v, ups_nodes) end end - order[i+1] = v + order[i + 1] = v end # move subbasins without upstream neighbor (headwater) to index [max_dist+1] - for i = 1:max_dist + for i in 1:max_dist for s in order[i] if isempty(inneighbors(g, s)) - append!(order[max_dist+1], s) + append!(order[max_dist + 1], s) filter!(e -> e ≠ s, order[i]) end end @@ -116,7 +116,7 @@ flow network for each subbasin cell. function graph_from_nodes(graph, subbas, subbas_fill) n = maximum(subbas) g = DiGraph(n) - for i = 1:n + for i in 1:n idx = findall(x -> x == i, subbas) ds_idx = outneighbors(graph, only(idx)) to_node = subbas_fill[ds_idx] @@ -149,7 +149,6 @@ streamorder, toposort, min_sto)`). Subbasins are extracted for each basin outlet - `topo_subbas` topological order per subbasin id stored as `Vector{Vector{Int}}` """ function kinwave_set_subdomains(graph, toposort, index_pit, streamorder, min_sto) - if nthreads() > 1 # extract basins (per outlet/pit), assign unique basin id n_pits = length(index_pit) @@ -169,7 +168,7 @@ function kinwave_set_subdomains(graph, toposort, index_pit, streamorder, min_sto topo_subbas = Vector{Vector{Int}}() index = Vector{Int}() total_subbas = 0 - for i = 1:n_pits + for i in 1:n_pits # extract subbasins per basin, make a graph at the subbasin level, calculate the # maximum distance of this graph, and group and order the subbasin ids from # upstream to downstream @@ -179,7 +178,7 @@ function kinwave_set_subdomains(graph, toposort, index_pit, streamorder, min_sto streamorder_subbas = streamorder[vmap] subbas = subbasins(g, streamorder_subbas, toposort_b, min_sto) subbas_fill = fillnodata_upstream(g, toposort_b, subbas, 0) - n_subbas = max(length(subbas[subbas.>0]), 1) + n_subbas = max(length(subbas[subbas .> 0]), 1) if n_subbas > 1 graph_subbas = graph_from_nodes(g, subbas, subbas_fill) toposort_subbas = topological_sort_by_dfs(graph_subbas) @@ -194,7 +193,7 @@ function kinwave_set_subdomains(graph, toposort, index_pit, streamorder, min_sto end # subbasins need a unique id (in case of multiple basins/outlets in the # kinematic wave domain) - for n = 1:length(v_subbas) + for n in 1:length(v_subbas) v_subbas[n] .= v_subbas[n] .+ total_subbas end total_subbas += n_subbas @@ -204,7 +203,7 @@ function kinwave_set_subdomains(graph, toposort, index_pit, streamorder, min_sto # (subgraph of the corresponding basin graph g), and the indices that match the # subbasin topological order if n_subbas > 1 - for s = 1:n_subbas + for s in 1:n_subbas subbas_s = findall(x -> x == s, subbas_fill) sg, _ = induced_subgraph(g, subbas_s) toposort_sg = topological_sort_by_dfs(sg) @@ -219,8 +218,8 @@ function kinwave_set_subdomains(graph, toposort, index_pit, streamorder, min_sto # reduce the order of subbasin ids by merging groups of subbasins that have the same # index (multiple basins/outlets in the kinematic wave domain) subbas_order = Vector{Vector{Int}}(undef, maximum(index)) - for m = 1:maximum(index) - subbas_order[m] = reduce(vcat, order_subbas[index.==m]) + for m in 1:maximum(index) + subbas_order[m] = reduce(vcat, order_subbas[index .== m]) end else subbas_order = [[1]] diff --git a/src/surfacewater/runoff.jl b/src/surfacewater/runoff.jl new file mode 100644 index 000000000..6203b4020 --- /dev/null +++ b/src/surfacewater/runoff.jl @@ -0,0 +1,145 @@ +abstract type AbstractRunoffModel{T} end + +"Struct for storing open water runoff variables" +@get_units @grid_loc @with_kw struct OpenWaterRunoffVariables{T} + # Runoff from river based on riverfrac [mm Δt⁻¹] + runoff_river::Vector{T} + # Net runoff from river [mm Δt⁻¹] + net_runoff_river::Vector{T} + # Runoff from land based on waterfrac [mm Δt⁻¹] + runoff_land::Vector{T} + # Actual evaporation from open water (land) [mm Δt⁻¹] + ae_openw_l::Vector{T} + # Actual evaporation from river [mm Δt⁻¹] + ae_openw_r::Vector{T} +end + +"Initialize open water runoff model variables" +function OpenWaterRunoffVariables(T::Type{<:AbstractFloat}, n::Int) + return OpenWaterRunoffVariables{T}(; + runoff_river = fill(mv, n), + runoff_land = fill(mv, n), + ae_openw_l = fill(mv, n), + ae_openw_r = fill(mv, n), + net_runoff_river = fill(mv, n), + ) +end + +"Struct for storing open water runoff parameters" +@get_units @grid_loc @with_kw struct OpenWaterRunoffParameters{T} + # Fraction of river [-] + riverfrac::Vector{T} | "-" + # Fraction of open water (excluding rivers) [-] + waterfrac::Vector{T} | "-" +end + +"Initialize open water runoff parameters" +function OpenWaterRunoffParameters(nc, config, inds, riverfrac) + # fraction open water + waterfrac = ncread( + nc, + config, + "vertical.runoff.parameters.waterfrac"; + sel = inds, + defaults = 0.0, + type = Float, + ) + waterfrac = max.(waterfrac .- riverfrac, Float(0.0)) + params = OpenWaterRunoffParameters(; waterfrac = waterfrac, riverfrac = riverfrac) + return params +end + +"Struct for storing open water runoff boundary conditions" +@get_units @grid_loc @with_kw struct OpenWaterRunoffBC{T} + water_flux_surface::Vector{T} + waterlevel_land::Vector{T} | "mm" + waterlevel_river::Vector{T} | "mm" +end + +"Initialize open water runoff boundary conditions" +function OpenWaterRunoffBC(T::Type{<:AbstractFloat}, n::Int) + return OpenWaterRunoffBC{T}(; + water_flux_surface = fill(mv, n), + waterlevel_land = fill(mv, n), + waterlevel_river = zeros(T, n), + ) +end + +"Open water runoff model" +@with_kw struct OpenWaterRunoff{T} <: AbstractRunoffModel{T} + boundary_conditions::OpenWaterRunoffBC{T} + parameters::OpenWaterRunoffParameters{T} + variables::OpenWaterRunoffVariables{T} +end + +"Initialize open water runoff model" +function OpenWaterRunoff(nc, config, inds, riverfrac) + n = length(riverfrac) + vars = OpenWaterRunoffVariables(Float, n) + bc = OpenWaterRunoffBC(Float, n) + params = OpenWaterRunoffParameters(nc, config, inds, riverfrac) + model = + OpenWaterRunoff(; boundary_conditions = bc, parameters = params, variables = vars) + return model +end + +"Return the water flux at the surface (boundary condition) when snow is not modelled" +function get_water_flux_surface!( + water_flux_surface, + snow::NoSnowModel, + glacier, + interception, +) + (; throughfall, stemflow) = interception.variables + @. water_flux_surface = throughfall + stemflow + return nothing +end + +"Return the water flux at the surface (boundary condition) when snow is modelled" +function get_water_flux_surface!( + water_flux_surface, + snow::AbstractSnowModel, + glacier, + interception, +) + water_flux_surface .= + get_runoff(snow) .+ get_glacier_melt(glacier) .* get_glacier_fraction(glacier) + return nothing +end + +"Update boundary conditions of the open water runoff model for a single timestep" +function update_boundary_conditions!( + model::OpenWaterRunoff, + external_models::NamedTuple, + lateral, + network, +) + (; water_flux_surface, waterlevel_river, waterlevel_land) = model.boundary_conditions + inds_riv = network.index_river + (; snow, glacier, interception) = external_models + + get_water_flux_surface!(water_flux_surface, snow, glacier, interception) + + # extract water levels h_av [m] from the land and river domains this is used to limit + # open water evaporation + waterlevel_land .= lateral.land.h_av .* 1000.0 + waterlevel_river[inds_riv] .= lateral.river.h_av .* 1000.0 + return nothing +end + +"Update the open water runoff model for a single timestep" +function update!(model::OpenWaterRunoff, atmospheric_forcing::AtmosphericForcing) + (; potential_evaporation) = atmospheric_forcing + (; runoff_river, net_runoff_river, runoff_land, ae_openw_r, ae_openw_l) = + model.variables + (; riverfrac, waterfrac) = model.parameters + (; water_flux_surface, waterlevel_river, waterlevel_land) = model.boundary_conditions + + @. runoff_river = min(1.0, riverfrac) * water_flux_surface + @. runoff_land = min(1.0, waterfrac) * water_flux_surface + @. ae_openw_r = min(waterlevel_river * riverfrac, riverfrac * potential_evaporation) + @. ae_openw_l = min(waterlevel_land * waterfrac, waterfrac * potential_evaporation) + @. net_runoff_river = runoff_river - ae_openw_r + + return nothing +end \ No newline at end of file diff --git a/src/utils.jl b/src/utils.jl index b81248c9e..40c514ec1 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -17,6 +17,23 @@ const mv = Float(NaN) # timestep that the parameter units are defined in const basetimestep = Second(Day(1)) +""" + scurve(x, a, b, c) + +Sigmoid "S"-shaped curve. + +# Arguments +- `x::Real`: input +- `a::Real`: determines the centre level +- `b::Real`: determines the amplitude of the curve +- `c::Real`: determines the steepness or "stepwiseness" of the curve. + The higher c the sharper the function. A negative c reverses the function. +""" +function scurve(x, a, b, c) + s = one(x) / (b + exp(-c * (x - a))) + return s +end + "Set at indices pit values (default = 5) in a gridded local drainage direction vector" function set_pit_ldd(pits_2d, ldd, indices; pit = 5) pits = pits_2d[indices] @@ -98,7 +115,7 @@ function cell_lengths(y::AbstractVector, cellength::Real, sizeinmetres::Bool) xl .= cellength yl .= cellength else - for i = 1:n + for i in 1:n longlen, latlen = lattometres(y[i]) xl[i] = longlen * cellength yl[i] = latlen * cellength @@ -116,7 +133,7 @@ function river_fraction( ) n = length(river) riverfrac = fill(mv, n) - for i = 1:n + for i in 1:n riverfrac[i] = if river[i] min((riverlength[i] * riverwidth[i]) / (xl[i] * yl[i]), 1.0) else @@ -127,7 +144,7 @@ function river_fraction( end """ - set_states(instate_path, model, state_ncnames; ) + set_states!(instate_path, model, state_ncnames; ) Read states contained in `Dict` `state_ncnames` from netCDF file located in `instate_path`, and set states in `model` object. Active cells are selected with the corresponding network's @@ -136,7 +153,7 @@ and set states in `model` object. Active cells are selected with the correspondi # Arguments - `type = nothing`: type to convert data to after reading. By default no conversion is done. """ -function set_states(instate_path, model; type = nothing, dimname = nothing) +function set_states!(instate_path, model; type = nothing, dimname = nothing) (; network, config) = model # Check if required states are covered @@ -154,8 +171,6 @@ function set_states(instate_path, model; type = nothing, dimname = nothing) if dims == 4 if dimname == :layer dimensions = (x = :, y = :, layer = :, time = 1) - elseif dimname == :classes - dimensions = (x = :, y = :, classes = :, time = 1) else error("Unrecognized dimension name $dimname") end @@ -195,6 +210,7 @@ function set_states(instate_path, model; type = nothing, dimname = nothing) end end end + return nothing end """ @@ -249,8 +265,6 @@ function ncread( # first timestep), that is later updated with the `update_cyclic!` function. if isnothing(dimname) dim_sel = (x = :, y = :, time = 1) - elseif dimname == :classes - dim_sel = (x = :, y = :, classes = :, time = 1) elseif dimname == :layer dim_sel = (x = :, y = :, layer = :, time = 1) elseif dimname == :flood_depth @@ -297,7 +311,7 @@ function ncread( # provided through the TOML file. if length(mod.index) > 1 # if index, scale and offset is provided in the TOML as a list. - for i = 1:length(mod.index) + for i in 1:length(mod.index) A[:, :, mod.index[i]] = A[:, :, mod.index[i]] .* mod.scale[i] .+ mod.offset[i] end @@ -344,25 +358,27 @@ function ncread( end """ - set_layerthickness(d::Real, sl::SVector) + set_layerthickness(reference_depth::Real, cum_depth::SVector, thickness::SVector) -Calculate actual soil thickness of layers based on a reference depth (e.g. soil depth or water table depth) `d`, -a SVector `sl` with cumulative soil depth starting at soil surface (0), and a SVector `tl` with actual thickness -per soil layer. +Calculate actual soil thickness of layers based on a reference depth (e.g. soil depth or +water table depth) `reference_depth`, a SVector `cum_depth` with cumulative soil depth starting +at soil surface (0), and a SVector `thickness` with thickness per soil layer. """ -function set_layerthickness(d::Real, sl::SVector, tl::SVector) - - act_d = tl .* mv - for i = 1:length(act_d) - if d > sl[i+1] - act_d = setindex(act_d, tl[i], i) - elseif d - sl[i] > 0.0 - act_d = setindex(act_d, d - sl[i], i) +function set_layerthickness(reference_depth::Real, cum_depth::SVector, thickness::SVector) + thicknesslayers = thickness .* mv + for i in 1:length(thicknesslayers) + if reference_depth > cum_depth[i + 1] + thicknesslayers = setindex(thicknesslayers, thickness[i], i) + elseif reference_depth - cum_depth[i] > 0.0 + thicknesslayers = setindex(thicknesslayers, reference_depth - cum_depth[i], i) end end + return thicknesslayers +end - nlayers = length(act_d) - sum(isnan.(act_d)) - return act_d, nlayers +function number_of_active_layers(thickness::SVector) + nlayers = length(thickness) - sum(isnan.(thickness)) + return nlayers end """ @@ -439,10 +455,10 @@ function sum_at(f::Function, inds, T) end # https://juliaarrays.github.io/StaticArrays.jl/latest/pages/api/#Arrays-of-static-arrays-1 -function svectorscopy(x::Matrix{T}, ::Val{N}) where {T,N} +function svectorscopy(x::Matrix{T}, ::Val{N}) where {T, N} size(x, 1) == N || error("sizes mismatch") isbitstype(T) || error("use for bitstypes only") - copy(reinterpret(SVector{N,T}, vec(x))) + return copy(reinterpret(SVector{N, T}, vec(x))) end """ @@ -501,8 +517,8 @@ julia> tosecond(Day(1)) """ tosecond(x::Hour) = Float64(Dates.value(Second(x))) tosecond(x::Minute) = Float64(Dates.value(Second(x))) -tosecond(x::T) where {T<:DatePeriod} = Float64(Dates.value(Second(x))) -tosecond(x::T) where {T<:TimePeriod} = x / convert(T, Second(1)) +tosecond(x::T) where {T <: DatePeriod} = Float64(Dates.value(Second(x))) +tosecond(x::T) where {T <: TimePeriod} = x / convert(T, Second(1)) """ adjacent_nodes_at_link(graph) @@ -523,7 +539,7 @@ function adjacent_links_at_node(graph, nodes_at_link) nodes = vertices(graph) src_link = Vector{Int}[] dst_link = copy(src_link) - for i = 1:nv(graph) + for i in 1:nv(graph) push!(src_link, findall(isequal(nodes[i]), nodes_at_link.dst)) push!(dst_link, findall(isequal(nodes[i]), nodes_at_link.src)) end @@ -537,6 +553,7 @@ function add_vertex_edge_graph!(graph, pits) add_vertex!(graph) add_edge!(graph, v, n + i) end + return nothing end """ @@ -560,7 +577,6 @@ function set_effective_flowwidth!( waterbody, inds_rev_riv, ) - toposort = topological_sort_by_dfs(graph_riv) n = length(we_x) for v in toposort @@ -610,6 +626,7 @@ function set_effective_flowwidth!( we_x[idx] = waterbody[v] ? 0.0 : max(we_x[idx] - 0.5 * w, 0.0) end end + return nothing end "Return julian day of year (leap days are not counted)" @@ -622,7 +639,7 @@ end "Partition indices with at least size `basesize`" function _partition(xs::Integer, basesize::Integer) n = Int(max(1, xs ÷ basesize)) - return (Int(1 + ((i - 1) * xs) ÷ n):Int((i * xs) ÷ n) for i = 1:n) + return (Int(1 + ((i - 1) * xs) ÷ n):Int((i * xs) ÷ n) for i in 1:n) end """ @@ -658,174 +675,252 @@ function threaded_foreach(f, x::AbstractArray; basesize::Integer) end """ - hydraulic_conductivity_at_depth(sbm::SBM, z, i, ksat_profile) + hydraulic_conductivity_at_depth(p::KvExponential, kvfrac, z, i, n) + hydraulic_conductivity_at_depth(p::KvExponentialConstant, kvfrac, z, i, n) + hydraulic_conductivity_at_depth(p::KvLayered, kvfrac, z, i, n) + hydraulic_conductivity_at_depth(p::KvLayeredExponential, kvfrac, z, i, n) -Return vertical hydraulic conductivity `kv_z` for soil layer `n` at depth `z` for vertical -concept `SBM` (at index `i`) based on hydraulic conductivity profile `ksat_profile`. +Return vertical hydraulic conductivity `kv_z` at depth `z` for index `i` using muliplication +factor `kv_frac` at soil layer `n` and vertical hydraulic conductivity profile `p`. """ -function hydraulic_conductivity_at_depth(sbm::SBM, z, i, n, ksat_profile) - if ksat_profile == "exponential" - kv_z = sbm.kvfrac[i][n] * sbm.kv_0[i] * exp(-sbm.f[i] * z) - elseif ksat_profile == "exponential_constant" - if z < sbm.z_exp[i] - kv_z = sbm.kvfrac[i][n] * sbm.kv_0[i] * exp(-sbm.f[i] * z) - else - kv_z = sbm.kvfrac[i][n] * sbm.kv_0[i] * exp(-sbm.f[i] * sbm.z_exp[i]) - end - elseif ksat_profile == "layered" - kv_z = sbm.kvfrac[i][n] * sbm.kv[i][n] - elseif ksat_profile == "layered_exponential" - if z < sbm.z_layered[i] - kv_z = sbm.kvfrac[i][n] * sbm.kv[i][n] - else - n = sbm.nlayers_kv[i] - kv_z = sbm.kvfrac[i][n] * sbm.kv[i][n] * exp(-sbm.f[i] * (z - sbm.z_layered[i])) - end +function hydraulic_conductivity_at_depth(p::KvExponential, kvfrac, z, i, n) + kv_z = kvfrac[i][n] * p.kv_0[i] * exp(-p.f[i] * z) + return kv_z +end + +function hydraulic_conductivity_at_depth(p::KvExponentialConstant, kvfrac, z, i, n) + (; kv_0, f) = p.exponential + if z < p.z_exp[i] + kv_z = kvfrac[i][n] * kv_0[i] * exp(-f[i] * z) else - error("unknown ksat_profile") + kv_z = kvfrac[i][n] * kv_0[i] * exp(-f[i] * p.z_exp[i]) end return kv_z end -""" - kh_layered_profile(sbm::SBM, khfrac, z, i, ksat_profile) +function hydraulic_conductivity_at_depth(p::KvLayered, kvfrac, z, i, n) + kv_z = kvfrac[i][n] * p.kv[i][n] + return kv_z +end + +function hydraulic_conductivity_at_depth(p::KvLayeredExponential, kvfrac, z, i, n) + return if z < p.z_layered[i] + kvfrac[i][n] * p.kv[i][n] + else + n = p.nlayers_kv[i] + kvfrac[i][n] * p.kv[i][n] * exp(-p.f[i] * (z - p.z_layered[i])) + end +end -Return equivalent horizontal hydraulic conductivity `kh` [m d⁻¹] for a layered soil profile -of vertical concept `SBM` (at index `i`) based on multiplication factor `khfrac` [-], water -table depth `z` [mm] and hydraulic conductivity profile `ksat_profile`. """ -function kh_layered_profile(sbm::SBM, khfrac, i, ksat_profile) + kh_layered_profile!(soil::SbmSoilModel, subsurface::LateralSSF, kv_profile::KvLayered, dt) + kh_layered_profile!(soil::SbmSoilModel, subsurface::LateralSSF, kv_profile::KvLayeredExponential, dt) - m = sbm.nlayers[i] - t_factor = (tosecond(basetimestep) / sbm.dt) - if (sbm.soilthickness[i] - sbm.zi[i]) > 0.0 - transmissivity = 0.0 - sumlayers = @view sbm.sumlayers[i][2:end] - n = max(sbm.n_unsatlayers[i], 1) +Compute equivalent horizontal hydraulic conductivity `kh` [m d⁻¹] using vertical hydraulic +conductivity profile `kv_profile`. +""" +function kh_layered_profile!( + soil::SbmSoilModel, + subsurface::LateralSSF, + kv_profile::KvLayered, + dt, +) + (; nlayers, sumlayers, act_thickl, soilthickness) = soil.parameters + (; n_unsatlayers, zi) = soil.variables + (; kh) = subsurface.kh_profile + (; khfrac) = subsurface + + t_factor = (tosecond(basetimestep) / dt) + for i in eachindex(kh) + m = nlayers[i] + + if soilthickness[i] > zi[i] + transmissivity = 0.0 + _sumlayers = @view sumlayers[i][2:end] + n = max(n_unsatlayers[i], 1) + transmissivity += (_sumlayers[n] - zi[i]) * kv_profile.kv[i][n] + n += 1 + while n <= m + transmissivity += act_thickl[i][n] * kv_profile.kv[i][n] + n += 1 + end + # convert units for kh [m d⁻¹] computation (transmissivity [mm² Δt⁻¹], soilthickness + # [mm] and zi [mm]) + kh[i] = + 0.001 * (transmissivity / (soilthickness[i] - zi[i])) * t_factor * khfrac[i] + else + kh[i] = 0.001 * kv_profile.kv[i][m] * t_factor * khfrac[i] + end + end + return nothing +end - if ksat_profile == "layered" - transmissivity += (sumlayers[n] - sbm.zi[i]) * sbm.kv[i][n] - elseif ksat_profile == "layered_exponential" - if sbm.zi[i] >= sbm.z_layered[i] - zt = sbm.soilthickness[i] - sbm.z_layered[i] - j = sbm.nlayers_kv[i] +function kh_layered_profile!( + soil::SbmSoilModel, + subsurface::LateralSSF, + kv_profile::KvLayeredExponential, + dt, +) + (; nlayers, sumlayers, act_thickl, soilthickness) = soil.parameters + (; nlayers_kv, z_layered, kv, f) = kv_profile + (; n_unsatlayers, zi) = soil.variables + (; kh) = subsurface.kh_profile + (; khfrac) = subsurface + t_factor = (tosecond(basetimestep) / dt) + + for i in eachindex(kh) + m = nlayers[i] + + if soilthickness[i] > zi[i] + transmissivity = 0.0 + n = max(n_unsatlayers[i], 1) + if zi[i] >= z_layered[i] + zt = soilthickness[i] - z_layered[i] + j = nlayers_kv[i] transmissivity += - sbm.kv[i][j] / sbm.f[i] * - (exp(-sbm.f[i] * (sbm.zi[i] - sbm.z_layered[i])) - exp(-sbm.f[i] * zt)) + kv[i][j] / f[i] * + (exp(-f[i] * (zi[i] - z_layered[i])) - exp(-f[i] * zt)) n = m else - transmissivity += (sumlayers[n] - sbm.zi[i]) * sbm.kv[i][n] + _sumlayers = @view sumlayers[i][2:end] + transmissivity += (_sumlayers[n] - zi[i]) * kv[i][n] end - else - error("unknown ksat_profile, `layered` or `layered_exponential` are allowed") - end - n += 1 - while n <= m - if ksat_profile == "layered" - transmissivity += sbm.act_thickl[i][n] * sbm.kv[i][n] - elseif ksat_profile == "layered_exponential" - if n > sbm.nlayers_kv[i] - zt = sbm.soilthickness[i] - sbm.z_layered[i] - j = sbm.nlayers_kv[i] - transmissivity += sbm.kv[i][j] / sbm.f[i] * (1.0 - exp(-sbm.f[i] * zt)) + n += 1 + while n <= m + if n > nlayers_kv[i] + zt = soilthickness[i] - z_layered[i] + j = nlayers_kv[i] + transmissivity += kv[i][j] / f[i] * (1.0 - exp(-f[i] * zt)) n = m else - transmissivity += sbm.act_thickl[i][n] * sbm.kv[i][n] + transmissivity += act_thickl[i][n] * kv[i][n] end + n += 1 end - n += 1 - end - # convert units for kh [m d⁻¹] computation (transmissivity [mm² Δt⁻¹], soilthickness - # [mm] and zi [mm]) - kh = - 0.001 * - (transmissivity / (sbm.soilthickness[i] - sbm.zi[i])) * - t_factor * - khfrac - else - if ksat_profile == "layered" - kh = 0.001 * sbm.kv[i][m] * t_factor * khfrac - elseif ksat_profile == "layered_exponential" - if sbm.zi[i] >= sbm.z_layered[i] - j = sbm.nlayers_kv[i] - kh = + # convert units for kh [m d⁻¹] computation (transmissivity [mm² Δt⁻¹], soilthickness + # [mm] and zi [mm]) + kh[i] = + 0.001 * (transmissivity / (soilthickness[i] - zi[i])) * t_factor * khfrac[i] + else + if zi[i] >= z_layered[i] + j = nlayers_kv[i] + kh[i] = 0.001 * - sbm.kv[i][j] * - exp(-sbm.f[i] * (sbm.zi[i] - sbm.z_layered[i])) * - khfrac * + kv[i][j] * + exp(-f[i] * (zi[i] - z_layered[i])) * + khfrac[i] * t_factor else - kh = 0.001 * sbm.kv[i][m] * t_factor * khfrac + kh[i] = 0.001 * kv[i][m] * t_factor * khfrac[i] end - else - error("unknown ksat_profile, `layered` or `layered_exponential` are allowed") end end - return kh -end - -"Initialize lateral subsurface variables `ssf` and `ssfmax` with `ksat_profile` `exponential`" -function initialize_lateralssf_exp!(ssf::LateralSSF) - for i in eachindex(ssf.ssf) - ssf.ssfmax[i] = - ((ssf.kh_0[i] * ssf.slope[i]) / ssf.f[i]) * - (1.0 - exp(-ssf.f[i] * ssf.soilthickness[i])) - ssf.ssf[i] = - ((ssf.kh_0[i] * ssf.slope[i]) / ssf.f[i]) * - (exp(-ssf.f[i] * ssf.zi[i]) - exp(-ssf.f[i] * ssf.soilthickness[i])) * - ssf.dw[i] - end + return nothing end -"Initialize lateral subsurface variables `ssf` and `ssfmax` with `ksat_profile` `exponential_constant`" -function initialize_lateralssf_exp_const!(ssf::LateralSSF) - ssf_constant = @. ssf.khfrac * - ssf.kh_0 * - exp(-ssf.f * ssf.z_exp) * - ssf.slope * - (ssf.soilthickness - ssf.z_exp) - for i in eachindex(ssf.ssf) - ssf.ssfmax[i] = - ((ssf.khfrac[i] * ssf.kh_0[i] * ssf.slope[i]) / ssf.f[i]) * - (1.0 - exp(-ssf.f[i] * ssf.z_exp[i])) + ssf_constant[i] - if ssf.zi[i] < ssf.z_exp[i] - ssf.ssf[i] = +kh_layered_profile!( + soil::SbmSoilModel, + subsurface::LateralSSF, + kv_profile::Union{KvExponential, KvExponentialConstant}, + dt, +) = nothing + +""" + initialize_lateralssf!(model::LateralSSF, kh_profile::KhExponential) + initialize_lateralssf!(model::LateralSSF, kh_profile::KhExponentialConstant) + +Initialize lateral subsurface variables `ssf` and `ssfmax` using horizontal hydraulic +conductivity profile `kh_profile`. +""" +function initialize_lateralssf!(model::LateralSSF, kh_profile::KhExponential) + (; kh_0, f) = kh_profile + (; ssf, ssfmax, zi, slope, soilthickness, dw) = model + + @. ssfmax = ((kh_0 * slope) / f) * (1.0 - exp(-f * soilthickness)) + @. ssf = ((kh_0 * slope) / f) * (exp(-f * zi) - exp(-f * soilthickness)) * dw + return nothing +end + +function initialize_lateralssf!(model::LateralSSF, kh_profile::KhExponentialConstant) + (; kh_0, f) = kh_profile.exponential + (; z_exp) = kh_profile + (; ssf, ssfmax, zi, slope, soilthickness, dw) = model + ssf_constant = @. kh_0 * exp(-f * z_exp) * slope * (soilthickness - z_exp) + for i in eachindex(ssf) + ssfmax[i] = + ((kh_0[i] * slope[i]) / f[i]) * (1.0 - exp(-f[i] * z_exp[i])) + ssf_constant[i] + if zi[i] < z_exp[i] + ssf[i] = ( - ((ssf.kh_0[i] * ssf.slope[i]) / ssf.f[i]) * - (exp(-ssf.f[i] * ssf.zi[i]) - exp(-ssf.f[i] * ssf.z_exp[i])) + - ssf_constant[i] - ) * ssf.dw[i] + ((kh_0[i] * slope[i]) / f[i]) * + (exp(-f[i] * zi[i]) - exp(-f[i] * z_exp[i])) + ssf_constant[i] + ) * dw[i] else - ssf.ssf[i] = - ssf.kh_0[i] * - exp(-ssf.f[i] * ssf.zi[i]) * - ssf.slope[i] * - (ssf.soilthickness[i] - ssf.zi[i]) * - ssf.dw[i] + ssf[i] = + kh_0[i] * exp(-f[i] * zi[i]) * slope[i] * (soilthickness[i] - zi[i]) * dw[i] + end + end + return nothing +end + +""" + initialize_lateralssf!(subsurface::LateralSSF, soil::SbmSoilModel, kv_profile::KvLayered, dt) + initialize_lateralssf!(subsurface::LateralSSF, soil::SbmSoilModel, kv_profile::KvLayeredExponential, dt) + +Initialize lateral subsurface variables `ssf` and `ssfmax` using vertical hydraulic +conductivity profile `kv_profile`. +""" +function initialize_lateralssf!( + subsurface::LateralSSF, + soil::SbmSoilModel, + kv_profile::KvLayered, + dt, +) + (; kh) = subsurface.kh_profile + (; nlayers, act_thickl) = soil.parameters + (; ssf, ssfmax, zi, khfrac, soilthickness, slope, dw) = subsurface + kh_layered_profile!(soil, subsurface, kv_profile, dt) + for i in eachindex(ssf) + ssf[i] = kh[i] * (soilthickness[i] - zi[i]) * slope[i] * dw[i] + kh_max = 0.0 + for j in 1:nlayers[i] + kh_max += kv_profile.kv[i][j] * act_thickl[i][j] end + kh_max = kh_max * khfrac[i] * 0.001 * 0.001 + ssfmax[i] = kh_max * slope[i] end + return nothing end -"Initialize lateral subsurface variables `ssf`, `ssfmax` and `kh` with ksat_profile` `layered` or `layered_exponential`" -function initialize_lateralssf_layered!(ssf::LateralSSF, sbm::SBM, ksat_profile) - for i in eachindex(ssf.ssf) - ssf.kh[i] = kh_layered_profile(sbm, ssf.khfrac[i], i, ksat_profile) - ssf.ssf[i] = - ssf.kh[i] * (ssf.soilthickness[i] - ssf.zi[i]) * ssf.slope[i] * ssf.dw[i] +function initialize_lateralssf!( + subsurface::LateralSSF, + soil::SbmSoilModel, + kv_profile::KvLayeredExponential, + dt, +) + (; ssf, ssfmax, zi, khfrac, soilthickness, slope, dw) = subsurface + (; nlayers, act_thickl) = soil.parameters + (; kh) = subsurface.kh_profile + (; kv, f, nlayers_kv, z_layered) = kv_profile + + kh_layered_profile!(soil, subsurface, kv_profile, dt) + for i in eachindex(ssf) + ssf[i] = kh[i] * (soilthickness[i] - zi[i]) * slope[i] * dw[i] kh_max = 0.0 - for j = 1:sbm.nlayers[i] - if j <= sbm.nlayers_kv[i] - kh_max += sbm.kv[i][j] * sbm.act_thickl[i][j] + for j in 1:nlayers[i] + if j <= nlayers_kv[i] + kh_max += kv[i][j] * act_thickl[i][j] else - zt = sbm.soilthickness[i] - sbm.z_layered[i] + zt = soil.parameters.soilthickness[i] - z_layered[i] k = max(j - 1, 1) - kh_max += sbm.kv[i][k] / sbm.f[i] * (1.0 - exp(-sbm.f[i] * zt)) + kh_max += kv[i][k] / f[i] * (1.0 - exp(-f[i] * zt)) break end end - kh_max = kh_max * ssf.khfrac[i] * 0.001 * 0.001 - ssf.ssfmax[i] = kh_max * ssf.slope[i] + kh_max = kh_max * khfrac[i] * 0.001 * 0.001 + ssfmax[i] = kh_max * slope[i] end + return nothing end """ diff --git a/src/vegetation/canopy.jl b/src/vegetation/canopy.jl new file mode 100644 index 000000000..98762ea50 --- /dev/null +++ b/src/vegetation/canopy.jl @@ -0,0 +1,153 @@ +abstract type AbstractInterceptionModel{T} end + +"Struct for storing interception model variables" +@get_units @grid_loc @with_kw struct InterceptionVariables{T} + # Canopy potential evaporation [mm Δt⁻¹] + canopy_potevap::Vector{T} + # Interception loss by evaporation [mm Δt⁻¹] + interception_rate::Vector{T} + # Canopy storage [mm] + canopy_storage::Vector{T} | "mm" + # Stemflow [mm Δt⁻¹] + stemflow::Vector{T} + # Throughfall [mm Δt⁻¹] + throughfall::Vector{T} +end + +"Initialize interception model variables" +function InterceptionVariables(T::Type{<:AbstractFloat}, n::Int) + return InterceptionVariables(; + canopy_potevap = fill(mv, n), + interception_rate = fill(mv, n), + canopy_storage = zeros(T, n), + stemflow = fill(mv, n), + throughfall = fill(mv, n), + ) +end + +"Struct for storing Gash interception model parameters" +@get_units @grid_loc @with_kw struct GashParameters{T} + # wet canopy [mm Δt⁻¹] and the average precipitation intensity [mm Δt⁻¹] on a saturated canopy + e_r::Vector{T} | "-" + vegetation_parameter_set::VegetationParameters{T} +end + +"Gash interception model" +@with_kw struct GashInterceptionModel{T} <: AbstractInterceptionModel{T} + parameters::GashParameters{T} + variables::InterceptionVariables{T} +end + +"Initialize Gash interception model" +function GashInterceptionModel(nc, config, inds, vegetation_parameter_set) + e_r = ncread( + nc, + config, + "vertical.interception.parameters.e_r"; + sel = inds, + defaults = 0.1, + type = Float, + ) + n = length(inds) + params = + GashParameters(; e_r = e_r, vegetation_parameter_set = vegetation_parameter_set) + vars = InterceptionVariables(Float, n) + model = GashInterceptionModel(; parameters = params, variables = vars) + return model +end + +"Update Gash interception model for a single timestep" +function update!(model::GashInterceptionModel, atmospheric_forcing::AtmosphericForcing) + (; leaf_area_index, canopygapfraction, cmax, kc) = + model.parameters.vegetation_parameter_set + (; canopy_potevap, throughfall, interception_rate, stemflow, canopy_storage) = + model.variables + (; precipitation, potential_evaporation) = atmospheric_forcing + e_r = model.parameters.e_r + n = length(precipitation) + if !isnothing(leaf_area_index) + update_canopy_parameters!(model) + threaded_foreach(1:n; basesize = 1000) do i + canopyfraction = 1.0 - canopygapfraction[i] + ewet = canopyfraction * potential_evaporation[i] * kc[i] + e_r[i] = + precipitation[i] > 0.0 ? + min(0.25, ewet / max(0.0001, canopyfraction * precipitation[i])) : 0.0 + end + end + threaded_foreach(1:n; basesize = 1000) do i + canopy_potevap[i] = kc[i] * potential_evaporation[i] * (1.0 - canopygapfraction[i]) + throughfall[i], interception_rate[i], stemflow[i], canopy_storage[i] = + rainfall_interception_gash( + cmax[i], + e_r[i], + canopygapfraction[i], + precipitation[i], + canopy_storage[i], + canopy_potevap[i], + ) + end + return nothing +end + +"Rutter interception model" +@with_kw struct RutterInterceptionModel{T} <: AbstractInterceptionModel{T} + parameters::VegetationParameters{T} + variables::InterceptionVariables{T} +end + +"Initialize Rutter interception model" +function RutterInterceptionModel(vegetation_parameter_set, n) + vars = InterceptionVariables(n) + model = + RutterInterceptionModel(; parameters = vegetation_parameter_set, variables = vars) + return model +end + +"Update Rutter interception model for a single timestep" +function update!(model::RutterInterceptionModel, atmospheric_forcing::AtmosphericForcing) + (; leaf_area_index, canopygapfraction, cmax, kc) = + model.parameters.vegetation_parameter_set + (; canopy_potevap, throughfall, interception_rate, stemflow, canopy_storage) = + model.variables + (; precipitation, potential_evaporation) = atmospheric_forcing + if !isnothing(leaf_area_index) + update_canopy_parameters!(model) + end + n = length(precipitation) + threaded_foreach(1:n; basesize = 1000) do i + canopy_potevap[i] = kc[i] * potential_evaporation[i] * (1.0 - canopygapfraction[i]) + throughfall[i], interception_rate[i], stemflow[i], canopy_storage[i] = + rainfall_interception_modrut( + precipitation[i], + canopy_potevap[i], + canopy_storage[i], + canopygapfraction[i], + cmax[i], + ) + end + return nothing +end + +"Update canopy parameters `cmax` and `canopygapfraction` based on `leaf_area_index` for a single timestep" +function update_canopy_parameters!(model::AbstractInterceptionModel) + (; + leaf_area_index, + storage_wood, + kext, + storage_specific_leaf, + canopygapfraction, + cmax, + ) = model.parameters.vegetation_parameter_set + + n = length(leaf_area_index) + threaded_foreach(1:n; basesize = 1000) do i + cmax[i] = storage_specific_leaf[i] * leaf_area_index[i] + storage_wood[i] + canopygapfraction[i] = exp(-kext[i] * leaf_area_index[i]) + end + return nothing +end + +"Return potential transpiration rate based on the interception rate" +get_potential_transpiration(model::AbstractInterceptionModel) = + @. max(0.0, model.variables.canopy_potevap - model.variables.interception_rate) \ No newline at end of file diff --git a/src/vegetation/rainfall_interception.jl b/src/vegetation/rainfall_interception.jl new file mode 100644 index 000000000..920a0b3dd --- /dev/null +++ b/src/vegetation/rainfall_interception.jl @@ -0,0 +1,95 @@ + +""" + rainfall_interception_gash(cmax, e_r, canopygapfraction, precipitation, canopystorage, maxevap) + +Interception according to the Gash model (for daily timesteps). `cmax` is the maximum canopy storage and +`e_r` is the ratio of the average evaporation from the wet canopy and the average precipitation intensity on +a saturated canopy. +""" +function rainfall_interception_gash( + cmax, + e_r, + canopygapfraction, + precipitation, + canopystorage, + maxevap, +) + # TODO: add other rainfall interception method (lui) + # TODO: include subdaily Gash model + # TODO: improve computation of stemflow partitioning coefficient pt (0.1 * canopygapfraction) + pt = min(0.1 * canopygapfraction, 1.0 - canopygapfraction) + pfrac = 1.0 - canopygapfraction - pt + p_sat = (-cmax / e_r) * log(1.0 - min((e_r / pfrac), 1.0)) + p_sat = isinf(p_sat) ? 0.0 : p_sat + + # large storms P > P_sat + largestorms = precipitation > p_sat + + iwet = largestorms ? (pfrac * p_sat) - cmax : precipitation * pfrac + isat = largestorms ? (e_r) * (precipitation - p_sat) : 0.0 + idry = largestorms ? cmax : 0.0 + itrunc = 0.0 + + stemflow = pt * precipitation + + throughfall = precipitation - iwet - idry - isat - itrunc - stemflow + interception = iwet + idry + isat + itrunc + + # Now corect for area without any Interception (say open water Cmax -- zero) + cmaxzero = cmax <= 0.0 + throughfall = cmaxzero ? precipitation : throughfall + interception = cmaxzero ? 0.0 : interception + stemflow = cmaxzero ? 0.0 : stemflow + + # Now corect for maximum potential evap + canopy_drainage = interception > maxevap ? interception - maxevap : 0.0 + interception = min(interception, maxevap) + + # Add surpluss to the throughfall + throughfall = throughfall + canopy_drainage + + return throughfall, interception, stemflow, canopystorage +end + +""" + rainfall_interception_modrut(precipitation, potential_evaporation, canopystorage, canopygapfraction, cmax) + +Interception according to a modified Rutter model. The model is solved explicitly and there is no +drainage below `cmax`. +""" +function rainfall_interception_modrut( + precipitation, + potential_evaporation, + canopystorage, + canopygapfraction, + cmax, +) + + # TODO: improve computation of stemflow partitioning coefficient pt (0.1 * canopygapfraction) + pt = min(0.1 * canopygapfraction, 1.0 - canopygapfraction) + + # Amount of p that falls on the canopy + precip_canopy = (1.0 - canopygapfraction - pt) * precipitation + + # Canopystorage cannot be larger than cmax, no gravity drainage below that. This check + # is required because cmax can change over time + canopy_drainage1 = canopystorage > cmax ? canopystorage - cmax : 0.0 + canopystorage = canopystorage - canopy_drainage1 + + # Add the precipitation that falls on the canopy to the store + canopystorage = canopystorage + precip_canopy + + # Evaporation, make sure the store does not get negative + canopy_evap = min(canopystorage, potential_evaporation) # interception rate + canopystorage = canopystorage - canopy_evap + + # Drain the canopystorage again if needed + canopy_drainage2 = canopystorage > cmax ? canopystorage - cmax : 0.0 + canopystorage = canopystorage - canopy_drainage2 + + # Calculate throughfall and stemflow + throughfall = canopy_drainage1 + canopy_drainage2 + canopygapfraction * precipitation + stemflow = precipitation * pt + + return throughfall, canopy_evap, stemflow, canopystorage +end diff --git a/src/vertical_process.jl b/src/vertical_process.jl deleted file mode 100644 index 032a0fc6c..000000000 --- a/src/vertical_process.jl +++ /dev/null @@ -1,439 +0,0 @@ -""" - scurve(x, a, b, c) - -Sigmoid "S"-shaped curve. - -# Arguments -- `x::Real`: input -- `a::Real`: determines the centre level -- `b::Real`: determines the amplitude of the curve -- `c::Real`: determines the steepness or "stepwiseness" of the curve. - The higher c the sharper the function. A negative c reverses the function. -""" -function scurve(x, a, b, c) - s = one(x) / (b + exp(-c * (x - a))) - return s -end - -""" - rainfall_interception_gash(cmax, e_r, canopygapfraction, precipitation, canopystorage, maxevap) - -Interception according to the Gash model (for daily timesteps). `cmax` is the maximum canopy storage and -`e_r` is the ratio of the average evaporation from the wet canopy and the average precipitation intensity on -a saturated canopy. -""" -function rainfall_interception_gash( - cmax, - e_r, - canopygapfraction, - precipitation, - canopystorage, - maxevap, -) - # TODO: add other rainfall interception method (lui) - # TODO: include subdaily Gash model - # TODO: improve computation of stemflow partitioning coefficient pt (0.1 * canopygapfraction) - pt = min(0.1 * canopygapfraction, 1.0 - canopygapfraction) - pfrac = 1.0 - canopygapfraction - pt - p_sat = (-cmax / e_r) * log(1.0 - min((e_r / pfrac), 1.0)) - p_sat = isinf(p_sat) ? 0.0 : p_sat - - # large storms P > P_sat - largestorms = precipitation > p_sat - - iwet = largestorms ? (pfrac * p_sat) - cmax : precipitation * pfrac - isat = largestorms ? (e_r) * (precipitation - p_sat) : 0.0 - idry = largestorms ? cmax : 0.0 - itrunc = 0.0 - - stemflow = pt * precipitation - - throughfall = precipitation - iwet - idry - isat - itrunc - stemflow - interception = iwet + idry + isat + itrunc - - # Now corect for area without any Interception (say open water Cmax -- zero) - cmaxzero = cmax <= 0.0 - throughfall = cmaxzero ? precipitation : throughfall - interception = cmaxzero ? 0.0 : interception - stemflow = cmaxzero ? 0.0 : stemflow - - # Now corect for maximum potential evap - canopy_drainage = interception > maxevap ? interception - maxevap : 0.0 - interception = min(interception, maxevap) - - # Add surpluss to the throughfall - throughfall = throughfall + canopy_drainage - - return throughfall, interception, stemflow, canopystorage - -end - -""" - rainfall_interception_modrut(precipitation, potential_evaporation, canopystorage, canopygapfraction, cmax) - -Interception according to a modified Rutter model. The model is solved explicitly and there is no -drainage below `cmax`. -""" -function rainfall_interception_modrut( - precipitation, - potential_evaporation, - canopystorage, - canopygapfraction, - cmax, -) - - # TODO: improve computation of stemflow partitioning coefficient pt (0.1 * canopygapfraction) - pt = min(0.1 * canopygapfraction, 1.0 - canopygapfraction) - - # Amount of p that falls on the canopy - precip_canopy = (1.0 - canopygapfraction - pt) * precipitation - - # Canopystorage cannot be larger than cmax, no gravity drainage below that. This check - # is required because cmax can change over time - canopy_drainage1 = canopystorage > cmax ? canopystorage - cmax : 0.0 - canopystorage = canopystorage - canopy_drainage1 - - # Add the precipitation that falls on the canopy to the store - canopystorage = canopystorage + precip_canopy - - # Now do the Evap, make sure the store does not get negative - canopy_evap = min(canopystorage, potential_evaporation) - canopystorage = canopystorage - canopy_evap - - # Amount of evap not used - leftover = potential_evaporation - canopy_evap - - # Now drain the canopystorage again if needed... - canopy_drainage2 = canopystorage > cmax ? canopystorage - cmax : 0.0 - canopystorage = canopystorage - canopy_drainage2 - - # Calculate throughfall and stemflow - throughfall = canopy_drainage1 + canopy_drainage2 + canopygapfraction * precipitation - stemflow = precipitation * pt - - # Calculate interception, this is NET Interception - netinterception = precipitation + canopy_drainage1 - throughfall - stemflow - interception = canopy_evap - - return netinterception, throughfall, stemflow, leftover, interception, canopystorage - -end - -""" - vwc_brooks_corey(h, hb, theta_s, theta_r, c) - -Volumetric water content based on the Brooks-Corey soil hydraulic model. -""" -function vwc_brooks_corey(h, hb, theta_s, theta_r, c) - if h < hb - par_lambda = 2.0 / (c - 3.0) - vwc = (theta_s - theta_r) * pow(hb / h, par_lambda) + theta_r - else - vwc = theta_s - end - return vwc -end - -""" - head_brooks_corey(vwc, theta_s, theta_r, c, hb) - -Soil water pressure head based on the Brooks-Corey soil hydraulic model. -""" -function head_brooks_corey(vwc, theta_s, theta_r, c, hb) - par_lambda = 2.0 / (c - 3.0) - # Note that in the original formula, theta_r is extracted from vwc, but theta_r is not part of the numerical vwc calculation - h = hb / (pow(((vwc) / (theta_s - theta_r)), (1.0 / par_lambda))) - h = min(h, hb) - return h -end - -""" - feddes_h3(h3_high, h3_low, tpot, Δt) - -Return soil water pressure head `h3` of Feddes root water uptake reduction function. -""" -function feddes_h3(h3_high, h3_low, tpot, Δt) - # value of h3 is a function of potential transpiration [mm/d] - tpot_daily = tpot * (basetimestep / Δt) - if (tpot_daily >= 0.0) && (tpot_daily <= 1.0) - h3 = h3_low - elseif (tpot_daily > 1.0) && (tpot_daily < 5.0) - h3 = h3_high + ((h3_low - h3_high) * (5.0 - tpot_daily)) / (5.0 - 1.0) - else - h3 = h3_high - end - return h3 -end - -""" - rwu_reduction_feddes(h, h1, h2, h3, h4, alpha_h1) - -Root water uptake reduction factor based on Feddes. -""" -function rwu_reduction_feddes(h, h1, h2, h3, h4, alpha_h1) - # root water uptake reduction coefficient alpha (see also Feddes et al., 1978) - if alpha_h1 == 0.0 - if (h <= h4) || (h > h1) - alpha = 0.0 - elseif (h > h2) && (h <= h1) - alpha = (h - h1) / (h2 - h1) - elseif (h >= h3) && (h <= h2) - alpha = 1.0 - elseif (h >= h4) && (h < h3) - alpha = (h - h4) / (h3 - h4) - end - else - if h <= h4 - alpha = 0.0 - elseif h >= h3 - alpha = 1.0 - elseif (h >= h4) && (h < h3) - alpha = (h - h4) / (h3 - h4) - end - end - return alpha -end - -""" - infiltration(avail_forinfilt, pathfrac, cf_soil, tsoil, infiltcapsoil, infiltcappath, ustorecapacity, modelsnow::Bool, soilinfreduction::Bool) - -Soil infiltration based on infiltration capacity soil `infiltcapsoil`, infiltration capacity compacted area -`infiltcappath` and capacity unsatured zone `ustorecapacity`. The soil infiltration capacity can be adjusted -in case the soil is frozen (`modelsnow` and `soilinfreduction` is `true`). - -""" -function infiltration( - avail_forinfilt, - pathfrac, - cf_soil, - tsoil, - infiltcapsoil, - infiltcappath, - ustorecapacity, - modelsnow::Bool, - soilinfreduction::Bool, -) - # First determine if the soil infiltration capacity can deal with the amount of water - # split between infiltration in undisturbed soil and compacted areas (paths) - soilinf = avail_forinfilt * (1.0 - pathfrac) - pathinf = avail_forinfilt * pathfrac - if modelsnow && soilinfreduction - bb = 1.0 / (1.0 - cf_soil) - soilinfredu = scurve(tsoil, Float(0.0), bb, Float(8.0)) + cf_soil - else - soilinfredu = 1.0 - end - max_infiltsoil = min(infiltcapsoil * soilinfredu, soilinf) - max_infiltpath = min(infiltcappath * soilinfredu, pathinf) - infiltsoilpath = min(max_infiltpath + max_infiltsoil, max(0.0, ustorecapacity)) - - if max_infiltpath + max_infiltsoil > 0.0 - infiltsoil = - max_infiltsoil * - min(1.0, max(0.0, ustorecapacity) / (max_infiltpath + max_infiltsoil)) - infiltpath = - max_infiltpath * - min(1.0, max(0.0, ustorecapacity) / (max_infiltpath + max_infiltsoil)) - else - infiltsoil = 0.0 - infiltpath = 0.0 - end - - infiltexcess = (soilinf - max_infiltsoil) + (pathinf - max_infiltpath) - - return infiltsoilpath, - infiltsoil, - infiltpath, - soilinf, - pathinf, - infiltexcess, - soilinfredu -end - -""" - unsatzone_flow_layer(usd, kv_z, l_sat, c) - -Assuming a unit head gradient, the transfer of water from an unsaturated store layer `usd` is controlled by the -vertical saturated hydraulic conductivity `kv_z` (bottom layer or water table), the effective saturation -degree of the layer (ratio `usd` and `l_sat`), and a Brooks-Corey power coefficient `c`. -""" -function unsatzone_flow_layer(usd, kv_z, l_sat, c) - if usd <= 0.0 - return 0.0, 0.0 - end - sum_ast = 0.0 - # first transfer soil water > maximum soil water capacity layer (iteration is not required because of steady theta (usd)) - st = kv_z * min(pow(usd / l_sat, c), 1.0) - st_sat = max(0.0, usd - l_sat) - usd -= min(st, st_sat) - sum_ast = sum_ast + min(st, st_sat) - ast = max(min(st - min(st, st_sat), usd), 0.0) - # number of iterations (to reduce "overshooting") based on fixed maximum change in soil water per iteration step (0.2 mm / model timestep) - its = Int(cld(ast, 0.2)) - for _ = 1:its - st = (kv_z / its) * min(pow(usd / l_sat, c), 1.0) - ast = min(st, usd) - usd -= ast - sum_ast += ast - end - - return usd, sum_ast -end - -""" - unsatzone_flow_sbm(ustorelayerdepth, soilwatercapacity, satwaterdepth, kv_z, usl, theta_s, theta_r) - -The transfer of water from the unsaturated store `ustorelayerdepth` to the saturated store `satwaterdepth` -is controlled by the vertical saturated hydraulic conductivity `kv_z` at the water table and the ratio between -`ustorelayerdepth` and the saturation deficit (`soilwatercapacity` minus `satwaterdepth`). This is the -original Topog_SBM vertical transfer formulation. - -""" -function unsatzone_flow_sbm( - ustorelayerdepth, - soilwatercapacity, - satwaterdepth, - kv_z, - usl, - theta_s, - theta_r, -) - - sd = soilwatercapacity - satwaterdepth - if sd <= 0.00001 - ast = 0.0 - else - st = kv_z * min(ustorelayerdepth, usl * (theta_s - theta_r)) / sd - ast = min(st, ustorelayerdepth) - ustorelayerdepth = ustorelayerdepth - ast - end - - return ustorelayerdepth, ast - -end - - -""" - snowpack_hbv(snow, snowwater, precipitation, temperature, tti, tt, ttm, cfmax, whc) - -HBV type snowpack modeling using a temperature degree factor. -All correction factors (RFCF and SFCF) are set to 1. -The refreezing efficiency factor is set to 0.05. - -# Arguments -- `snow` (snow storage) -- `snowwater` (liquid water content in the snow pack) -- `precipitation` (throughfall + stemflow) -- `temperature` -- `tti` (snowfall threshold interval length) -- `tt` (threshold temperature for snowfall) -- `ttm` (melting threshold) -- `cfmax` (degree day factor, rate of snowmelt) -- `whc` (Water holding capacity of snow) -- `rfcf` correction factor for rainfall -- `sfcf` correction factor for snowfall -- `cfr` refreeing efficiency constant in refreezing of freewater in snow - -# Output -- `snow` -- `snowwater` -- `snowmelt` -- `rainfall` (precipitation that occurs as rainfall) -- `snowfall` (precipitation that occurs as snowfall) -""" -function snowpack_hbv( - snow, - snowwater, - precipitation, - temperature, - tti, - tt, - ttm, - cfmax, - whc; - rfcf = 1.0, - sfcf = 1.0, - cfr = 0.05, -) - - # fraction of precipitation which falls as rain - rainfrac = if iszero(tti) - Float(temperature > tt) - else - frac = (temperature - (tt - tti / 2.0)) / tti - min(frac, 1.0) - end - rainfrac = max(rainfrac, 0.0) - - # fraction of precipitation which falls as snow - snowfrac = 1.0 - rainfrac - # different correction for rainfall and snowfall - snowfall = snowfrac * sfcf * precipitation # snowfall depth - rainfall = rainfrac * rfcf * precipitation # rainfall depth - # potential snow melt, based on temperature - potsnowmelt = temperature > ttm ? cfmax * (temperature - ttm) : 0.0 - # potential refreezing, based on temperature - potrefreezing = temperature < ttm ? cfmax * cfr * (ttm - temperature) : 0.0 - # actual refreezing - refreezing = temperature < ttm ? min(potrefreezing, snowwater) : 0.0 - - # no landuse correction here - snowmelt = min(potsnowmelt, snow) # actual snow melt - snow = snow + snowfall + refreezing - snowmelt # dry snow content - snowwater = snowwater - refreezing # free water content in snow - maxsnowwater = snow * whc # max water in the snow - snowwater = snowwater + snowmelt + rainfall # add all water and potentially supersaturate the snowpack - rainfall = max(snowwater - maxsnowwater, 0.0) # rain + surpluss snowwater - snowwater = snowwater - rainfall - - return snow, snowwater, snowmelt, rainfall, snowfall -end - -""" - glacier_hbv(glacierfrac, glacierstore, snow, temperature, tt, cfmax, g_sifrac, dt) - -HBV-light type of glacier modelling. -First, a fraction of the snowpack is converted into ice using the HBV-light -model (fraction between 0.001-0.005 per day). -Glacier melting is modelled using a temperature degree factor and only -occurs if the snow cover < 10 mm. - -# Arguments -- `glacierFrac` fraction covered by glaciers [-] -- `glacierstore` volume of the glacier [mm] w.e. -- `snow` snow pack on top of glacier [mm] -- `temperature` air temperature [°C] -- `tt` temperature threshold for ice melting [°C] -- `cfmax` ice degree-day factor in [mm/(°C/day)] -- `g_sifrac` fraction of the snow turned into ice [-] -- `dt` model timestep [s] - -# Output -- `snow` -- `snow2glacier` -- `glacierstore` -- `glaciermelt` - -""" -function glacier_hbv(glacierfrac, glacierstore, snow, temperature, tt, cfmax, g_sifrac, dt) - - # Fraction of the snow transformed into ice (HBV-light model) - snow2glacier = g_sifrac * snow - snow2glacier = glacierfrac > 0.0 ? snow2glacier : 0.0 - - # Max conversion to 8mm/day - snow2glacier = min(snow2glacier, 8.0 * (dt / basetimestep)) - - snow = snow - (snow2glacier * glacierfrac) - glacierstore = glacierstore + snow2glacier - - # Potential snow melt, based on temperature - potmelt = temperature > tt ? cfmax * (temperature - tt) : 0.0 - - # actual Glacier melt - glaciermelt = snow < 10.0 ? min(potmelt, glacierstore) : 0.0 - glacierstore = glacierstore - glaciermelt - - return snow, snow2glacier, glacierstore, glaciermelt - -end diff --git a/src/water_demand.jl b/src/water_demand.jl deleted file mode 100644 index 432db1b69..000000000 --- a/src/water_demand.jl +++ /dev/null @@ -1,626 +0,0 @@ -@get_units @exchange @grid_type @grid_location @with_kw struct NonIrrigationDemand{T} - demand_gross::Vector{T} # gross water demand [mm Δt⁻¹] - demand_net::Vector{T} # net water demand [mm Δt⁻¹] - returnflow_fraction::Vector{T} | "-" # return flow fraction [-] - returnflow::Vector{T} # return flow [mm Δt⁻¹] -end - -@get_units @exchange @grid_type @grid_location @with_kw struct NonPaddy{T} - demand_gross::Vector{T} # irrigation gross demand [mm Δt⁻¹] - irrigation_efficiency::Vector{T} | "-" # irrigation efficiency [-] - maximum_irrigation_rate::Vector{T} # maximum irrigation depth [mm Δt⁻¹] - irrigation_areas::Vector{Bool} | "-" # irrigation areas [-] - irrigation_trigger::Vector{Bool} | "-" # irrigation on or off [-] -end - -@get_units @exchange @grid_type @grid_location @with_kw struct Paddy{T} - demand_gross::Vector{T} # irrigation gross demand [mm Δt⁻¹] - irrigation_efficiency::Vector{T} | "-" # irrigation efficiency [-] - maximum_irrigation_rate::Vector{T} # maximum irrigation depth [mm Δt⁻¹] - irrigation_areas::Vector{Bool} | "-" # irrigation areas [-] - irrigation_trigger::Vector{Bool} | "-" # irrigation on or off [-] - h_min::Vector{T} | "mm" # minimum required water depth in the irrigated rice field [mm] - h_opt::Vector{T} | "mm" # optimal water depth in the irrigated rice fields [mm] - h_max::Vector{T} | "mm" # water depth when rice field starts spilling water (overflow) [mm] - h::Vector{T} | "mm" # actual water depth in rice field [mm] -end - -@get_units @exchange @grid_type @grid_location @with_kw struct AllocationRiver{T} - act_surfacewater_abst::Vector{T} # actual surface water abstraction [mm Δt⁻¹] - act_surfacewater_abst_vol::Vector{T} | "m3 dt-1" # actual surface water abstraction [m³ Δt⁻¹] - available_surfacewater::Vector{T} | "m3" # available surface water [m³] - nonirri_returnflow::Vector{T} # return flow from non irrigation [mm Δt⁻¹] -end - -@get_units @exchange @grid_type @grid_location @with_kw struct AllocationLand{T} - irri_demand_gross::Vector{T} # irrigation gross demand [mm Δt⁻¹] - nonirri_demand_gross::Vector{T} # non-irrigation gross demand [mm Δt⁻¹] - total_gross_demand::Vector{T} # total gross demand [mm Δt⁻¹] - frac_sw_used::Vector{T} | "-" # fraction surface water used [-] - areas::Vector{Int} | "-" # allocation areas [-] - surfacewater_demand::Vector{T} # demand from surface water [mm Δt⁻¹] - surfacewater_alloc::Vector{T} # allocation from surface water [mm Δt⁻¹] - act_groundwater_abst::Vector{T} # actual groundwater abstraction [mm Δt⁻¹] - act_groundwater_abst_vol::Vector{T} | "m3 dt-1" # actual groundwater abstraction [m³ Δt⁻¹] - available_groundwater::Vector{T} | "m3" # available groundwater [m³] - groundwater_demand::Vector{T} # demand from groundwater [mm Δt⁻¹] - groundwater_alloc::Vector{T} # allocation from groundwater [mm Δt⁻¹] - irri_alloc::Vector{T} # allocated water for irrigation [mm Δt⁻¹] - nonirri_alloc::Vector{T} # allocated water for non-irrigation [mm Δt⁻¹] - total_alloc::Vector{T} # total allocated water [mm Δt⁻¹] - nonirri_returnflow::Vector{T} # return flow from non irrigation [mm Δt⁻¹] -end - -"Return return flow fraction based on gross water demand `demand_gross` and net water demand `demand_net`" -function returnflow_fraction(demand_gross, demand_net) - fraction = bounded_divide(demand_net, demand_gross) - returnflow_fraction = 1.0 - fraction - return returnflow_fraction -end - -"Initialize water demand for the domestic sector" -function initialize_domestic_demand(nc, config, inds, dt) - demand_gross = - ncread( - nc, - config, - "vertical.domestic.demand_gross"; - sel = inds, - defaults = 0.0, - type = Float, - ) .* (dt / basetimestep) - demand_net = - ncread( - nc, - config, - "vertical.domestic.demand_net"; - sel = inds, - defaults = 0.0, - type = Float, - ) .* (dt / basetimestep) - n = length(inds) - returnflow_f = returnflow_fraction.(demand_gross, demand_net) - - domestic = NonIrrigationDemand{Float}( - demand_gross = demand_gross, - demand_net = demand_net, - returnflow_fraction = returnflow_f, - returnflow = fill(Float(0), n), - ) - - return domestic -end - -"Initialize water demand for the industry sector" -function initialize_industry_demand(nc, config, inds, dt) - demand_gross = - ncread( - nc, - config, - "vertical.industry.demand_gross"; - sel = inds, - defaults = 0.0, - type = Float, - ) .* (dt / basetimestep) - demand_net = - ncread( - nc, - config, - "vertical.industry.demand_net"; - sel = inds, - defaults = 0.0, - type = Float, - ) .* (dt / basetimestep) - n = length(inds) - returnflow_f = returnflow_fraction.(demand_gross, demand_net) - - industry = NonIrrigationDemand{Float}( - demand_gross = demand_gross, - demand_net = demand_net, - returnflow_fraction = returnflow_f, - returnflow = fill(Float(0), n), - ) - - return industry -end - -"Initialize water demand for the livestock sector" -function initialize_livestock_demand(nc, config, inds, dt) - demand_gross = - ncread( - nc, - config, - "vertical.livestock.demand_gross"; - sel = inds, - defaults = 0.0, - type = Float, - ) .* (dt / basetimestep) - demand_net = - ncread( - nc, - config, - "vertical.livestock.demand_net"; - sel = inds, - defaults = 0.0, - type = Float, - ) .* (dt / basetimestep) - n = length(inds) - returnflow_f = returnflow_fraction.(demand_gross, demand_net) - - livestock = NonIrrigationDemand{Float}( - demand_gross = demand_gross, - demand_net = demand_net, - returnflow_fraction = returnflow_f, - returnflow = fill(Float(0), n), - ) - return livestock -end - -"Initialize paddy (rice) fields for water demand and irrigation computations" -function initialize_paddy(nc, config, inds, dt) - h_min = ncread( - nc, - config, - "vertical.paddy.h_min"; - sel = inds, - defaults = 20.0, - type = Float, - ) - h_opt = ncread( - nc, - config, - "vertical.paddy.h_opt"; - sel = inds, - defaults = 50.0, - type = Float, - ) - h_max = ncread( - nc, - config, - "vertical.paddy.h_max"; - sel = inds, - defaults = 80.0, - type = Float, - ) - efficiency = ncread( - nc, - config, - "vertical.paddy.irrigation_efficiency"; - sel = inds, - defaults = 1.0, - type = Float, - ) - areas = ncread( - nc, - config, - "vertical.paddy.irrigation_areas"; - sel = inds, - optional = false, - type = Bool, - ) - irrigation_trigger = ncread( - nc, - config, - "vertical.paddy.irrigation_trigger"; - sel = inds, - optional = false, - type = Bool, - ) - max_irri_rate = - ncread( - nc, - config, - "vertical.paddy.maximum_irrigation_rate"; - sel = inds, - defaults = 25.0, - type = Float, - ) .* (dt / basetimestep) - - paddy = Paddy{Float}( - demand_gross = fill(mv, length(inds)), - irrigation_efficiency = efficiency, - maximum_irrigation_rate = max_irri_rate, - irrigation_trigger = irrigation_trigger, - h_min = h_min, - h_max = h_max, - h_opt = h_opt, - irrigation_areas = areas, - h = fill(0.0, length(inds)), - ) - return paddy -end - -"Initialize crop (non paddy) fields for water demand and irrigation computations" -function initialize_nonpaddy(nc, config, inds, dt) - efficiency = ncread( - nc, - config, - "vertical.nonpaddy.irrigation_efficiency"; - sel = inds, - defaults = 1.0, - type = Float, - ) - areas = ncread( - nc, - config, - "vertical.nonpaddy.irrigation_areas"; - sel = inds, - defaults = 1, - optional = false, - type = Int, - ) - irrigation_trigger = ncread( - nc, - config, - "vertical.nonpaddy.irrigation_trigger"; - sel = inds, - defaults = 1, - optional = false, - type = Bool, - ) - max_irri_rate = - ncread( - nc, - config, - "vertical.nonpaddy.maximum_irrigation_rate"; - sel = inds, - defaults = 25.0, - type = Float, - ) .* (dt / basetimestep) - - nonpaddy = NonPaddy{Float}( - demand_gross = fill(mv, length(inds)), - maximum_irrigation_rate = max_irri_rate, - irrigation_efficiency = efficiency, - irrigation_areas = areas, - irrigation_trigger = irrigation_trigger, - ) - - return nonpaddy -end - -"Initialize water allocation for the river domain" -function initialize_allocation_river(n) - allocation = AllocationRiver( - act_surfacewater_abst = zeros(Float, n), - act_surfacewater_abst_vol = zeros(Float, n), - available_surfacewater = zeros(Float, n), - nonirri_returnflow = zeros(Float, n), - ) - return allocation -end - -"Initialize water allocation for the land domain (`vertical`)" -function initialize_allocation_land(nc, config, inds) - frac_sw_used = ncread( - nc, - config, - "vertical.allocation.frac_sw_used"; - sel = inds, - defaults = 1, - type = Float, - ) - areas = ncread( - nc, - config, - "vertical.allocation.areas"; - sel = inds, - defaults = 1, - type = Int, - ) - - n = length(inds) - - allocation = AllocationLand( - irri_demand_gross = zeros(Float, n), - nonirri_demand_gross = zeros(Float, n), - total_gross_demand = zeros(Float, n), - frac_sw_used = frac_sw_used, - areas = areas, - surfacewater_demand = zeros(Float, n), - surfacewater_alloc = zeros(Float, n), - act_groundwater_abst = zeros(Float, n), - act_groundwater_abst_vol = zeros(Float, n), - available_groundwater = zeros(Float, n), - groundwater_demand = zeros(Float, n), - groundwater_alloc = zeros(Float, n), - irri_alloc = zeros(Float, n), - nonirri_alloc = zeros(Float, n), - total_alloc = zeros(Float, n), - nonirri_returnflow = zeros(Float, n), - ) - return allocation -end - -"Return non-irrigation gross demand and update returnflow fraction" -function update_non_irrigation_demand(non_irri::NonIrrigationDemand, i) - non_irri.returnflow_fraction[i] = - returnflow_fraction(non_irri.demand_gross[i], non_irri.demand_net[i]) - return non_irri.demand_gross[i] -end - -# return zero (gross demand) if non-irrigation sector is not defined -update_non_irrigation_demand(non_irri::Nothing, i) = 0.0 - -"Update water allocation for river and land domains based on local surface water (river) availability." -function surface_water_allocation_local(land, river, network) - # maps from the land domain to the internal river domain (linear index), excluding water bodies - index_river = network.land.index_river_wb - for i in eachindex(land.allocation.surfacewater_demand) - if index_river[i] > 0.0 - # the available volume is limited by a fixed scaling factor of 0.8 to prevent - # rivers completely drying out. check for abstraction through inflow (external - # negative inflow) and adjust available volume. - if river.inflow[index_river[i]] < 0.0 - inflow = river.inflow[index_river[i]] * land.dt - available_volume = max(river.volume[index_river[i]] * 0.80 + inflow, 0.0) - else - available_volume = river.volume[index_river[i]] * 0.80 - end - # satisfy surface water demand with available local river volume - surfacewater_demand_vol = - land.allocation.surfacewater_demand[i] * 0.001 * network.land.area[i] - abstraction_vol = min(surfacewater_demand_vol, available_volume) - river.allocation.act_surfacewater_abst_vol[index_river[i]] = abstraction_vol - # remaining available surface water and demand - river.allocation.available_surfacewater[index_river[i]] = - max(available_volume - abstraction_vol, 0.0) - abstraction = (abstraction_vol / network.land.area[i]) * 1000.0 - land.allocation.surfacewater_demand[i] = - max(land.allocation.surfacewater_demand[i] - abstraction, 0.0) - # update actual abstraction from river and surface water allocation (land cell) - river.allocation.act_surfacewater_abst[index_river[i]] = abstraction - land.allocation.surfacewater_alloc[i] = abstraction - end - end -end - -"Update water allocation for river and land domains based on surface water (river) availability for allocation areas." -function surface_water_allocation_area(land, river, network) - inds_river = network.river.indices_allocation_areas - inds_land = network.land.indices_allocation_areas - res_index = network.river.reservoir_index - lake_index = network.river.lake_index - - m = length(inds_river) - # loop over allocation areas - for i = 1:m - # surface water demand (allocation area) - sw_demand_vol = 0.0 - for j in inds_land[i] - sw_demand_vol += - land.allocation.surfacewater_demand[j] * 0.001 * network.land.area[j] - end - # surface water availability (allocation area) - sw_available = 0.0 - for j in inds_river[i] - # for reservoir locations use reservoir volume - if res_index[j] > 0 - k = res_index[j] - river.allocation.available_surfacewater[j] = - river.reservoir.volume[k] * 0.98 # limit available reservoir volume - sw_available += river.allocation.available_surfacewater[j] - # for lake locations use lake volume - elseif lake_index[j] > 0 - k = lake_index[j] - river.allocation.available_surfacewater[j] = river.lake.storage[k] * 0.98 # limit available lake volume - sw_available += river.allocation.available_surfacewater[j] - # river volume - else - sw_available += river.allocation.available_surfacewater[j] - end - end - # total actual surface water abstraction [m3] in an allocation area, minimum of - # available surface water and demand in an allocation area. - sw_abstraction = min(sw_available, sw_demand_vol) - - # fraction of available surface water that can be abstracted at allocation area - # level - frac_abstract_sw = bounded_divide(sw_abstraction, sw_available) - # fraction of water demand that can be satisfied by available surface water at - # allocation area level. - frac_allocate_sw = bounded_divide(sw_abstraction, sw_demand_vol) - - # water abstracted from surface water at each river cell (including reservoir and - # lake locations). - for j in inds_river[i] - river.allocation.act_surfacewater_abst_vol[j] += - frac_abstract_sw * river.allocation.available_surfacewater[j] - river.allocation.act_surfacewater_abst[j] = - (river.allocation.act_surfacewater_abst_vol[j] / network.river.area[j]) * - 1000.0 - end - - # water allocated to each land cell. - for j in inds_land[i] - land.allocation.surfacewater_alloc[j] += - frac_allocate_sw * land.allocation.surfacewater_demand[j] - end - end -end - -"Update water allocation for subsurface domain based on local groundwater availability." -function groundwater_allocation_local(land, groundwater_volume, network) - for i in eachindex(land.allocation.groundwater_demand) - # groundwater demand based on allocation from surface water. - land.allocation.groundwater_demand[i] = max( - land.allocation.total_gross_demand[i] - land.allocation.surfacewater_alloc[i], - 0.0, - ) - # land index excluding water bodies - if network.index_wb[i] - # satisfy groundwater demand with available local groundwater volume - groundwater_demand_vol = - land.allocation.groundwater_demand[i] * 0.001 * network.area[i] - available_volume = groundwater_volume[i] * 0.75 # limit available groundwater volume - abstraction_vol = min(groundwater_demand_vol, available_volume) - land.allocation.act_groundwater_abst_vol[i] = abstraction_vol - # remaining available groundwater and demand - land.allocation.available_groundwater[i] = - max(available_volume - abstraction_vol, 0.0) - abstraction = (abstraction_vol / network.area[i]) * 1000.0 - land.allocation.groundwater_demand[i] = - max(land.allocation.groundwater_demand[i] - abstraction, 0.0) - # update actual abstraction from groundwater and groundwater allocation (land cell) - land.allocation.act_groundwater_abst[i] = abstraction - land.allocation.groundwater_alloc[i] = abstraction - end - end -end - -"Update water allocation for subsurface domain based on groundwater availability for allocation areas." -function groundwater_allocation_area(land, network) - inds_river = network.river.indices_allocation_areas - inds_land = network.land.indices_allocation_areas - m = length(inds_river) - # loop over allocation areas - for i = 1:m - # groundwater demand and availability (allocation area) - gw_demand_vol = 0.0 - gw_available = 0.0 - for j in inds_land[i] - gw_demand_vol += - land.allocation.groundwater_demand[j] * 0.001 * network.land.area[j] - gw_available += land.allocation.available_groundwater[j] - end - # total actual groundwater abstraction [m3] in an allocation area, minimum of - # available groundwater and demand in an allocation area. - gw_abstraction = min(gw_available, gw_demand_vol) - - # fraction of available groundwater that can be abstracted at allocation area level - frac_abstract_gw = bounded_divide(gw_abstraction, gw_available) - # fraction of water demand that can be satisfied by available groundwater at - # allocation area level. - frac_allocate_gw = bounded_divide(gw_abstraction, gw_demand_vol) - - # water abstracted from groundwater and allocated. - for j in inds_land[i] - land.allocation.act_groundwater_abst_vol[j] += - frac_abstract_gw * land.allocation.available_groundwater[j] - land.allocation.act_groundwater_abst[j] = - 1000.0 * - (land.allocation.act_groundwater_abst_vol[j] / network.land.area[j]) - land.allocation.groundwater_alloc[j] += - frac_allocate_gw * land.allocation.groundwater_demand[j] - end - end -end - -"Return and update non-irrigation sector (domestic, livestock, industry) return flow" -function return_flow(non_irri::NonIrrigationDemand, allocation) - for i in eachindex(non_irri.returnflow) - frac = bounded_divide(non_irri.demand_gross[i], allocation.nonirri_demand_gross[i]) - allocate = frac * allocation.nonirri_alloc[i] - non_irri.returnflow[i] = non_irri.returnflow_fraction[i] * allocate - end - return non_irri.returnflow -end - -# return zero (return flow) if non-irrigation sector is not defined -return_flow(non_irri::Nothing, allocation) = 0.0 - -groundwater_volume(gw::LateralSSF) = gw.volume -groundwater_volume(gw) = gw.flow.aquifer.volume - -""" - update_water_allocation(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:Union{SbmModel, SbmGwfModel}} - -Update water allocation for model type `sbm` or `sbm_gwf` for a single timestep. First, -surface water abstraction is computed to satisfy local water demand (non-irrigation and -irrigation), and then updated (including lakes and reservoirs) to satisfy the remaining -water demand for allocation areas. Then groundwater abstraction is computed to satisfy the -remaining local water demand, and then updated to satisfy the remaining water demand for -allocation areas. Finally, non-irrigation return flows are updated. -""" -function update_water_allocation( - model::Model{N,L,V,R,W,T}, -) where {N,L,V,R,W,T<:Union{SbmModel,SbmGwfModel}} - - (; network, lateral, vertical) = model - - river = lateral.river - index_river = network.land.index_river_wb - res_index_f = network.river.reservoir_index_f - lake_index_f = network.river.lake_index_f - - vertical.allocation.surfacewater_alloc .= 0.0 - river.allocation.act_surfacewater_abst .= 0.0 - river.allocation.act_surfacewater_abst_vol .= 0.0 - # total surface water demand for each land cell - @. vertical.allocation.surfacewater_demand = - vertical.allocation.frac_sw_used * vertical.allocation.nonirri_demand_gross + - vertical.allocation.frac_sw_used * vertical.allocation.irri_demand_gross - - # local surface water demand and allocation (river, excluding reservoirs and lakes) - surface_water_allocation_local(vertical, river, network) - # surface water demand and allocation for areas - surface_water_allocation_area(vertical, river, network) - - @. river.abstraction = river.allocation.act_surfacewater_abst_vol / vertical.dt - - # for reservoir and lake locations set river abstraction at zero and abstract volume - # from reservoir and lake, including an update of lake waterlevel - if !isnothing(river.reservoir) - @. river.abstraction[res_index_f] = 0.0 - @. river.reservoir.volume -= river.allocation.act_surfacewater_abst_vol[res_index_f] - elseif !isnothing(river.lake) - @. river.abstraction[lake_index_f] = 0.0 - lakes = river.lake - @. lakes.storage -= river.allocation.act_surfacewater_abst_vol[lake_index_f] - @. lakes.waterlevel = - waterlevel(lakes.storfunc, lakes.area, lakes.storage, lakes.sh) - end - - vertical.allocation.groundwater_alloc .= 0.0 - vertical.allocation.act_groundwater_abst_vol .= 0.0 - vertical.allocation.act_groundwater_abst .= 0.0 - # local groundwater demand and allocation - groundwater_allocation_local( - vertical, - groundwater_volume(lateral.subsurface), - network.land, - ) - # groundwater demand and allocation for areas - groundwater_allocation_area(vertical, network) - - # irrigation allocation - for i in eachindex(vertical.allocation.total_alloc) - vertical.allocation.total_alloc[i] = - vertical.allocation.groundwater_alloc[i] + - vertical.allocation.surfacewater_alloc[i] - frac_irri = bounded_divide( - vertical.allocation.irri_demand_gross[i], - vertical.allocation.total_gross_demand[i], - ) - vertical.allocation.irri_alloc[i] = frac_irri * vertical.allocation.total_alloc[i] - vertical.allocation.nonirri_alloc[i] = - vertical.allocation.total_alloc[i] - vertical.allocation.irri_alloc[i] - end - - # non-irrigation return flows - returnflow_livestock = return_flow(vertical.livestock, vertical.allocation) - returnflow_domestic = return_flow(vertical.domestic, vertical.allocation) - returnflow_industry = return_flow(vertical.industry, vertical.allocation) - - # map non-irrigation return flow to land and river water allocation - if ( - !isnothing(vertical.livestock) || - !isnothing(vertical.domestic) || - !isnothing(vertical.industry) - ) - @. vertical.allocation.nonirri_returnflow = - returnflow_livestock + returnflow_domestic + returnflow_industry - - for i in eachindex(vertical.allocation.nonirri_returnflow) - if index_river[i] > 0.0 - k = index_river[i] - river.allocation.nonirri_returnflow[k] = - vertical.allocation.nonirri_returnflow[i] - vertical.allocation.nonirri_returnflow[i] = 0.0 - else - vertical.allocation.nonirri_returnflow[i] = - vertical.allocation.nonirri_returnflow[i] - end - end - end -end diff --git a/test/bmi.jl b/test/bmi.jl index ba0f3751b..23d3c15a6 100644 --- a/test/bmi.jl +++ b/test/bmi.jl @@ -1,11 +1,8 @@ tomlpath = joinpath(@__DIR__, "sbm_config.toml") - @testset "BMI" begin - @testset "BMI functions" begin - model = BMI.initialize(Wflow.Model, tomlpath) @testset "initialization and time functions" begin @@ -20,11 +17,11 @@ tomlpath = joinpath(@__DIR__, "sbm_config.toml") @testset "model information functions" begin @test BMI.get_component_name(model) == "sbm" - @test BMI.get_input_item_count(model) == 207 - @test BMI.get_output_item_count(model) == 207 + @test BMI.get_input_item_count(model) == 203 + @test BMI.get_output_item_count(model) == 203 to_check = [ - "vertical.nlayers", - "vertical.theta_r", + "vertical.soil.parameters.nlayers", + "vertical.soil.parameters.theta_r", "lateral.river.q", "lateral.river.reservoir.outflow", ] @@ -35,12 +32,12 @@ tomlpath = joinpath(@__DIR__, "sbm_config.toml") end @testset "variable information functions" begin - @test BMI.get_var_grid(model, "vertical.theta_s") == 6 + @test BMI.get_var_grid(model, "vertical.soil.parameters.theta_s") == 6 @test BMI.get_var_grid(model, "lateral.river.h") == 3 @test BMI.get_var_grid(model, "lateral.river.reservoir.inflow") == 0 @test_throws ErrorException BMI.get_var_grid(model, "lateral.river.lake.volume") @test BMI.get_var_type(model, "lateral.river.reservoir.inflow") == "$Float" - @test BMI.get_var_units(model, "vertical.theta_s") == "-" + @test BMI.get_var_units(model, "vertical.soil.parameters.theta_s") == "-" @test BMI.get_var_itemsize(model, "lateral.subsurface.ssf") == sizeof(Float) @test BMI.get_var_nbytes(model, "lateral.river.q") == length(model.lateral.river.q) * sizeof(Float) @@ -55,34 +52,46 @@ tomlpath = joinpath(@__DIR__, "sbm_config.toml") @testset "update and get and set functions" begin @test BMI.get_current_time(model) == 86400.0 @test_throws ErrorException BMI.get_value_ptr(model, "vertical.") - dest = zeros(Float, size(model.vertical.zi)) - BMI.get_value(model, "vertical.zi", dest) + dest = zeros(Float, size(model.vertical.soil.variables.zi)) + BMI.get_value(model, "vertical.soil.variables.zi", dest) @test mean(dest) ≈ 276.1625022866973 @test BMI.get_value_at_indices( model, - "vertical.vwc[1]", + "vertical.soil.variables.vwc[1]", zeros(Float, 3), [1, 2, 3], - ) ≈ getindex.(model.vertical.vwc, 1)[1:3] + ) ≈ getindex.(model.vertical.soil.variables.vwc, 1)[1:3] BMI.set_value_at_indices( model, - "vertical.vwc[2]", + "vertical.soil.variables.vwc[2]", [1, 2, 3], [0.10, 0.15, 0.20], - ) ≈ getindex.(model.vertical.vwc, 2)[1:3] + ) ≈ getindex.(model.vertical.soil.variables.vwc, 2)[1:3] @test BMI.get_value_at_indices( model, "lateral.river.q", zeros(Float, 3), [1, 100, 5617], ) ≈ [0.623325399343309, 5.227139951657074, 0.027942874327781947] - BMI.set_value(model, "vertical.zi", fill(300.0, length(model.vertical.zi))) + BMI.set_value( + model, + "vertical.soil.variables.zi", + fill(300.0, length(model.vertical.soil.variables.zi)), + ) @test mean( - BMI.get_value(model, "vertical.zi", zeros(Float, size(model.vertical.zi))), + BMI.get_value( + model, + "vertical.soil.variables.zi", + zeros(Float, size(model.vertical.soil.variables.zi)), + ), ) == 300.0 - BMI.set_value_at_indices(model, "vertical.zi", [1], [250.0]) - @test BMI.get_value_at_indices(model, "vertical.zi", zeros(Float, 2), [1, 2]) == - [250.0, 300.0] + BMI.set_value_at_indices(model, "vertical.soil.variables.zi", [1], [250.0]) + @test BMI.get_value_at_indices( + model, + "vertical.soil.variables.zi", + zeros(Float, 2), + [1, 2], + ) == [250.0, 300.0] end @testset "model grid functions" begin @@ -125,7 +134,6 @@ tomlpath = joinpath(@__DIR__, "sbm_config.toml") BMI.update_until(model, time - BMI.get_time_step(model)) BMI.finalize(model) end - end @testset "BMI grid edges" begin @@ -150,15 +158,15 @@ tomlpath = joinpath(@__DIR__, "sbm_config.toml") model = BMI.initialize(Wflow.Model, tomlpath) # update the recharge part of the SBM model - model = BMI.update(model, run = "sbm_until_recharge") + model = BMI.update(model; run = "sbm_until_recharge") @testset "recharge part of SBM" begin sbm = model.vertical - @test sbm.interception[1] ≈ 0.32734913737568716f0 - @test sbm.ustorelayerdepth[1][1] ≈ 0.0f0 - @test sbm.snow[1] ≈ 3.4847899611762876f0 - @test sbm.recharge[5] ≈ 0.0f0 - @test sbm.zi[5] ≈ 300.0f0 + @test sbm.interception.variables.interception_rate[1] ≈ 0.32734913737568716f0 + @test sbm.soil.variables.ustorelayerdepth[1][1] ≈ 0.0f0 + @test sbm.snow.variables.snow_storage[1] ≈ 3.4847899611762876f0 + @test sbm.soil.variables.recharge[5] ≈ 0.0f0 + @test sbm.soil.variables.zi[5] ≈ 300.0f0 end # set zi and exfiltwater from external source (e.g. a groundwater model) @@ -173,16 +181,16 @@ tomlpath = joinpath(@__DIR__, "sbm_config.toml") fill(1.0e-5, BMI.get_grid_node_count(model, 6)), ) # update SBM after subsurface flow - model = BMI.update(model, run = "sbm_after_subsurfaceflow") + model = BMI.update(model; run = "sbm_after_subsurfaceflow") @testset "SBM after subsurface flow" begin sbm = model.vertical sub = model.lateral.subsurface - @test sbm.interception[1] ≈ 0.32734913737568716f0 - @test sbm.ustorelayerdepth[1][1] ≈ 0.0f0 - @test sbm.snow[1] ≈ 3.4847899611762876f0 - @test sbm.recharge[5] ≈ 0.0f0 - @test sbm.zi[5] ≈ 250.0f0 + @test sbm.interception.variables.interception_rate[1] ≈ 0.32734913737568716f0 + @test sbm.soil.variables.ustorelayerdepth[1][1] ≈ 0.0f0 + @test sbm.snow.variables.snow_storage[1] ≈ 3.4847899611762876f0 + @test sbm.soil.variables.recharge[5] ≈ 0.0f0 + @test sbm.soil.variables.zi[5] ≈ 250.0f0 @test sub.zi[5] ≈ 0.25f0 @test sub.exfiltwater[1] ≈ 1.0f-5 @test sub.ssf[1] ≈ 0.0f0 @@ -190,17 +198,15 @@ tomlpath = joinpath(@__DIR__, "sbm_config.toml") BMI.finalize(model) end - end @testset "BMI extension functions" begin - model = BMI.initialize(Wflow.Model, tomlpath) @test Wflow.get_start_unix_time(model) == 9.466848e8 - satwaterdepth = mean(model.vertical.satwaterdepth) + satwaterdepth = mean(model.vertical.soil.variables.satwaterdepth) model.config.model.reinit = false model = Wflow.load_state(model) - @test satwaterdepth ≠ mean(model.vertical.satwaterdepth) + @test satwaterdepth ≠ mean(model.vertical.soil.variables.satwaterdepth) @test_logs ( :info, "Write output states to netCDF file `$(model.writer.state_nc_path)`.", diff --git a/test/flextopo_config.toml b/test/flextopo_config.toml deleted file mode 100644 index aba6659af..000000000 --- a/test/flextopo_config.toml +++ /dev/null @@ -1,523 +0,0 @@ -casename = "wflow_meuse" -calendar = "proleptic_gregorian" -starttime = "2009-12-31T00:00:00" -endtime = "2010-07-01T00:00:00" -time_units = "days since 1900-01-01 00:00:00" -timestepsecs = 86400 -dir_input = "data/input" -dir_output = "data/output" -loglevel = "info" - -[state] -path_input = "instates.nc" -path_output = "outstates-meuse.nc" - -[input] -path_forcing = "forcing_meuse.nc" -path_static = "staticmaps_flex_meuse.nc" -gauges = "wflow_gauges" -ldd = "wflow_ldd" -river_location = "wflow_river" -subcatchment = "wflow_subcatch" -forcing = [ "vertical.precipitation", "vertical.temperature", "vertical.potential_evaporation",] -#cyclic = [ "vertical.leaf_area_index",] -gauges_grdc = "wflow_gauges_grdc" -gauges_S01 = "wflow_gauges_S01" -gauges_S02 = "wflow_gauges_S02" -gauges_S03 = "wflow_gauges_S03" -gauges_S04 = "wflow_gauges_S04" -gauges_S05 = "wflow_gauges_S05" -gauges_S06 = "wflow_gauges_S06" -gauges_Sall = "wflow_gauges_Sall" - -sub_S01 = "wflow_subcatch_S01" -sub_S02 = "wflow_subcatch_S02" -sub_S03 = "wflow_subcatch_S03" -sub_S04 = "wflow_subcatch_S04" -sub_S05 = "wflow_subcatch_S05" -sub_S06 = "wflow_subcatch_S06" -sub_Sall = "wflow_subcatch_Sall" - -[model] -type = "flextopo" -masswasting = true -snow = true -reinit = true -reservoirs = false -lakes = false -glacier = false -kin_wave_iteration = true -kw_river_tstep = 900 -kw_land_tstep = 3600 -classes = ["h", "p", "w"] - - -select_snow = ["common_snow_hbv"] -select_interception = ["interception_overflow", "interception_overflow", "interception_overflow"] -select_hortonponding = ["hortonponding_no_storage", "hortonponding_no_storage", "hortonponding_no_storage"] -select_hortonrunoff = ["hortonrunoff_no_storage", "hortonrunoff_no_storage", "hortonrunoff_no_storage"] -select_rootzone = ["rootzone_storage", "rootzone_storage", "rootzone_storage"] -select_fast = ["fast_storage", "fast_storage", "fast_storage"] -select_slow = ["common_slow_storage"] - -[input.vertical] -altitude = "wflow_dem" -potential_evaporation = "PET" -precipitation = "P" -temperature = "TEMP" -tt = "tth" -ttm = "tmh" -cfmax = "fmh" -whc = "WHC" -#alfa = "alfa" -beta = "beta" -cap = "cap" -#ds = "d" -fdec = "decf" -fmax = "fmax" -imax = "imax" -#kf = "kf" -#ks = "ksh" -#lp = "lp" -perc = "perc" -srmax = "sumax" -hrufrac = "hrufrac_lu" - -[input.vertical.lp] -netcdf.variable.name = "lp" -scale = [0.4, 0.4, 0.4] -offset = [0.0, 0.0, 0.0] -class = ["h", "p", "w"] - -[input.vertical.ds] -netcdf.variable.name = "d" -scale = [1.2, 1.2, 1.0] -offset = [0.0, 0.0, 0.0] -class = ["h", "p", "w"] - -[input.vertical.kf] -netcdf.variable.name = "kf" -scale = [1.0, 3.0, 3.0] -offset = [0.0, 0.0, 0.0] -class = ["h", "p", "w"] - -[input.vertical.alfa] -netcdf.variable.name = "alfa" -scale = 1.3 -offset = 0 -class = "p" - -[input.vertical.ks] -netcdf.variable.name = "ksh" -scale = 0.5 -offset = 0.0 - - -[input.lateral.river] -length = "wflow_riverlength" -#n = "N_River" -n = "n_river_uniform" -slope = "RiverSlope" -width = "wflow_riverwidth" -bankfull_depth = "RiverDepth" - -[input.lateral.land] -#n = "N" -n = "n_uniform" -slope = "Slope" - -[input.lateral.river.reservoir] -area = "ResSimpleArea" -areas = "wflow_reservoirareas" -demand = "ResDemand" -locs = "wflow_reservoirlocs" -maxrelease = "ResMaxRelease" -maxvolume = "ResMaxVolume" -targetfullfrac = "ResTargetFullFrac" -targetminfrac = "ResTargetMinFrac" - -[state.lateral.river.reservoir] -#volume = "volume_reservoir" - -[state.vertical] -snow = "snow" -snowwater = "snowwater" -interceptionstorage = "interceptionstorage" -hortonpondingstorage = "hortonpondingstorage" -hortonrunoffstorage = "hortonrunoffstorage" -rootzonestorage = "rootzonestorage" -faststorage = "faststorage" -slowstorage = "slowstorage" - - -[state.lateral.river] -q = "q_river" -h = "h_river" -h_av = "h_av_river" - - -[state.lateral.land] -q = "q_land" -h = "h_land" -h_av = "h_av_land" - - -[output] -path = "output-flex-meuse.nc" - -[output.vertical] -#precipitation = "prec" -#temperature = "temp" -#potential_evaporation = "pet" -faststorage = "faststorage" - - -[output.lateral.river] -q_av = "q_river" -#h = "h_river" - - -[output.lateral.land] -#q = "q_land" -#h = "h_land" - - -[csv] -path = "output-flex-meuse.csv" - -[[csv.column]] -header = "Q" -map = "gauges" -parameter = "lateral.river.q_av" - -[[csv.column]] -header = "Q" -map = "gauges_grdc" -parameter = "lateral.river.q_av" - - -[[csv.column]] -header = "Q" -map = "gauges_Sall" -parameter = "lateral.river.q_av" - -[[csv.column]] -header = "H" -map = "gauges_Sall" -parameter = "lateral.river.h_av" - -[[csv.column]] -header = "P" -map = "sub_S06" -parameter = "vertical.precipitation" -reducer = "mean" - -[[csv.column]] -header = "Ep" -map = "sub_S06" -parameter = "vertical.potential_evaporation" -reducer = "mean" - -[[csv.column]] -header = "T" -map = "sub_S06" -parameter = "vertical.temperature" -reducer = "mean" - -[[csv.column]] -header = "Ea" -map = "sub_S06" -parameter = "vertical.actevap_m" -reducer = "mean" - -[[csv.column]] -header = "Ei" -map = "sub_S06" -parameter = "vertical.intevap_m" -reducer = "mean" - -[[csv.column]] -header = "Er" -map = "sub_S06" -parameter = "vertical.rootevap_m" -reducer = "mean" - -[[csv.column]] -header = "Eh" -map = "sub_S06" -parameter = "vertical.hortonevap_m" -reducer = "mean" - -[[csv.column]] -header = "Sw" -map = "sub_S06" -parameter = "vertical.snow" -reducer = "mean" - -[[csv.column]] -header = "Sww" -map = "sub_S06" -parameter = "vertical.snowwater" -reducer = "mean" - -[[csv.column]] -header = "Si" -map = "sub_S06" -parameter = "vertical.interceptionstorage_m" -reducer = "mean" - -[[csv.column]] -header = "Sr" -map = "sub_S06" -parameter = "vertical.srootzone_m" -reducer = "mean" - -[[csv.column]] -header = "Sh" -map = "sub_S06" -parameter = "vertical.hortonpondingstorage_m" -reducer = "mean" - -[[csv.column]] -header = "Shf" -map = "sub_S06" -parameter = "vertical.hortonrunoffstorage_m" -reducer = "mean" - -[[csv.column]] -header = "Sf" -map = "sub_S06" -parameter = "vertical.faststorage_m" -reducer = "mean" - -[[csv.column]] -header = "Ss" -map = "sub_S06" -parameter = "vertical.slowstorage" -reducer = "mean" - -[[csv.column]] -header = "Sr_over_srmax_p" -map = "sub_S06" -parameter = "vertical.srootzone_over_srmax" -reducer = "mean" -class = "p" - -[[csv.column]] -header = "Sr_over_srmax_m" -map = "sub_S06" -parameter = "vertical.srootzone_over_srmax_m" -reducer = "mean" - - - - -[[csv.column]] -header = "Qftotal" -map = "sub_S06" -parameter = "vertical.qfast_tot" -reducer = "mean" - -[[csv.column]] -header = "Qh" -map = "sub_S06" -parameter = "vertical.qhortonpond" -reducer = "mean" -class = "p" - -[[csv.column]] -header = "Qhf" -map = "sub_S06" -parameter = "vertical.qhortonrun" -reducer = "mean" -class = "p" - -[[csv.column]] -header = "Qs" -map = "sub_S01" -parameter = "vertical.qslow" -reducer = "mean" - -[[csv.column]] -header = "Qs" -map = "sub_S06" -parameter = "vertical.qslow" -reducer = "mean" - -[[csv.column]] -header = "Qcap" -map = "sub_S06" -parameter = "vertical.qcapillary_m" -reducer = "mean" - -[[csv.column]] -header = "QfP" -map = "sub_S06" -parameter = "vertical.qfast" -reducer = "mean" -class = "p" - -[[csv.column]] -header = "QfW" -map = "sub_S06" -parameter = "vertical.qfast" -reducer = "mean" -class = "w" - -[[csv.column]] -header = "QfH" -map = "sub_S06" -parameter = "vertical.qfast" -reducer = "mean" -class = "h" - - -[[csv.column]] -header = "percentageP" -map = "sub_S01" -parameter = "vertical.hrufrac" -reducer = "mean" -class = "p" - -[[csv.column]] -header = "percentageW" -map = "sub_S01" -parameter = "vertical.hrufrac" -reducer = "mean" -class = "w" - -[[csv.column]] -header = "percentageH" -map = "sub_S01" -parameter = "vertical.hrufrac" -reducer = "mean" -class = "h" - -[[csv.column]] -header = "percentageP" -map = "sub_S06" -parameter = "vertical.hrufrac" -reducer = "mean" -class = "p" - -[[csv.column]] -header = "percentageW" -map = "sub_S06" -parameter = "vertical.hrufrac" -reducer = "mean" -class = "w" - -[[csv.column]] -header = "percentageH" -map = "sub_S06" -parameter = "vertical.hrufrac" -reducer = "mean" -class = "h" - - - - -[[csv.column]] -header = "QfP" -map = "sub_S01" -parameter = "vertical.qfast" -reducer = "mean" -class = "p" - -[[csv.column]] -header = "QfW" -map = "sub_S01" -parameter = "vertical.qfast" -reducer = "mean" -class = "w" - -[[csv.column]] -header = "QfH" -map = "sub_S01" -parameter = "vertical.qfast" -reducer = "mean" -class = "h" - - -[[csv.column]] -header = "kfP" -map = "sub_Sall" -parameter = "vertical.kf" -reducer = "mean" -class = "p" - -[[csv.column]] -header = "kfW" -map = "sub_Sall" -parameter = "vertical.kf" -reducer = "mean" -class = "w" - -[[csv.column]] -header = "kfH" -map = "sub_Sall" -parameter = "vertical.kf" -reducer = "mean" -class = "h" - - - -[[csv.column]] -header = "wbtot" -map = "sub_S06" -parameter = "vertical.wb_tot" -reducer = "mean" - -[[csv.column]] -header = "wbSi_p" -map = "sub_S06" -parameter = "vertical.wb_interception" -reducer = "mean" -class = "p" - -[[csv.column]] -header = "wbShf_p" -map = "sub_S06" -parameter = "vertical.wb_hortonrunoff" -reducer = "mean" -class = "p" - -[[csv.column]] -header = "wbSh_p" -map = "sub_S06" -parameter = "vertical.wb_hortonponding" -reducer = "mean" -class = "p" - -[[csv.column]] -header = "wbSr_p" -map = "sub_S06" -parameter = "vertical.wb_rootzone" -reducer = "mean" -class = "p" - -[[csv.column]] -header = "wbSf_p" -map = "sub_S06" -parameter = "vertical.wb_fast" -reducer = "mean" -class = "p" - -[[csv.column]] -header = "wbSs" -map = "sub_S06" -parameter = "vertical.wb_slow" -reducer = "mean" - -[[csv.column]] -header = "to_river_land" -map = "gauges_S06" -parameter = "lateral.land.to_river" - -[[csv.column]] -header = "land_inwater" -map = "sub_S06" -parameter = "lateral.land.inwater" -reducer = "mean" - - diff --git a/test/groundwater.jl b/test/groundwater.jl index 5a59a3e20..def5ce7e5 100644 --- a/test/groundwater.jl +++ b/test/groundwater.jl @@ -1,6 +1,6 @@ function initial_head(x) - 2 * √x + return 2 * √x end """ @@ -10,12 +10,11 @@ Non-steady flow in an unconfined rectangular aquifer, with Dirichlet h(0, t) = 0 on the left edge, and a Neumann Boundary Condition (dh/dx = 0) on the right. """ function transient_aquifer_1d(x, time, conductivity, specific_yield, aquifer_length, beta) - initial_head(x) / 1.0 + - (beta * conductivity * initial_head(aquifer_length) * time) / - (specific_yield * aquifer_length * aquifer_length) + return initial_head(x) / 1.0 + + (beta * conductivity * initial_head(aquifer_length) * time) / + (specific_yield * aquifer_length * aquifer_length) end - """ drawdown_theis(distance, time, discharge, transmissivity, storativity) @@ -26,7 +25,6 @@ function drawdown_theis(distance, time, discharge, transmissivity, storativity) return discharge / (4 * pi * transmissivity) * expint(u) end - function homogenous_aquifer(nrow, ncol) shape = (nrow, ncol) # Domain, geometry @@ -62,7 +60,6 @@ function homogenous_aquifer(nrow, ncol) return (connectivity, conf_aqf, unconf_aqf) end - @testset "groundwater" begin ncol = 2 nrow = 3 @@ -343,19 +340,19 @@ end connectivity, aquifer, _ = homogenous_aquifer(3, 1) constanthead = Wflow.ConstantHead([2.0, 4.0], [1, 3]) conductivity_profile = "uniform" - gwf = Wflow.GroundwaterFlow( - aquifer, - connectivity, - constanthead, - Wflow.AquiferBoundaryCondition[], + gwf = Wflow.GroundwaterFlow{Wflow.Float}(; + aquifer = aquifer, + connectivity = connectivity, + constanthead = constanthead, + boundaries = Wflow.AquiferBoundaryCondition[], ) # Set constant head (dirichlet) boundaries gwf.aquifer.head[gwf.constanthead.index] .= gwf.constanthead.head Q = zeros(3) dt = 0.25 # days - for _ = 1:50 - Wflow.update(gwf, Q, dt, conductivity_profile) + for _ in 1:50 + Wflow.update!(gwf, Q, dt, conductivity_profile) end @test gwf.aquifer.head ≈ [2.0, 3.0, 4.0] @@ -365,19 +362,19 @@ end connectivity, aquifer, _ = homogenous_aquifer(3, 1) constanthead = Wflow.ConstantHead([2.0, 4.0], [1, 3]) conductivity_profile = "exponential" - gwf = Wflow.GroundwaterFlow( - aquifer, - connectivity, - constanthead, - Wflow.AquiferBoundaryCondition[], + gwf = Wflow.GroundwaterFlow{Wflow.Float}(; + aquifer = aquifer, + connectivity = connectivity, + constanthead = constanthead, + boundaries = Wflow.AquiferBoundaryCondition[], ) # Set constant head (dirichlet) boundaries gwf.aquifer.head[gwf.constanthead.index] .= gwf.constanthead.head Q = zeros(3) dt = 0.25 # days - for _ = 1:50 - Wflow.update(gwf, Q, dt, conductivity_profile) + for _ in 1:50 + Wflow.update!(gwf, Q, dt, conductivity_profile) end @test gwf.aquifer.head ≈ [2.0, 3.0, 4.0] @@ -404,7 +401,7 @@ end indices, reverse_indices = Wflow.active_indices(domain, false) connectivity = Wflow.Connectivity(indices, reverse_indices, dx, dy) ncell = connectivity.ncell - xc = collect(range(0.0, stop = aquifer_length - cellsize, step = cellsize)) + xc = collect(range(0.0; stop = aquifer_length - cellsize, step = cellsize)) aquifer = Wflow.UnconfinedAquifer( initial_head.(xc), fill(conductivity, ncell), @@ -418,11 +415,11 @@ end ) # constant head on left boundary, 0 at 0 constanthead = Wflow.ConstantHead([0.0], [1]) - gwf = Wflow.GroundwaterFlow( - aquifer, - connectivity, - constanthead, - Wflow.AquiferBoundaryCondition[], + gwf = Wflow.GroundwaterFlow{Wflow.Float}(; + aquifer = aquifer, + connectivity = connectivity, + constanthead = constanthead, + boundaries = Wflow.AquiferBoundaryCondition[], ) dt = Wflow.stable_timestep(gwf.aquifer, conductivity_profile) @@ -431,8 +428,8 @@ end nstep = Int(ceil(time / dt)) time = nstep * dt - for i = 1:nstep - Wflow.update(gwf, Q, dt, conductivity_profile) + for i in 1:nstep + Wflow.update!(gwf, Q, dt, conductivity_profile) # Gradient dh/dx is positive, all flow to the left @test all(diff(gwf.aquifer.head) .> 0.0) end @@ -472,7 +469,7 @@ end indices, reverse_indices = Wflow.active_indices(domain, false) connectivity = Wflow.Connectivity(indices, reverse_indices, dx, dy) ncell = connectivity.ncell - xc = collect(range(0.0, stop = aquifer_length - cellsize, step = cellsize)) + xc = collect(range(0.0; stop = aquifer_length - cellsize, step = cellsize)) aquifer = Wflow.UnconfinedAquifer( initial_head.(xc), fill(conductivity, ncell), @@ -486,11 +483,11 @@ end ) # constant head on left boundary, 0 at 0 constanthead = Wflow.ConstantHead([0.0], [1]) - gwf = Wflow.GroundwaterFlow( - aquifer, - connectivity, - constanthead, - Wflow.AquiferBoundaryCondition[], + gwf = Wflow.GroundwaterFlow{Wflow.Float}(; + aquifer = aquifer, + connectivity = connectivity, + constanthead = constanthead, + boundaries = Wflow.AquiferBoundaryCondition[], ) dt = Wflow.stable_timestep(gwf.aquifer, conductivity_profile) @@ -499,8 +496,8 @@ end nstep = Int(ceil(time / dt)) time = nstep * dt - for i = 1:nstep - Wflow.update(gwf, Q, dt, conductivity_profile) + for i in 1:nstep + Wflow.update!(gwf, Q, dt, conductivity_profile) # Gradient dh/dx is positive, all flow to the left @test all(diff(gwf.aquifer.head) .> 0.0) end @@ -556,12 +553,17 @@ end fill(0.0, ncell), # total volume that can be released, to be set ) - cell_index = reshape(collect(range(1, ncell, step = 1)), shape) + cell_index = reshape(collect(range(1, ncell; step = 1)), shape) indices = vcat(cell_index[1, :], cell_index[end, :])# , cell_index[:, 1], cell_index[:, end],) constanthead = Wflow.ConstantHead(fill(10.0, size(indices)), indices) # Place a well in the middle of the domain well = Wflow.Well([discharge], [0.0], [reverse_indices[wellrow, wellrow]]) - gwf = Wflow.GroundwaterFlow(aquifer, connectivity, constanthead, [well]) + gwf = Wflow.GroundwaterFlow{Wflow.Float}(; + aquifer = aquifer, + connectivity = connectivity, + constanthead = constanthead, + boundaries = Wflow.AquiferBoundaryCondition[well], + ) dt = Wflow.stable_timestep(gwf.aquifer, conductivity_profile) Q = zeros(ncell) @@ -569,24 +571,23 @@ end nstep = Int(ceil(time / dt)) time = nstep * dt - for i = 1:nstep - Wflow.update(gwf, Q, dt, conductivity_profile) + for i in 1:nstep + Wflow.update!(gwf, Q, dt, conductivity_profile) end # test for symmetry on x and y axes head = reshape(gwf.aquifer.head, shape) - @test head[1:halfnrow, :] ≈ head[end:-1:halfnrow+2, :] - @test head[:, 1:halfnrow] ≈ head[:, end:-1:halfnrow+2] + @test head[1:halfnrow, :] ≈ head[end:-1:(halfnrow + 2), :] + @test head[:, 1:halfnrow] ≈ head[:, end:-1:(halfnrow + 2)] # compare with analytical solution start = -0.5 * aquifer_length + 0.5 * cellsize stop = 0.5 * aquifer_length - 0.5 * cellsize - X = collect(range(start, stop = stop, step = cellsize)) + X = collect(range(start; stop = stop, step = cellsize)) head_analytical = [drawdown_theis(x, time, discharge, transmissivity, storativity) for x in X] .+ 10.0 # compare left-side, since it's symmetric anyway. Skip the well cell, and its first neighbor - difference = head[1:halfnrow-1, halfnrow] - head_analytical[1:halfnrow-1] + difference = head[1:(halfnrow - 1), halfnrow] - head_analytical[1:(halfnrow - 1)] @test all(difference .< 0.02) end - end diff --git a/test/hbv_config.toml b/test/hbv_config.toml deleted file mode 100644 index 1a6a584b5..000000000 --- a/test/hbv_config.toml +++ /dev/null @@ -1,137 +0,0 @@ -# This is a TOML configuration file for Wflow. -# Relative file paths are interpreted as being relative to this TOML file. -# Wflow documentation https://deltares.github.io/Wflow.jl/dev/ -# TOML documentation: https://github.com/toml-lang/toml - -calendar = "proleptic_gregorian" -endtime = 2000-02-01T00:00:00 -starttime = 1999-12-31T00:00:00 -time_units = "days since 1900-01-01 00:00:00" -timestepsecs = 86400 - -[state] -path_input = "data/input/instates-lahn.nc" -path_output = "data/output/outstates-lahn.nc" - -# if listed, the variable must be present in the NetCDF or error -# if not listed, the variable can get a default value if it has one - -[state.vertical] -interceptionstorage = "interceptionstorage" -lowerzonestorage = "lowerzonestorage" -snow = "snow" -snowwater = "snowwater" -soilmoisture = "soilmoisture" -upperzonestorage = "upperzonestorage" - -[state.lateral.river] -h = "h_river" -q = "q_river" - -[state.lateral.land] -h = "h_land" -q = "q_land" - -[input] -path_forcing = "data/input/forcing-lahn.nc" -path_static = "data/input/staticmaps-lahn.nc" - -# these are not directly part of the model -gauges = "wflow_gauges" -ldd = "wflow_ldd" -river_location = "wflow_river" -subcatchment = "wflow_subcatch" - -# specify the internal IDs of the parameters which vary over time -# the external name mapping needs to be below together with the other mappings -forcing = [ - "vertical.precipitation", - "vertical.temperature", - "vertical.potential_evaporation", -] - -[input.vertical] -alphanl = "AlphaNL" -altitude = "wflow_dem" -betaseepage = "BetaSeepage" -cevpf = "CEVPF" -cflux = "Cflux" -cfmax = "Cfmax" -cfr = "CFR" -epf = "EPF" -fc = "FC" -hq = "HQ" -icf = "ICF" -k4 = "K4" -khq = "KHQ" -lp = "LP" -perc = "PERC" -potential_evaporation = "PET" -precipitation = "P" -sfcf = "SFCF" -temperature = "TEMP" -tt = "TT" -tti = "TTI" -ttm = "TTM" -whc = "WHC" - -[input.lateral.river] -length = "wflow_riverlength" -n = "N_River" -slope = "RiverSlope" -width = "wflow_riverwidth" - -[input.lateral.land] -n = "N" -slope = "Slope" - -[model] -kin_wave_iteration = true -masswasting = true -reinit = true -snow = true -type = "hbv" - -[output] -path = "data/output/output_lahn.nc" - -[output.vertical] -lowerzonestorage = "lowerzonestorage" -snow = "snow" -snowwater = "snowwater" -soilmoisture = "soilmoisture" -upperzonestorage = "upperzonestorage" - -[output.lateral.river] -q = "q" - -[csv] -path = "data/output/output_lahn.csv" - -[[csv.column]] -header = "Q" -parameter = "lateral.river.q" -reducer = "maximum" - -[[csv.column]] -coordinate.x = 8.279 -coordinate.y = 50.534 -header = "temp_bycoord" -parameter = "vertical.temperature" - -[[csv.column]] -header = "temp_byindex" -index.x = 88 -index.y = 95 -parameter = "vertical.temperature" - -[[csv.column]] -header = "Q" -map = "gauges" -parameter = "lateral.river.q" - -[[csv.column]] -header = "perc" -map = "subcatchment" -parameter = "vertical.perc" -reducer = "mean" diff --git a/test/horizontal_process.jl b/test/horizontal_process.jl index eb3c96763..7cb8bc780 100644 --- a/test/horizontal_process.jl +++ b/test/horizontal_process.jl @@ -41,11 +41,12 @@ Q = Wflow.kin_wave!(Q, graph, toposort, Qold, q, alpha, beta, DCL, dt_sec) @testset "flow rate" begin @test sum(Q) ≈ 2.957806043289641e6 @test Q[toposort[1]] ≈ 0.007260052312634069f0 - @test Q[toposort[n-100]] ≈ 3945.762718338739f0 + @test Q[toposort[n - 100]] ≈ 3945.762718338739f0 @test Q[sink] ≈ 4131.101474418251 end @testset "kinematic wave subsurface flow" begin + kh_profile = Wflow.KhExponential([18021.0], [0.0017669756]) @test all( isapprox.( Wflow.kinematic_wave_ssf( @@ -53,17 +54,15 @@ end 215395179156.82645, 1540.34273559, 1.238, - 18021.0, 0.25, 0.346, - 0.0017669756, 1800.0, 1.0, 1697.05 * 1000.0, 1200.0 * 1000.0, 2443723.716252628, - 1.0, - "exponential", + kh_profile, + 1, ), (7.410313985168225e10, 1540.1496836278836, -0.0), ), @@ -114,7 +113,6 @@ end end @testset "local inertial long channel MacDonald (1997)" begin - g = 9.80665 L = 1000.0 dx = 5.0 @@ -135,11 +133,11 @@ end h_a = h.([dx:dx:L;]) # water depth profile (analytical solution) # integrate slope to get elevation (bed level) z x = [dx:dx:L;] - zb = first.([quadgk(s, xi, L, rtol = 1e-12) for xi in x]) + zb = first.([quadgk(s, xi, L; rtol = 1e-12) for xi in x]) # initialize ShallowWaterRiver graph = DiGraph(n) - for i = 1:n + for i in 1:n add_edge!(graph, i, i + 1) end @@ -156,7 +154,7 @@ end width_at_link = fill(0.0, _ne) length_at_link = fill(0.0, _ne) mannings_n_sq = fill(0.0, _ne) - for i = 1:_ne + for i in 1:_ne zb_max[i] = max(zb[nodes_at_link.src[i]], zb[nodes_at_link.dst[i]]) width_at_link[i] = min(width[nodes_at_link.dst[i]], width[nodes_at_link.src[i]]) length_at_link[i] = 0.5 * (dl[nodes_at_link.dst[i]] + dl[nodes_at_link.src[i]]) @@ -168,7 +166,6 @@ end mannings_n_sq[i] = mannings_n * mannings_n end - network = ( nodes_at_link = nodes_at_link, links_at_node = Wflow.adjacent_links_at_node(graph, nodes_at_link), @@ -181,10 +178,10 @@ end h_init = zeros(n - 1) push!(h_init, h_a[n]) - sw_river = Wflow.ShallowWaterRiver( + sw_river = Wflow.ShallowWaterRiver(; n = n, ne = _ne, - active_n = collect(1:n-1), + active_n = collect(1:(n - 1)), active_e = collect(1:_ne), g = 9.80665, alpha = alpha, @@ -234,7 +231,7 @@ end sw_river.inwater[1] = 20.0 h0 = mean(sw_river.h) dt = Wflow.stable_timestep(sw_river) - Wflow.shallowwater_river_update(sw_river, network, dt, 0.0, true) + Wflow.shallowwater_river_update!(sw_river, network, dt, 0.0, true) d = abs(h0 - mean(sw_river.h)) if d <= epsilon break @@ -243,5 +240,4 @@ end # test for mean absolute error [cm] @test mean(abs.(sw_river.h .- h_a)) * 100.0 ≈ 1.873574206931199 - end diff --git a/test/io.jl b/test/io.jl index 25e142de3..a5700b10a 100644 --- a/test/io.jl +++ b/test/io.jl @@ -10,7 +10,7 @@ parsed_toml = TOML.parsefile(tomlpath) config = Wflow.Config(tomlpath) @testset "configuration file" begin - @test parsed_toml isa Dict{String,Any} + @test parsed_toml isa Dict{String, Any} @test config isa Wflow.Config @test Dict(config) == parsed_toml @test pathof(config) == tomlpath @@ -24,15 +24,25 @@ config = Wflow.Config(tomlpath) @test collect(keys(config.output)) == ["lateral", "vertical", "path"] # theta_s can also be provided under the alias theta_s - @test Wflow.get_alias(config.input.vertical, "theta_s", "theta_s", nothing) == "thetaS" - val = pop!(config.input.vertical, "theta_s") - config.input.vertical["theta_s"] = val - @test Wflow.get_alias(config.input.vertical, "theta_s", "theta_s", nothing) == "thetaS" + @test Wflow.get_alias( + config.input.vertical.soil.parameters, + "theta_s", + "theta_s", + nothing, + ) == "thetaS" + val = pop!(config.input.vertical.soil.parameters, "theta_s") + config.input.vertical.soil.parameters["theta_s"] = val + @test Wflow.get_alias( + config.input.vertical.soil.parameters, + "theta_s", + "theta_s", + nothing, + ) == "thetaS" # modifiers can also be applied - kvconf = Wflow.get_alias(config.input.vertical, "kv_0", "kv_0", nothing) + kvconf = Wflow.get_alias(config.input.vertical.soil.parameters, "kv_0", "kv_0", nothing) @test kvconf isa Wflow.Config - ncname, modifier = Wflow.ncvar_name_modifier(kvconf, config = config) + ncname, modifier = Wflow.ncvar_name_modifier(kvconf; config = config) @test ncname === "KsatVer" @test modifier.scale == 1.0 @test modifier.offset == 0.0 @@ -46,14 +56,6 @@ config = Wflow.Config(tomlpath) joinpath(@__DIR__, "data", "input", "instates-moselle.nc") @test Wflow.output_path(config, config.state.path_output) == joinpath(@__DIR__, "data", "output", "outstates-moselle.nc") - # hbv_config doesn't use dir_input and dir_output - hbv_config = Wflow.Config(joinpath(@__DIR__, "hbv_config.toml")) - @test !haskey(hbv_config, "dir_input") - @test !haskey(hbv_config, "dir_output") - @test Wflow.input_path(hbv_config, hbv_config.state.path_input) == - joinpath(@__DIR__, "data", "input", "instates-lahn.nc") - @test Wflow.output_path(hbv_config, hbv_config.state.path_output) == - joinpath(@__DIR__, "data", "output", "outstates-lahn.nc") end @testset "Clock constructor" begin @@ -212,13 +214,15 @@ end @testset "warm states" begin @test Wflow.param(model, "lateral.river.reservoir.volume")[1] ≈ 3.2807224993363418e7 - @test Wflow.param(model, "vertical.satwaterdepth")[9115] ≈ 477.13548089422125 - @test Wflow.param(model, "vertical.snow")[5] ≈ 11.019233179897599 - @test Wflow.param(model, "vertical.tsoil")[5] ≈ 0.21814478119608938 - @test Wflow.param(model, "vertical.ustorelayerdepth")[50063][1] ≈ 9.969116007201725 - @test Wflow.param(model, "vertical.snowwater")[5] ≈ 0.0 - @test Wflow.param(model, "vertical.canopystorage")[50063] ≈ 0.0 - @test Wflow.param(model, "vertical.zi")[50063] ≈ 296.8028609104624 + @test Wflow.param(model, "vertical.soil.variables.satwaterdepth")[9115] ≈ + 477.13548089422125 + @test Wflow.param(model, "vertical.snow.variables.snow_storage")[5] ≈ 11.019233179897599 + @test Wflow.param(model, "vertical.soil.variables.tsoil")[5] ≈ 0.21814478119608938 + @test Wflow.param(model, "vertical.soil.variables.ustorelayerdepth")[50063][1] ≈ + 9.969116007201725 + @test Wflow.param(model, "vertical.snow.variables.snow_water")[5] ≈ 0.0 + @test Wflow.param(model, "vertical.interception.variables.canopy_storage")[50063] ≈ 0.0 + @test Wflow.param(model, "vertical.soil.variables.zi")[50063] ≈ 296.8028609104624 @test Wflow.param(model, "lateral.subsurface.ssf")[10606] ≈ 39.972334552895816 @test Wflow.param(model, "lateral.river.q")[149] ≈ 53.48673634956338 @test Wflow.param(model, "lateral.river.h")[149] ≈ 1.167635369628945 @@ -252,22 +256,22 @@ end @testset "initial parameter values" begin (; vertical) = model - @test vertical.cfmax[1] ≈ 3.7565300464630127 - @test vertical.soilthickness[1] ≈ 2000.0 - @test vertical.precipitation[49951] ≈ 2.2100000381469727 - @test vertical.c[1] ≈ + @test vertical.snow.parameters.cfmax[1] ≈ 3.7565300464630127 + @test vertical.soil.parameters.soilthickness[1] ≈ 2000.0 + @test vertical.atmospheric_forcing.precipitation[49951] ≈ 2.2100000381469727 + @test vertical.soil.parameters.c[1] ≈ [9.152995289601465, 8.919674421902961, 8.70537452585209, 8.690681062890977] end -config.input.vertical.cfmax = Dict("value" => 2.0) -config.input.vertical.soilthickness = Dict( +config.input.vertical.snow.parameters.cfmax = Dict("value" => 2.0) +config.input.vertical.soil.parameters.soilthickness = Dict( "scale" => 3.0, "offset" => 100.0, "netcdf" => Dict("variable" => Dict("name" => "SoilThickness")), ) -config.input.vertical.precipitation = +config.input.vertical.atmospheric_forcing.precipitation = Dict("scale" => 1.5, "netcdf" => Dict("variable" => Dict("name" => "precip"))) -config.input.vertical.c = Dict( +config.input.vertical.soil.parameters.c = Dict( "scale" => [2.0, 3.0], "offset" => [0.0, 0.0], "layer" => [1, 3], @@ -280,10 +284,10 @@ Wflow.load_dynamic_input!(model) @testset "changed parameter values" begin (; vertical) = model - @test vertical.cfmax[1] == 2.0 - @test vertical.soilthickness[1] ≈ 2000.0 * 3.0 + 100.0 - @test vertical.precipitation[49951] ≈ 1.5 * 2.2100000381469727 - @test vertical.c[1] ≈ [ + @test vertical.snow.parameters.cfmax[1] == 2.0 + @test vertical.soil.parameters.soilthickness[1] ≈ 2000.0 * 3.0 + 100.0 + @test vertical.atmospheric_forcing.precipitation[49951] ≈ 1.5 * 2.2100000381469727 + @test vertical.soil.parameters.c[1] ≈ [ 2.0 * 9.152995289601465, 8.919674421902961, 3.0 * 8.70537452585209, @@ -291,7 +295,7 @@ Wflow.load_dynamic_input!(model) ] end -Wflow.close_files(model, delete_output = false) +Wflow.close_files(model; delete_output = false) @testset "NetCDF creation" begin path = Base.Filesystem.tempname() @@ -303,7 +307,6 @@ end @testset "NetCDF read variants" begin NCDataset(staticmaps_moselle_path) do ds - @test Wflow.is_increasing(ds[:lon]) @test !Wflow.is_increasing(ds[:lat]) @@ -314,7 +317,7 @@ end x = collect(Wflow.nc_dim(ds, :lon)) @test length(x) == 291 - @test x isa Vector{Union{Missing,Float64}} + @test x isa Vector{Union{Missing, Float64}} @test Wflow.internal_dim_name(:lon) == :x @test Wflow.internal_dim_name(:latitude) == :y @@ -323,7 +326,7 @@ end @test_throws ArgumentError Wflow.read_dims(ds["c"], (x = :, y = :)) @test_throws ArgumentError Wflow.read_dims(ds["LAI"], (x = :, y = :)) data, data_dim_order = Wflow.read_dims(ds["wflow_dem"], (x = :, y = :)) - @test data isa Matrix{Union{Float32,Missing}} + @test data isa Matrix{Union{Float32, Missing}} @test data[end, end] === missing @test data[125, 1] ≈ 647.187f0 @test data_dim_order == (:x, :y) @@ -447,9 +450,9 @@ end # Extracting required states and test if some are covered (not all are tested!) required_states = Wflow.extract_required_states(config) - @test (:vertical, :satwaterdepth) in required_states - @test (:vertical, :ustorelayerdepth) in required_states - @test (:vertical, :canopystorage) in required_states + @test (:vertical, :soil, :variables, :satwaterdepth) in required_states + @test (:vertical, :soil, :variables, :ustorelayerdepth) in required_states + @test (:vertical, :interception, :variables, :canopy_storage) in required_states @test (:lateral, :subsurface, :ssf) in required_states @test (:lateral, :river, :q) in required_states @test (:lateral, :river, :h_av) in required_states @@ -457,27 +460,27 @@ end @test !((:lateral, :river, :lake, :waterlevel) in required_states) # Adding an unused state the see if the right warning message is thrown - config.state.vertical.additional_state = "additional_state" + config.state.vertical.soil.variables.additional_state = "additional_state" @test_logs ( :warn, string( - "State variable `(:vertical, :additional_state)` provided, but is not used in ", + "State variable `(:vertical, :soil, :variables, :additional_state)` provided, but is not used in ", "model setup, skipping.", ), ) Wflow.check_states(config) # Removing the unused and required state, to test the exception being thrown - delete!(config.state["vertical"], "additional_state") - delete!(config.state["vertical"], "snow") + delete!(config.state.vertical.soil["variables"], "additional_state") + delete!(config.state.vertical.snow["variables"], "snow_storage") @test_throws ArgumentError Wflow.check_states(config) # Extracting required states for model type sbm_gwf and test if some are covered tomlpath = joinpath(@__DIR__, "sbm_gwf_config.toml") config = Wflow.Config(tomlpath) required_states = Wflow.extract_required_states(config) - @test (:vertical, :satwaterdepth) in required_states - @test (:vertical, :ustorelayerdepth) in required_states - @test (:vertical, :canopystorage) in required_states + @test (:vertical, :soil, :variables, :satwaterdepth) in required_states + @test (:vertical, :soil, :variables, :ustorelayerdepth) in required_states + @test (:vertical, :interception, :variables, :canopy_storage) in required_states @test (:lateral, :subsurface, :flow, :aquifer, :head) in required_states @test (:lateral, :river, :q) in required_states @test (:lateral, :river, :h_av) in required_states diff --git a/test/reservoir_lake.jl b/test/reservoir_lake.jl index aec71a06e..261021927 100644 --- a/test/reservoir_lake.jl +++ b/test/reservoir_lake.jl @@ -1,24 +1,23 @@ -@testset "reservoir simple" begin - res = Wflow.SimpleReservoir{Float64}( - dt = 86400.0, - demand = [52.523], - maxrelease = [420.184], - maxvolume = [25_000_000.0], - volume = [1.925e7], - totaloutflow = [0.0], - inflow = [0.0], - area = [1885665.353626924], - targetfullfrac = [0.8], - targetminfrac = [0.2425554726620697], - precipitation = [4.2], - evaporation = [1.5], - actevap = [0.0], - outflow = [NaN], - percfull = [NaN], - demandrelease = [NaN], - ) - - Wflow.update(res, 1, 100.0, 86400.0) +res = Wflow.SimpleReservoir{Float64}(; + dt = 86400.0, + demand = [52.523], + maxrelease = [420.184], + maxvolume = [25_000_000.0], + volume = [1.925e7], + totaloutflow = [0.0], + inflow = [0.0], + area = [1885665.353626924], + targetfullfrac = [0.8], + targetminfrac = [0.2425554726620697], + precipitation = [4.2], + evaporation = [1.5], + actevap = [0.0], + outflow = [NaN], + percfull = [NaN], + demandrelease = [NaN], +) +@testset "Update reservoir simple" begin + Wflow.update!(res, 1, 100.0, 86400.0) @test res.outflow[1] ≈ 91.3783714867453 @test res.totaloutflow[1] ≈ 7.895091296454794e6 @test res.volume[1] ≈ 2.0e7 @@ -29,31 +28,31 @@ @test res.actevap[1] ≈ 1.5 end -@testset "lake" begin - lake = Wflow.Lake{Float64}( - dt = 86400.0, - lowerlake_ind = [0], - area = [180510409.0], - maxstorage = Wflow.maximum_storage([1], [3], [180510409.0], [missing], [missing]), - threshold = [0.0], - storfunc = [1], - outflowfunc = [3], - totaloutflow = [0.0], - inflow = [0.0], - b = [0.22], - e = [2.0], - sh = [missing], - hq = [missing], - storage = Wflow.initialize_storage([1], [180510409.0], [18.5], [missing]), - waterlevel = [18.5], - precipitation = [20.0], - evaporation = [3.2], - actevap = [0.0], - outflow = [NaN], - ) - - Wflow.update(lake, 1, 2500.0, 181, 86400.0) - @test Wflow.waterlevel(lake.storfunc, lake.area, lake.storage, lake.sh)[1] ≈ 19.672653848925634 +lake = Wflow.Lake{Float64}(; + dt = 86400.0, + lowerlake_ind = [0], + area = [180510409.0], + maxstorage = Wflow.maximum_storage([1], [3], [180510409.0], [missing], [missing]), + threshold = [0.0], + storfunc = [1], + outflowfunc = [3], + totaloutflow = [0.0], + inflow = [0.0], + b = [0.22], + e = [2.0], + sh = [missing], + hq = [missing], + storage = Wflow.initialize_storage([1], [180510409.0], [18.5], [missing]), + waterlevel = [18.5], + precipitation = [20.0], + evaporation = [3.2], + actevap = [0.0], + outflow = [NaN], +) +@testset "Update lake" begin + Wflow.update!(lake, 1, 2500.0, 181, 86400.0) + @test Wflow.waterlevel(lake.storfunc, lake.area, lake.storage, lake.sh)[1] ≈ + 19.672653848925634 @test lake.outflow[1] ≈ 85.14292808113598 @test lake.totaloutflow[1] ≈ 7.356348986210149e6 @test lake.storage[1] ≈ 3.55111879238499e9 @@ -70,9 +69,9 @@ sh = [ ] @testset "linked lakes (HBV)" begin @test keys(sh[1]) == (:H, :S) - @test typeof(values(sh[1])) == Tuple{Vector{Float},Vector{Float}} + @test typeof(values(sh[1])) == Tuple{Vector{Float}, Vector{Float}} - lake = Wflow.Lake{Float}( + lake = Wflow.Lake{Float}(; dt = 86400.0, lowerlake_ind = [2, 0], area = [472461536.0, 60851088.0], @@ -105,8 +104,8 @@ sh = [ ), ) - Wflow.update(lake, 1, 500.0, 15, 86400.0) - Wflow.update(lake, 2, 500.0, 15, 86400.0) + Wflow.update!(lake, 1, 500.0, 15, 86400.0) + Wflow.update!(lake, 2, 500.0, 15, 86400.0) @test lake.outflow ≈ [214.80170846121263, 236.83281600000214] atol = 1e-2 @test lake.totaloutflow ≈ [1.855886761104877e7, 2.0462355302400187e7] atol = 1e3 @test lake.storage ≈ [1.2737435094769483e9, 2.6019755340159863e8] atol = 1e4 @@ -114,8 +113,8 @@ sh = [ lake.actevap .= 0.0 lake.totaloutflow .= 0.0 lake.inflow .= 0.0 - Wflow.update(lake, 1, 500.0, 15, 86400.0) - Wflow.update(lake, 2, 500.0, 15, 86400.0) + Wflow.update!(lake, 1, 500.0, 15, 86400.0) + Wflow.update!(lake, 2, 500.0, 15, 86400.0) @test lake.outflow ≈ [0.0, 239.66710359986183] atol = 1e-2 @test lake.totaloutflow ≈ [-2.2446764487487033e7, 4.3154002238515094e7] atol = 1e3 @test lake.storage ≈ [1.3431699662524352e9, 2.6073035986708355e8] atol = 1e4 @@ -124,7 +123,7 @@ sh = [ end @testset "overflowing lake with sh and hq" begin - lake = Wflow.Lake{Float}( + lake = Wflow.Lake{Float}(; dt = 86400.0, lowerlake_ind = [0], area = [200_000_000], @@ -152,8 +151,9 @@ end storage = [410_760_000], ) - Wflow.update(lake, 1, 1500.0, 15, 86400.0) - @test Wflow.waterlevel(lake.storfunc, lake.area, lake.storage, lake.sh) ≈ [398.0] atol = 1e-2 + Wflow.update!(lake, 1, 1500.0, 15, 86400.0) + @test Wflow.waterlevel(lake.storfunc, lake.area, lake.storage, lake.sh) ≈ [398.0] atol = + 1e-2 @test lake.outflow ≈ [1303.67476852] atol = 1e-2 @test lake.totaloutflow ≈ [11.26375000e7] atol = 1e3 @test lake.storage ≈ [4.293225e8] atol = 1e4 diff --git a/test/run.jl b/test/run.jl index 45cd79750..5f3237d06 100644 --- a/test/run.jl +++ b/test/run.jl @@ -17,7 +17,7 @@ config.state.path_output = config.output.path = joinpath(dirname(tomlpath), "data/state-test/output-moselle-january.nc") model = Wflow.initialize_sbm_model(config) -Wflow.run(model) +Wflow.run!(model) # first half of January, cold start config.starttime = DateTime("2000-01-01T00:00:00") @@ -28,7 +28,7 @@ config.state.path_output = config.output.path = joinpath(dirname(tomlpath), "data/state-test/output-moselle-january-1of2.nc") model = Wflow.initialize_sbm_model(config) -Wflow.run(model) +Wflow.run!(model) # second half of January, warm start config.starttime = DateTime("2000-01-15T00:00:00") @@ -41,8 +41,7 @@ config.state.path_output = config.output.path = joinpath(dirname(tomlpath), "data/state-test/output-moselle-january-2of2.nc") model = Wflow.initialize_sbm_model(config) -Wflow.run(model) - +Wflow.run!(model) # second half of January, warm start, fews_run set to true, and starttime set one day earlier # to match endtime of part 1 @@ -59,7 +58,7 @@ config.state.path_output = joinpath( config.output.path = joinpath(dirname(tomlpath), "data/state-test/output-moselle-january-2of2-fews_run.nc") model = Wflow.initialize_sbm_model(config) -Wflow.run(model) +Wflow.run!(model) # verify that there are minimal differences in the end state of the two runs endstate_one_run_path = diff --git a/test/run_flextopo.jl b/test/run_flextopo.jl deleted file mode 100644 index 974fc085c..000000000 --- a/test/run_flextopo.jl +++ /dev/null @@ -1,116 +0,0 @@ - -tomlpath = joinpath(@__DIR__, "flextopo_config.toml") -config = Wflow.Config(tomlpath) - -model = Wflow.initialize_flextopo_model(config) -(; network) = model - -model = Wflow.run_timestep(model) - -# test if the first timestep was written to the CSV file -flush(model.writer.csv_io) # ensure the buffer is written fully to disk -@testset "CSV output" begin - row = csv_first_row(model.writer.csv_path) - - @test row.time == DateTime("2010-01-01T00:00:00") - @test row.Q_16 ≈ 0.0000370817920883859f0 - @test row.percentageH_16 ≈ 0.340213f0 - @test row.QfP_1011 ≈ 0.0f0 - @test row.kfW_10 ≈ 0.136334478855133f0 - @test row.kfH_10 ≈ 0.0454448275268077f0 - @test row.Qs_503 ≈ 0.1575031550601124f0 - @test row.Si_16 ≈ 0.0f0 - @test row.Ss_16 ≈ 29.89794354323524f0 - @test row.Ea_16 ≈ 0.0110627892408438f0 -end - -@testset "first timestep" begin - flextopo = model.vertical - @test flextopo.tt[3500] ≈ 1.3f0 - @test model.clock.iteration == 1 - @test flextopo.rootzonestorage[3500] ≈ - [147.11238663084805f0, 79.08369375691255f0, 79.23637697443984f0] - @test flextopo.runoff[3500] ≈ 0.19008129369467497f0 - @test flextopo.rootevap[3500] ≈ - [0.009225917980074883f0, 0.009225917980074883f0, 0.009225917980074883f0] - @test flextopo.snow[3500] == 0.0 -end - -# run the second timestep -model = Wflow.run_timestep(model) - -@testset "second timestep" begin - flextopo = model.vertical - @test flextopo.rootzonestorage[3500] ≈ - [146.73222385533154f0, 78.55190222246516f0, 78.85739340140256f0] - @test flextopo.runoff[3500] ≈ 0.18887692333975817f0 - @test flextopo.rootevap[3500] ≈ - [0.38016277551651f0, 0.38016277551651f0, 0.38016277551651f0] - @test flextopo.snow[3500] == 0.0f0 -end - -@testset "overland domain" begin - q = model.lateral.land.q_av - land = model.lateral.land - @test sum(q) ≈ 202.1162497378859f0 - @test q[10354] ≈ 0.001624108000162103f0 - @test land.volume[10354] ≈ 66.41398729880544f0 - @test land.inwater[10354] ≈ 0.001658182220814705f0 - @test q[network.land.order[end]] ≈ 0.0033337667005815565f0 -end - -@testset "river flow" begin - q = model.lateral.river.q_av - @test sum(q) ≈ 108.45732176546284f0 - @test q[651] ≈ 0.014755293483684526f0 - @test q[1056] ≈ 0.0030933658056867823f0 - @test q[network.river.order[end]] ≈ 0.0005541137960384742f0 -end - -#change the config to use other functions for the storages for several classes: -config = Wflow.Config(tomlpath) -config["model"]["select_snow"] = ["common_snow_no_storage"] -config["model"]["select_interception"] = - ["interception_no_storage", "interception_overflow", "interception_overflow"] -config["model"]["select_hortonponding"] = - ["hortonponding_no_storage", "hortonponding", "hortonponding_no_storage"] -config["model"]["select_hortonrunoff"] = - ["hortonrunoff_no_storage", "hortonrunoff", "hortonrunoff_no_storage"] -config["model"]["select_rootzone"] = - ["rootzone_storage", "rootzone_storage", "rootzone_no_storage"] -config["model"]["select_fast"] = ["fast_no_storage", "fast_storage", "fast_storage"] -config["model"]["select_slow"] = ["common_slow_storage"] - -model = Wflow.initialize_flextopo_model(config) -(; network) = model - -model = Wflow.run_timestep(model) - -@testset "first timestep" begin - flextopo = model.vertical - @test flextopo.rootzonestorage[3500] ≈ - [147.11238663084805f0, 79.08369375691255f0, 0.0f0] - @test flextopo.runoff[3500] ≈ 0.19008129369467497f0 - @test flextopo.rootevap[3500] ≈ [0.009225917980074883f0, 0.009225917980074883f0, 0.0f0] - @test flextopo.snow[3500] == 0.0f0 -end - -@testset "overland domain" begin - q = model.lateral.land.q_av - land = model.lateral.land - @test sum(q) ≈ 69.51434665542678f0 - @test q[10354] ≈ 0.0009376627801121855f0 - @test land.volume[10354] ≈ 63.46997463442048f0 - @test land.inwater[10354] ≈ 0.0016722689680105701f0 - @test q[network.land.order[end]] ≈ 0.0006175366185364747f0 -end - -@testset "river flow" begin - q = model.lateral.river.q_av - @test sum(q) ≈ 22.236556345676156f0 - @test q[651] ≈ 0.00035906990499781237f0 - @test q[1056] ≈ 0.0002647264689318855f0 - @test q[network.river.order[end]] ≈ 0.000015647838277869928f0 -end - -Wflow.close_files(model, delete_output = false) diff --git a/test/run_hbv.jl b/test/run_hbv.jl deleted file mode 100644 index b0706f316..000000000 --- a/test/run_hbv.jl +++ /dev/null @@ -1,66 +0,0 @@ - -tomlpath = joinpath(@__DIR__, "hbv_config.toml") -config = Wflow.Config(tomlpath) - -model = Wflow.initialize_hbv_model(config) -(; network) = model - -model = Wflow.run_timestep(model) - -# test if the first timestep was written to the CSV file -flush(model.writer.csv_io) # ensure the buffer is written fully to disk -@testset "CSV output" begin - row = csv_first_row(model.writer.csv_path) - - @test row.time == DateTime("2000-01-01T00:00:00") - @test row.Q ≈ 521.8433822888003f0 - @test row.temp_bycoord ≈ 2.965437173843384f0 - @test row.temp_byindex ≈ 1.1716821193695068f0 - @test row.Q_1 ≈ 505.1935875677504f0 - @test row.perc_33 ≈ 2.308000087738037f0 - @test row.perc_34 ≈ 1.8980000019073486f0 - @test row.perc_35 ≈ 2.7100000381469727f0 - @test row.perc_36 ≈ 3.818000078201294f0 - @test row.perc_37 ≈ 2.1440000534057617f0 -end - -@testset "first timestep" begin - hbv = model.vertical - @test hbv.tt[4377] ≈ 0.0 - @test model.clock.iteration == 1 - @test hbv.soilmoisture[4377] ≈ 134.35299682617188f0 - @test hbv.runoff[4377] ≈ 7.406898120121746f0 - @test hbv.soilevap[4377] == 0.0 - @test hbv.snow[4377] == 0.0 -end - -# run the second timestep -model = Wflow.run_timestep(model) - -@testset "second timestep" begin - hbv = model.vertical - @test hbv.soilmoisture[4377] ≈ 134.35299682617188f0 - @test hbv.runoff[4377] ≈ 4.3533463f0 - @test hbv.soilevap[4377] == 0.0 - @test hbv.snow[4377] == 0.0 -end - -@testset "overland domain" begin - q = model.lateral.land.q_av - land = model.lateral.land - @test sum(q) ≈ 3264.157286198723f0 - @test q[10354] ≈ 0.2350958683414288f0 - @test land.volume[10354] ≈ 2057.7314802432425f0 - @test land.inwater[10354] ≈ 0.027351853491789667f0 - @test q[network.land.order[end]] ≈ 0.27158713754634217f0 -end - -@testset "river flow" begin - q = model.lateral.river.q_av - @test sum(q) ≈ 52686.97949266187f0 - @test q[651] ≈ 5.7624898709967125f0 - @test q[1056] ≈ 8.892291220438524f0 - @test q[network.river.order[end]] ≈ 360.40425076323913f0 -end - -Wflow.close_files(model, delete_output = false) diff --git a/test/run_sbm.jl b/test/run_sbm.jl index fe81c53ec..2d03bed96 100644 --- a/test/run_sbm.jl +++ b/test/run_sbm.jl @@ -6,7 +6,7 @@ config = Wflow.Config(tomlpath) model = Wflow.initialize_sbm_model(config) (; network) = model -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) # test if the first timestep was written to the CSV file flush(model.writer.csv_io) # ensure the buffer is written fully to disk @@ -69,47 +69,48 @@ end end @testset "first timestep" begin - sbm = model.vertical - - @test sbm.tt[50063] ≈ 0.0f0 + sbm = model.vertical.soil + snow = model.vertical.snow + @test snow.parameters.tt[50063] ≈ 0.0f0 @test model.clock.iteration == 1 - @test sbm.theta_s[50063] ≈ 0.48755401372909546f0 - @test sbm.theta_r[50063] ≈ 0.15943120419979095f0 - @test mean(sbm.runoff) ≈ 0.04177459898728149f0 - @test mean(sbm.soilevap) ≈ 0.02122698830889417f0 - @test mean(sbm.actevap) ≈ 0.3353001180202587f0 - @test mean(sbm.actinfilt) ≈ 1.6444774688444848f0 - @test sbm.snow[5] ≈ 3.768513390588815f0 - @test mean(sbm.snow) ≈ 0.038019723676094325f0 - @test sbm.total_storage[50063] ≈ 559.9035608052374f0 - @test sbm.total_storage[429] ≈ 597.4578475404879f0 # river cell + @test sbm.parameters.theta_s[50063] ≈ 0.48755401372909546f0 + @test sbm.parameters.theta_r[50063] ≈ 0.15943120419979095f0 + @test mean(sbm.variables.runoff) ≈ 0.04177459898728149f0 + @test mean(sbm.variables.soilevap) ≈ 0.02122698830889417f0 + @test mean(sbm.variables.actevap) ≈ 0.3353001180202587f0 + @test mean(sbm.variables.actinfilt) ≈ 1.6444774688444848f0 + @test snow.variables.snow_storage[5] ≈ 3.768513390588815f0 + @test mean(snow.variables.snow_storage) ≈ 0.038019723676094325f0 + @test sbm.variables.total_storage[50063] ≈ 559.9035608052374f0 + @test sbm.variables.total_storage[429] ≈ 597.4578475404879f0 # river cell end # run the second timestep -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) @testset "second timestep" begin - sbm = model.vertical - @test sbm.theta_s[50063] ≈ 0.48755401372909546f0 - @test sbm.theta_r[50063] ≈ 0.15943120419979095f0 - @test mean(sbm.net_runoff) ≈ 0.23734052031823816f0 - @test mean(sbm.runoff) ≈ 0.23770898226019577f0 - @test mean(sbm.soilevap) ≈ 0.018750808322054897f0 - @test mean(sbm.actevap) ≈ 0.14545276216428166f0 - @test mean(sbm.actinfilt) ≈ 0.08863102527394363f0 - @test sbm.snow[5] ≈ 3.843412524052313f0 - @test mean(sbm.snow) ≈ 0.03461317061870949f0 - @test sbm.total_storage[50063] ≈ 560.0152135062889f0 - @test sbm.total_storage[429] ≈ 617.2238533241972f0 # river cell + sbm = model.vertical.soil + snow = model.vertical.snow + @test sbm.parameters.theta_s[50063] ≈ 0.48755401372909546f0 + @test sbm.parameters.theta_r[50063] ≈ 0.15943120419979095f0 + @test mean(sbm.variables.net_runoff) ≈ 0.23734052031823816f0 + @test mean(sbm.variables.runoff) ≈ 0.23770898226019577f0 + @test mean(sbm.variables.soilevap) ≈ 0.018750808322054897f0 + @test mean(sbm.variables.actevap) ≈ 0.14545276216428166f0 + @test mean(sbm.variables.actinfilt) ≈ 0.08863102527394363f0 + @test snow.variables.snow_storage[5] ≈ 3.843412524052313f0 + @test mean(snow.variables.snow_storage) ≈ 0.03461317061870949f0 + @test sbm.variables.total_storage[50063] ≈ 560.0152135062889f0 + @test sbm.variables.total_storage[429] ≈ 617.2238533241972f0 # river cell end @testset "subsurface flow" begin ssf = model.lateral.subsurface.ssf @test sum(ssf) ≈ 6.3761585406186976f7 @test ssf[network.land.order[1]] ≈ 718.2802566393531f0 - @test ssf[network.land.order[end-100]] ≈ 2337.771227118579f0 + @test ssf[network.land.order[end - 100]] ≈ 2337.771227118579f0 @test ssf[network.land.order[end]] ≈ 288.19428729403984f0 end @@ -139,12 +140,12 @@ end end # set these variables for comparison in "changed dynamic parameters" -precip = copy(model.vertical.precipitation) -evap = copy(model.vertical.potential_evaporation) -lai = copy(model.vertical.leaf_area_index) +precip = copy(model.vertical.atmospheric_forcing.precipitation) +evap = copy(model.vertical.atmospheric_forcing.potential_evaporation) +lai = copy(model.vertical.vegetation_parameter_set.leaf_area_index) res_evap = copy(model.lateral.river.reservoir.evaporation) -Wflow.close_files(model, delete_output = false) +Wflow.close_files(model; delete_output = false) # test for setting a pit and multithreading multiple basins (by setting 2 extra pits # resulting in 3 basins) @@ -176,26 +177,27 @@ end tomlpath = joinpath(@__DIR__, "sbm_config.toml") config = Wflow.Config(tomlpath) -config.input.vertical.precipitation = +config.input.vertical.atmospheric_forcing.precipitation = Dict("scale" => 2.0, "netcdf" => Dict("variable" => Dict("name" => "precip"))) -config.input.vertical.potential_evaporation = Dict( +config.input.vertical.atmospheric_forcing.potential_evaporation = Dict( "scale" => 3.0, "offset" => 1.50, "netcdf" => Dict("variable" => Dict("name" => "pet")), ) -config.input.vertical.leaf_area_index = +config.input.vertical.vegetation_parameter_set.leaf_area_index = Dict("scale" => 1.6, "netcdf" => Dict("variable" => Dict("name" => "LAI"))) model = Wflow.initialize_sbm_model(config) -model = Wflow.run_timestep(model) -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) +Wflow.run_timestep!(model) @testset "changed dynamic parameters" begin res = model.lateral.river.reservoir vertical = model.vertical - @test vertical.precipitation[2] / precip[2] ≈ 2.0f0 - @test (vertical.potential_evaporation[100] - 1.50) / evap[100] ≈ 3.0f0 - @test vertical.leaf_area_index[100] / lai[100] ≈ 1.6f0 + @test vertical.atmospheric_forcing.precipitation[2] / precip[2] ≈ 2.0f0 + @test (vertical.atmospheric_forcing.potential_evaporation[100] - 1.50) / evap[100] ≈ + 3.0f0 + @test vertical.vegetation_parameter_set.leaf_area_index[100] / lai[100] ≈ 1.6f0 @test (res.evaporation[2] - 1.50) / res_evap[2] ≈ 3.0000012203408635f0 end @@ -203,12 +205,13 @@ end tomlpath = joinpath(@__DIR__, "sbm_config.toml") config = Wflow.Config(tomlpath) -config.input.cyclic = ["vertical.leaf_area_index", "lateral.river.inflow"] +config.input.cyclic = + ["vertical.vegetation_parameter_set.leaf_area_index", "lateral.river.inflow"] config.input.lateral.river.inflow = "inflow" model = Wflow.initialize_sbm_model(config) -model = Wflow.run_timestep(model) -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) +Wflow.run_timestep!(model) @testset "river inflow (cyclic)" begin @test model.lateral.river.inflow[44] ≈ 0.75 @@ -217,25 +220,25 @@ end # test fixed forcing (precipitation = 2.5) config = Wflow.Config(tomlpath) -config.input.vertical.precipitation = Dict("value" => 2.5) +config.input.vertical.atmospheric_forcing.precipitation = Dict("value" => 2.5) model = Wflow.initialize_sbm_model(config) -Wflow.load_fixed_forcing(model) +Wflow.load_fixed_forcing!(model) @testset "fixed precipitation forcing (initialize)" begin - @test maximum(model.vertical.precipitation) ≈ 2.5 - @test minimum(model.vertical.precipitation) ≈ 0.0 + @test maximum(model.vertical.atmospheric_forcing.precipitation) ≈ 2.5 + @test minimum(model.vertical.atmospheric_forcing.precipitation) ≈ 0.0 @test all(isapprox.(model.lateral.river.reservoir.precipitation, 2.5)) end -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) @testset "fixed precipitation forcing (first timestep)" begin - @test maximum(model.vertical.precipitation) ≈ 2.5 - @test minimum(model.vertical.precipitation) ≈ 0.0 + @test maximum(model.vertical.atmospheric_forcing.precipitation) ≈ 2.5 + @test minimum(model.vertical.atmospheric_forcing.precipitation) ≈ 0.0 @test all(isapprox.(model.lateral.river.reservoir.precipitation, 2.5)) end -Wflow.close_files(model, delete_output = false) +Wflow.close_files(model; delete_output = false) # test local-inertial option for river flow river_routing tomlpath = joinpath(@__DIR__, "sbm_config.toml") @@ -243,8 +246,8 @@ config = Wflow.Config(tomlpath) config.model.river_routing = "local-inertial" model = Wflow.initialize_sbm_model(config) -model = Wflow.run_timestep(model) -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) +Wflow.run_timestep!(model) @testset "river flow and depth (local inertial)" begin q = model.lateral.river.q_av @@ -259,15 +262,15 @@ model = Wflow.run_timestep(model) q_channel = model.lateral.river.q_channel_av @test q ≈ q_channel end -Wflow.close_files(model, delete_output = false) +Wflow.close_files(model; delete_output = false) # test local-inertial option for river and overland flow tomlpath = joinpath(@__DIR__, "sbm_swf_config.toml") config = Wflow.Config(tomlpath) model = Wflow.initialize_sbm_model(config) -model = Wflow.run_timestep(model) -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) +Wflow.run_timestep!(model) @testset "river and overland flow and depth (local inertial)" begin q = model.lateral.river.q_av @@ -287,7 +290,8 @@ model = Wflow.run_timestep(model) @test h[[26, 35, 631]] ≈ [0.07367301172613304f0, 0.009139882310161706f0, 0.0007482998926237368f0] end -Wflow.close_files(model, delete_output = false) + +Wflow.close_files(model; delete_output = false) # test local-inertial option for river flow including 1D floodplain schematization tomlpath = joinpath(@__DIR__, "sbm_config.toml") @@ -396,8 +400,8 @@ dh = diff(fp.depth) @test Wflow.wetted_perimeter(fp.p[i1, 4], fp.depth[i1], h) ≈ 90.11775307900271f0 end -model = Wflow.run_timestep(model) -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) +Wflow.run_timestep!(model) @testset "river flow (local inertial) with floodplain schematization simulation" begin q = model.lateral.river.q_av @@ -417,8 +421,8 @@ end config.input.lateral.river.riverlength_bc = "riverlength_bc" config.input.lateral.river.riverdepth_bc = "riverdepth_bc" model = Wflow.initialize_sbm_model(config) -model = Wflow.run_timestep(model) -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) +Wflow.run_timestep!(model) @testset "change boundary condition for local inertial routing (including floodplain)" begin q = model.lateral.river.q_av @@ -433,110 +437,94 @@ model = Wflow.run_timestep(model) @test h[501] ≈ 0.056707564314724804f0 @test h[5808] ≈ 2.0000006940603936f0 end -Wflow.close_files(model, delete_output = false) +Wflow.close_files(model; delete_output = false) # test different ksat profiles @testset "ksat profiles (SBM)" begin i = 100 tomlpath = joinpath(@__DIR__, "sbm_config.toml") config = Wflow.Config(tomlpath) - config.input.vertical.kv = "kv" - config.input.vertical.z_exp = Dict("value" => 400.0) - config.input.vertical.z_layered = Dict("value" => 400.0) + config.input.vertical.soil.parameters.kv = "kv" + config.input.vertical.soil.parameters.z_exp = Dict("value" => 400.0) + config.input.vertical.soil.parameters.z_layered = Dict("value" => 400.0) @testset "exponential profile" begin model = Wflow.initialize_sbm_model(config) - (; vertical) = model - z = vertical.zi[i] - kv_z = Wflow.hydraulic_conductivity_at_depth(vertical, z, i, 2, "exponential") - @test kv_z ≈ vertical.kvfrac[i][2] * vertical.kv_0[i] * exp(-vertical.f[i] * z) - @test vertical.z_exp == vertical.soilthickness - @test_throws ErrorException Wflow.kh_layered_profile( - vertical, - 100.0, - i, - "exponential", - ) - @test all(isnan.(vertical.z_layered)) - @test all(isnan.(vertical.kv[i])) - @test all(vertical.nlayers_kv .== 0) + (; soil) = model.vertical + (; kv_profile) = soil.parameters + (; subsurface) = model.lateral + z = soil.variables.zi[i] + kvfrac = soil.parameters.kvfrac + kv_z = Wflow.hydraulic_conductivity_at_depth(kv_profile, kvfrac, z, i, 2) + @test kv_z ≈ kvfrac[i][2] * kv_profile.kv_0[i] * exp(-kv_profile.f[i] * z) + @test subsurface.ssfmax[i] ≈ 28.32720603576582f0 + @test subsurface.ssf[i] ≈ 11683.330684556406f0 end @testset "exponential constant profile" begin config.input.vertical.ksat_profile = "exponential_constant" model = Wflow.initialize_sbm_model(config) - (; vertical) = model - z = vertical.zi[i] - kv_z = - Wflow.hydraulic_conductivity_at_depth(vertical, z, i, 2, "exponential_constant") - @test kv_z ≈ vertical.kvfrac[i][2] * vertical.kv_0[i] * exp(-vertical.f[i] * z) - kv_400 = Wflow.hydraulic_conductivity_at_depth( - vertical, - 400.0, - i, - 2, - "exponential_constant", - ) - kv_1000 = Wflow.hydraulic_conductivity_at_depth( - vertical, - 1000.0, - i, - 3, - "exponential_constant", - ) + (; soil) = model.vertical + (; kv_profile) = soil.parameters + (; subsurface) = model.lateral + z = soil.variables.zi[i] + kvfrac = soil.parameters.kvfrac + kv_z = Wflow.hydraulic_conductivity_at_depth(kv_profile, kvfrac, z, i, 2) + @test kv_z ≈ + kvfrac[i][2] * + kv_profile.exponential.kv_0[i] * + exp(-kv_profile.exponential.f[i] * z) + kv_400 = Wflow.hydraulic_conductivity_at_depth(kv_profile, kvfrac, 400.0, i, 2) + kv_1000 = Wflow.hydraulic_conductivity_at_depth(kv_profile, kvfrac, 1000.0, i, 3) @test kv_400 ≈ kv_1000 - @test_throws ErrorException Wflow.kh_layered_profile( - vertical, - 100.0, - i, - "exponential_constant", - ) - @test all(isnan.(vertical.z_layered)) - @test all(isnan.(vertical.kv[i])) - @test all(vertical.nlayers_kv .== 0) - @test all(vertical.z_exp .== 400.0) + @test all(kv_profile.z_exp .== 400.0) + @test subsurface.ssfmax[i] ≈ 49.38558575188426f0 + @test subsurface.ssf[i] ≈ 24810.460986497365f0 end @testset "layered profile" begin config.input.vertical.ksat_profile = "layered" model = Wflow.initialize_sbm_model(config) - (; vertical) = model - z = vertical.zi[i] - @test Wflow.hydraulic_conductivity_at_depth(vertical, z, i, 2, "layered") ≈ - vertical.kv[100][2] - @test Wflow.kh_layered_profile(vertical, 100.0, i, "layered") ≈ 47.508932674632355f0 - @test vertical.nlayers_kv[i] == 4 - @test vertical.z_layered == vertical.soilthickness - @test all(isnan.(vertical.z_exp)) + (; soil) = model.vertical + (; kv_profile) = soil.parameters + (; subsurface) = model.lateral + z = soil.variables.zi[i] + kvfrac = soil.parameters.kvfrac + @test Wflow.hydraulic_conductivity_at_depth(kv_profile, kvfrac, z, i, 2) ≈ + kv_profile.kv[i][2] + Wflow.kh_layered_profile!(soil, subsurface, kv_profile, 86400.0) + @test subsurface.kh_profile.kh[i] ≈ 47.508932674632355f0 + @test subsurface.ssfmax[i] ≈ 30.237094380100316f0 + @test subsurface.ssf[i] ≈ 14546.518932613191f0 end @testset "layered exponential profile" begin config.input.vertical.ksat_profile = "layered_exponential" model = Wflow.initialize_sbm_model(config) - (; vertical) = model - z = vertical.zi[i] - @test Wflow.hydraulic_conductivity_at_depth( - vertical, - z, - i, - 2, - "layered_exponential", - ) ≈ vertical.kv[i][2] - @test vertical.nlayers_kv[i] == 2 - @test Wflow.kh_layered_profile(vertical, 100.0, i, "layered_exponential") ≈ - 33.76026208801769f0 - @test all(vertical.z_layered[1:10] .== 400.0) - @test all(isnan.(vertical.z_exp)) + (; soil) = model.vertical + (; kv_profile) = soil.parameters + (; subsurface) = model.lateral + z = soil.variables.zi[i] + kvfrac = soil.parameters.kvfrac + @test Wflow.hydraulic_conductivity_at_depth(kv_profile, kvfrac, z, i, 2) ≈ + kv_profile.kv[i][2] + @test kv_profile.nlayers_kv[i] == 2 + Wflow.kh_layered_profile!(soil, subsurface, kv_profile, 86400.0) + @test subsurface.kh_profile.kh[i] ≈ 33.76026208801769f0 + @test all(kv_profile.z_layered[1:10] .== 400.0) + @test subsurface.ssfmax[i] ≈ 23.4840490395906f0 + @test subsurface.ssf[i] ≈ 10336.88327617503f0 end - model = Wflow.run_timestep(model) - model = Wflow.run_timestep(model) @testset "river flow layered exponential profile" begin + model = Wflow.initialize_sbm_model(config) + Wflow.run_timestep!(model) + Wflow.run_timestep!(model) q = model.lateral.river.q_av @test sum(q) ≈ 3159.38300016008f0 @test q[1622] ≈ 0.0005972577112819149f0 @test q[43] ≈ 10.017642376280731f0 end - Wflow.close_files(model, delete_output = false) + Wflow.close_files(model; delete_output = false) end diff --git a/test/run_sbm_gwf.jl b/test/run_sbm_gwf.jl index 3af207643..60b77a8de 100644 --- a/test/run_sbm_gwf.jl +++ b/test/run_sbm_gwf.jl @@ -5,7 +5,7 @@ config = Wflow.Config(tomlpath) model = Wflow.initialize_sbm_gwf_model(config) (; network) = model -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) # test if the first timestep was written to the CSV file flush(model.writer.csv_io) # ensure the buffer is written fully to disk @@ -21,21 +21,21 @@ end sbm = model.vertical @test model.clock.iteration == 1 - @test sbm.theta_s[1] ≈ 0.44999998807907104f0 - @test sbm.runoff[1] == 0.0 - @test sbm.soilevap[1] == 0.0 - @test sbm.transpiration[1] ≈ 0.30587632831650247f0 + @test sbm.soil.parameters.theta_s[1] ≈ 0.44999998807907104f0 + @test sbm.soil.variables.runoff[1] == 0.0 + @test sbm.soil.variables.soilevap[1] == 0.0 + @test sbm.soil.variables.transpiration[1] ≈ 0.30587632831650247f0 end # run the second timestep -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) @testset "second timestep" begin sbm = model.vertical - @test sbm.theta_s[1] ≈ 0.44999998807907104f0 - @test sbm.runoff[1] == 0.0 - @test sbm.soilevap[1] == 0.0 - @test sbm.transpiration[4] ≈ 0.7000003898938235f0 + @test sbm.soil.parameters.theta_s[1] ≈ 0.44999998807907104f0 + @test sbm.soil.variables.runoff[1] == 0.0 + @test sbm.soil.variables.soilevap[1] == 0.0 + @test sbm.soil.variables.transpiration[4] ≈ 0.7000003898938235f0 end @testset "overland flow (kinematic wave)" begin @@ -76,7 +76,7 @@ end @test collect(keys(model.lateral.subsurface)) == [:flow, :recharge, :river] end -Wflow.close_files(model, delete_output = false) +Wflow.close_files(model; delete_output = false) # test local-inertial option for river flow routing tomlpath = joinpath(@__DIR__, "sbm_gwf_config.toml") @@ -87,8 +87,8 @@ config.input.lateral.river.bankfull_elevation = "bankfull_elevation" config.input.lateral.river.bankfull_depth = "bankfull_depth" model = Wflow.initialize_sbm_gwf_model(config) -model = Wflow.run_timestep(model) -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) +Wflow.run_timestep!(model) @testset "river domain (local inertial)" begin q = model.lateral.river.q_av @@ -100,7 +100,7 @@ model = Wflow.run_timestep(model) @test q[13] ≈ 0.0004638698607639214f0 @test q[5] ≈ 0.0064668491697542786f0 end -Wflow.close_files(model, delete_output = false) +Wflow.close_files(model; delete_output = false) # test local-inertial option for river and overland flow routing tomlpath = joinpath(@__DIR__, "sbm_gwf_config.toml") @@ -118,8 +118,8 @@ config.state.lateral.land.qx = "qx_land" config.state.lateral.land.qy = "qy_land" model = Wflow.initialize_sbm_gwf_model(config) -model = Wflow.run_timestep(model) -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) +Wflow.run_timestep!(model) @testset "river and land domain (local inertial)" begin q = model.lateral.river.q_av @@ -136,7 +136,7 @@ model = Wflow.run_timestep(model) @test all(qx .== 0.0f0) @test all(qy .== 0.0f0) end -Wflow.close_files(model, delete_output = false) +Wflow.close_files(model; delete_output = false) # test with warm start tomlpath = joinpath(@__DIR__, "sbm_gwf_config.toml") @@ -146,14 +146,14 @@ config.model.reinit = false model = Wflow.initialize_sbm_gwf_model(config) (; network) = model -model = Wflow.run_timestep(model) -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) +Wflow.run_timestep!(model) @testset "second timestep warm start" begin sbm = model.vertical - @test sbm.runoff[1] == 0.0 - @test sbm.soilevap[1] ≈ 0.2889306511074693f0 - @test sbm.transpiration[1] ≈ 0.8370726722706481f0 + @test sbm.soil.variables.runoff[1] == 0.0 + @test sbm.soil.variables.soilevap[1] ≈ 0.2889306511074693f0 + @test sbm.soil.variables.transpiration[1] ≈ 0.8370726722706481f0 end @testset "overland flow warm start (kinematic wave)" begin @@ -186,4 +186,21 @@ end @test gw.drain.flux[1] ≈ 0.0 @test gw.recharge.rate[19] ≈ -0.0014241196552847502f0 end -Wflow.close_files(model, delete_output = false) + +@testset "Exchange and grid location aquifer, recharge and constant head" begin + aquifer = model.lateral.subsurface.flow.aquifer + @test Wflow.exchange(aquifer.head) == true + @test Wflow.exchange(aquifer.k) == true + @test Wflow.grid_loc(aquifer, :head) == "node" + @test Wflow.grid_loc(aquifer, :k) == "node" + recharge = model.lateral.subsurface.recharge + @test Wflow.exchange(recharge.rate) == true + @test Wflow.exchange(recharge.flux) == true + @test Wflow.grid_loc(recharge, :rate) == "node" + @test Wflow.grid_loc(recharge, :flux) == "node" + constanthead = model.lateral.subsurface.flow.constanthead + @test Wflow.exchange(constanthead) == false + @test Wflow.grid_loc(constanthead, :head) == "node" +end + +Wflow.close_files(model; delete_output = false) diff --git a/test/run_sbm_gwf_piave.jl b/test/run_sbm_gwf_piave.jl index 2f39b5a90..28e31a415 100644 --- a/test/run_sbm_gwf_piave.jl +++ b/test/run_sbm_gwf_piave.jl @@ -1,50 +1,56 @@ tomlpath = joinpath(@__DIR__, "sbm_gwf_piave_demand_config.toml") config = Wflow.Config(tomlpath) model = Wflow.initialize_sbm_gwf_model(config) -model = Wflow.run_timestep(model) -sbm = model.vertical +Wflow.run_timestep!(model) +(; paddy, nonpaddy, industry, livestock, domestic) = model.vertical.demand +(; total_alloc, irri_alloc, nonirri_alloc, surfacewater_alloc, act_groundwater_abst) = + model.vertical.allocation.variables @testset "piave water demand and allocation first timestep" begin - sum_total_alloc = sum(sbm.allocation.total_alloc) - @test sum(sbm.allocation.irri_alloc) + sum(sbm.allocation.nonirri_alloc) ≈ - sum_total_alloc - @test sum(sbm.allocation.surfacewater_alloc) ≈ 1016.4268126167575f0 - @test sum(sbm.allocation.act_groundwater_abst) ≈ 182.2057678312209f0 - @test sbm.paddy.h[[45, 76, 296]] ≈ + sum_total_alloc = sum(total_alloc) + @test sum(irri_alloc) + sum(nonirri_alloc) ≈ sum_total_alloc + @test sum(surfacewater_alloc) ≈ 1016.4268126167575f0 + @test sum(act_groundwater_abst) ≈ 182.2057678312209f0 + @test paddy.variables.h[[45, 76, 296]] ≈ [33.55659436283413f0, 44.11663357735189f0, 35.232731550849486f0] - @test sbm.paddy.irrigation_trigger[[45, 76, 296]] == [1, 1, 1] - @test sbm.paddy.demand_gross[[45, 76, 296]] ≈ [0.0, 0.0, 0.0] - @test sbm.nonpaddy.irrigation_trigger[[10, 33, 1293]] == [1, 1, 1] - @test sbm.nonpaddy.demand_gross[[10, 33, 1293]] ≈ [3.3014913197447964f0, 0.0f0, 0.0f0] - @test sbm.industry.demand_gross[[1, end]] ≈ [0.2105557769536972f0, 0.0485190823674202f0] - @test sbm.industry.demand_net[[1, end]] ≈ + @test paddy.parameters.irrigation_trigger[[45, 76, 296]] == [1, 1, 1] + @test paddy.variables.demand_gross[[45, 76, 296]] ≈ [0.0, 0.0, 0.0] + @test nonpaddy.parameters.irrigation_trigger[[10, 33, 1293]] == [1, 1, 1] + @test nonpaddy.variables.demand_gross[[10, 33, 1293]] ≈ + [3.3014913197447964f0, 0.0f0, 0.0f0] + @test industry.demand.demand_gross[[1, end]] ≈ + [0.2105557769536972f0, 0.0485190823674202f0] + @test industry.demand.demand_net[[1, end]] ≈ [0.05265098437666893f0, 0.012132546864449978f0] - @test sbm.industry.returnflow[[1, end]] ≈ [0.15790479257702827f0, 0.03638653550297022f0] - @test sbm.livestock.demand_gross[[1, end]] ≈ + @test industry.variables.returnflow[[1, end]] ≈ + [0.15790479257702827f0, 0.03638653550297022f0] + @test livestock.demand.demand_gross[[1, end]] ≈ [9.896758274408057f-5, 6.352497439365834f-5] - @test sbm.livestock.demand_net[[1, end]] ≈ [9.896758274408057f-5, 6.352497439365834f-5] - @test sbm.livestock.returnflow[[1, end]] ≈ [0.0f0, 0.0f0] - @test sbm.domestic.demand_gross[[1, end]] ≈ [0.5389957427978516f0, 0.0f0] - @test sbm.domestic.demand_net[[1, end]] ≈ [0.33949509263038635f0, 0.0f0] - @test sbm.domestic.returnflow[[1, end]] ≈ [0.1995004952035704f0, 0.0f0] + @test livestock.demand.demand_net[[1, end]] ≈ + [9.896758274408057f-5, 6.352497439365834f-5] + @test livestock.variables.returnflow[[1, end]] ≈ [0.0f0, 0.0f0] + @test domestic.demand.demand_gross[[1, end]] ≈ [0.5389957427978516f0, 0.0f0] + @test domestic.demand.demand_net[[1, end]] ≈ [0.33949509263038635f0, 0.0f0] + @test domestic.variables.returnflow[[1, end]] ≈ [0.1995004952035704f0, 0.0f0] end -model = Wflow.run_timestep(model) -sbm = model.vertical +Wflow.run_timestep!(model) +(; paddy, nonpaddy, industry, livestock, domestic) = model.vertical.demand +(; total_alloc, irri_alloc, nonirri_alloc, surfacewater_alloc, act_groundwater_abst) = + model.vertical.allocation.variables @testset "piave water demand and allocation second timestep" begin - sum_total_alloc = sum(sbm.allocation.total_alloc) - @test sum(sbm.allocation.irri_alloc) + sum(sbm.allocation.nonirri_alloc) ≈ - sum_total_alloc - @test sum(sbm.allocation.surfacewater_alloc) ≈ 1057.2573971583527f0 - @test sum(sbm.allocation.act_groundwater_abst) ≈ 189.12436534660375f0 - @test sbm.paddy.h[[45, 76, 296]] ≈ + sum_total_alloc = sum(total_alloc) + @test sum(irri_alloc) + sum(nonirri_alloc) ≈ sum_total_alloc + @test sum(surfacewater_alloc) ≈ 1057.2573971583527f0 + @test sum(act_groundwater_abst) ≈ 189.12436534660375f0 + @test paddy.variables.h[[45, 76, 296]] ≈ [28.197082339552537f0, 25.873022895247782f0, 30.066801639786547f0] - @test sbm.paddy.irrigation_trigger[[45, 76, 296]] == [1, 1, 1] - @test sbm.paddy.demand_gross[[45, 76, 296]] ≈ [0.0f0, 0.0f0, 0.0f0] - @test sbm.nonpaddy.irrigation_trigger[[10, 33, 1293]] == [1, 1, 1] - @test sbm.nonpaddy.demand_gross[[10, 33, 1293]] ≈ + @test paddy.parameters.irrigation_trigger[[45, 76, 296]] == [1, 1, 1] + @test paddy.variables.demand_gross[[45, 76, 296]] ≈ [0.0f0, 0.0f0, 0.0f0] + @test nonpaddy.parameters.irrigation_trigger[[10, 33, 1293]] == [1, 1, 1] + @test nonpaddy.variables.demand_gross[[10, 33, 1293]] ≈ [4.059144161330735f0, 0.0f0, 1.9399078662788196f0] end -Wflow.close_files(model, delete_output = false) +Wflow.close_files(model; delete_output = false) diff --git a/test/run_sbm_piave.jl b/test/run_sbm_piave.jl index 1d5a2fae6..a409cad08 100644 --- a/test/run_sbm_piave.jl +++ b/test/run_sbm_piave.jl @@ -3,8 +3,8 @@ function run_piave(model, steps) q = zeros(steps) ssf_vol = zeros(steps) riv_vol = zeros(steps) - for i = 1:steps - model = Wflow.run_timestep(model) + for i in 1:steps + Wflow.run_timestep!(model) ssf_vol[i] = mean(model.lateral.subsurface.volume) riv_vol[i] = mean(model.lateral.river.volume) q[i] = model.lateral.river.q_av[1] @@ -16,13 +16,13 @@ tomlpath = joinpath(@__DIR__, "sbm_piave_demand_config.toml") config = Wflow.Config(tomlpath) model = Wflow.initialize_sbm_model(config) q_demand, riv_vol_demand, ssf_vol_demand = run_piave(model, 30) -Wflow.close_files(model, delete_output = false) +Wflow.close_files(model; delete_output = false) tomlpath = joinpath(@__DIR__, "sbm_piave_config.toml") config = Wflow.Config(tomlpath) model = Wflow.initialize_sbm_model(config) q_, riv_vol, ssf_vol = run_piave(model, 30) -Wflow.close_files(model, delete_output = false) +Wflow.close_files(model; delete_output = false) @testset "piave with and without water demand" begin idx = 1:3:28 @@ -103,51 +103,57 @@ end tomlpath = joinpath(@__DIR__, "sbm_piave_demand_config.toml") config = Wflow.Config(tomlpath) model = Wflow.initialize_sbm_model(config) -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) sbm = model.vertical +(; paddy, nonpaddy, industry, livestock, domestic) = model.vertical.demand +(; total_alloc, irri_alloc, nonirri_alloc, surfacewater_alloc, act_groundwater_abst) = + sbm.allocation.variables @testset "piave water demand and allocation first timestep" begin - sum_total_alloc = sum(sbm.allocation.total_alloc) - @test sum(sbm.allocation.irri_alloc) + sum(sbm.allocation.nonirri_alloc) ≈ - sum_total_alloc - @test sum(sbm.allocation.surfacewater_alloc) ≈ 1030.1528204311428f0 - @test sum(sbm.allocation.act_groundwater_abst) ≈ 184.47031930837645f0 - @test sbm.paddy.h[[45, 76, 296]] ≈ + sum_total_alloc = sum(total_alloc) + @test sum(irri_alloc) + sum(nonirri_alloc) ≈ sum_total_alloc + @test sum(surfacewater_alloc) ≈ 1030.1528204311428f0 + @test sum(act_groundwater_abst) ≈ 184.47031930837645f0 + @test paddy.variables.h[[45, 76, 296]] ≈ [34.759292485507515f0, 42.517504464353635f0, 35.83766686539591f0] - @test sbm.paddy.irrigation_trigger[[45, 76, 296]] == [1, 1, 1] - @test sbm.paddy.demand_gross[[45, 76, 296]] ≈ [0.0, 0.0, 0.0] - @test sbm.nonpaddy.irrigation_trigger[[10, 33, 1293]] == [1, 1, 1] - @test sbm.nonpaddy.demand_gross[[10, 33, 1293]] ≈ + @test paddy.parameters.irrigation_trigger[[45, 76, 296]] == [1, 1, 1] + @test paddy.variables.demand_gross[[45, 76, 296]] ≈ [0.0, 0.0, 0.0] + @test nonpaddy.parameters.irrigation_trigger[[10, 33, 1293]] == [1, 1, 1] + @test nonpaddy.variables.demand_gross[[10, 33, 1293]] ≈ [3.031574420740574f0, 1.8618934872392217f0, 0.42233065562148375f0] - @test sbm.industry.demand_gross[[1, end]] ≈ [0.2105557769536972f0, 0.0485190823674202f0] - @test sbm.industry.demand_net[[1, end]] ≈ + @test industry.demand.demand_gross[[1, end]] ≈ + [0.2105557769536972f0, 0.0485190823674202f0] + @test industry.demand.demand_net[[1, end]] ≈ [0.05265098437666893f0, 0.012132546864449978f0] - @test sbm.industry.returnflow[[1, end]] ≈ [0.15790479257702827f0, 0.03638653550297022f0] - @test sbm.livestock.demand_gross[[1, end]] ≈ + @test industry.variables.returnflow[[1, end]] ≈ + [0.15790479257702827f0, 0.03638653550297022f0] + @test livestock.demand.demand_gross[[1, end]] ≈ [9.896758274408057f-5, 6.352497439365834f-5] - @test sbm.livestock.demand_net[[1, end]] ≈ [9.896758274408057f-5, 6.352497439365834f-5] - @test sbm.livestock.returnflow[[1, end]] ≈ [0.0f0, 0.0f0] - @test sbm.domestic.demand_gross[[1, end]] ≈ [0.5389957427978516f0, 0.0f0] - @test sbm.domestic.demand_net[[1, end]] ≈ [0.33949509263038635f0, 0.0f0] - @test sbm.domestic.returnflow[[1, end]] ≈ [0.1995004952035704f0, 0.0f0] + @test livestock.demand.demand_net[[1, end]] ≈ + [9.896758274408057f-5, 6.352497439365834f-5] + @test livestock.variables.returnflow[[1, end]] ≈ [0.0f0, 0.0f0] + @test domestic.demand.demand_gross[[1, end]] ≈ [0.5389957427978516f0, 0.0f0] + @test domestic.demand.demand_net[[1, end]] ≈ [0.33949509263038635f0, 0.0f0] + @test domestic.variables.returnflow[[1, end]] ≈ [0.1995004952035704f0, 0.0f0] end -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) sbm = model.vertical - +(; paddy, nonpaddy, industry, livestock, domestic) = model.vertical.demand +(; total_alloc, irri_alloc, nonirri_alloc, surfacewater_alloc, act_groundwater_abst) = + sbm.allocation.variables @testset "piave water demand and allocation second timestep" begin - sum_total_alloc = sum(sbm.allocation.total_alloc) - @test sum(sbm.allocation.irri_alloc) + sum(sbm.allocation.nonirri_alloc) ≈ - sum_total_alloc - @test sum(sbm.allocation.surfacewater_alloc) ≈ 940.1941924010235f0 - @test sum(sbm.allocation.act_groundwater_abst) ≈ 163.59123515939052f0 - @test sbm.paddy.h[[45, 76, 296]] ≈ + sum_total_alloc = sum(total_alloc) + @test sum(irri_alloc) + sum(nonirri_alloc) ≈ sum_total_alloc + @test sum(surfacewater_alloc) ≈ 940.1941924010235f0 + @test sum(act_groundwater_abst) ≈ 163.59123515939052f0 + @test paddy.variables.h[[45, 76, 296]] ≈ [29.50674105890283f0, 38.11966817469463f0, 30.679920457042897f0] - @test sbm.paddy.irrigation_trigger[[45, 76, 296]] == [1, 1, 1] - @test sbm.paddy.demand_gross[[45, 76, 296]] ≈ [0.0f0, 0.0f0, 0.0f0] - @test sbm.nonpaddy.irrigation_trigger[[10, 33, 1293]] == [1, 1, 1] - @test sbm.nonpaddy.demand_gross[[10, 33, 1293]] ≈ + @test paddy.parameters.irrigation_trigger[[45, 76, 296]] == [1, 1, 1] + @test paddy.variables.demand_gross[[45, 76, 296]] ≈ [0.0f0, 0.0f0, 0.0f0] + @test nonpaddy.parameters.irrigation_trigger[[10, 33, 1293]] == [1, 1, 1] + @test nonpaddy.variables.demand_gross[[10, 33, 1293]] ≈ [3.8551909476218054f0, 0.0f0, 1.3531153828385536f0] end -Wflow.close_files(model, delete_output = false) +Wflow.close_files(model; delete_output = false) diff --git a/test/run_sediment.jl b/test/run_sediment.jl index f8b323996..58c0096bc 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -6,7 +6,7 @@ config = Wflow.Config(tomlpath) model = Wflow.initialize_sediment_model(config) (; network) = model -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) @testset "first timestep sediment model (vertical)" begin eros = model.vertical @@ -21,7 +21,7 @@ model = Wflow.run_timestep(model) end # run the second timestep -model = Wflow.run_timestep(model) +Wflow.run_timestep!(model) @testset "second timestep sediment model (vertical)" begin eros = model.vertical @@ -51,4 +51,20 @@ end @test lat.river.outclay[5649] ≈ 2.359031898208781f-9 end +@testset "Exchange and grid location sediment" begin + @test Wflow.exchange(model.vertical.n) == false + @test Wflow.exchange(model.vertical.erosk) == true + @test Wflow.exchange(model.vertical.leaf_area_index) == true + @test Wflow.grid_loc(model.vertical, :n) == "none" + @test Wflow.grid_loc(model.vertical, :erosk) == "node" + @test Wflow.grid_loc(model.vertical, :leaf_area_index) == "node" + land = model.lateral.land + @test Wflow.exchange(land.n) == false + @test Wflow.exchange(land.soilloss) == true + @test Wflow.exchange(land.inlandsed) == true + @test Wflow.grid_loc(land, :n) == "none" + @test Wflow.grid_loc(land, :soilloss) == "node" + @test Wflow.grid_loc(land, :inlandsed) == "node" +end + Wflow.close_files(model) diff --git a/test/runtests.jl b/test/runtests.jl index 67947e377..6981af08d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -10,11 +10,11 @@ using Wflow using Base.MathConstants: eulergamma using Base.Threads using BasicModelInterface -import Polynomials +using Polynomials: Polynomials using DelimitedFiles using LoggingExtras using QuadGK -import Aqua +using Aqua: Aqua const BMI = BasicModelInterface const Float = Wflow.Float @@ -37,11 +37,7 @@ end staticmaps_rhine_path = testdata(v"0.1", "staticmaps.nc", "staticmaps-rhine.nc") staticmaps_moselle_path = testdata(v"0.2.9", "staticmaps-moselle.nc", "staticmaps-moselle.nc") -staticmaps_lahn_path = testdata(v"0.2.1", "staticmaps-lahn.nc", "staticmaps-lahn.nc") -staticmaps_meuse_path = - testdata(v"0.2.8", "staticmaps_flex_meuse.nc", "staticmaps_flex_meuse.nc") forcing_moselle_path = testdata(v"0.2.6", "forcing-moselle.nc", "forcing-moselle.nc") -forcing_lahn_path = testdata(v"0.2", "forcing-lahn.nc", "forcing-lahn.nc") forcing_moselle_sed_path = testdata(v"0.2.3", "forcing-moselle-sed.nc", "forcing-moselle-sed.nc") staticmaps_moselle_sed_path = @@ -59,7 +55,6 @@ forcing_sbm_gw_path = testdata( "forcing-sbm-groundwater-part2.nc", "forcing-sbm-groundwater-part2.nc", ) -forcing_meuse_path = testdata(v"0.2.8", "forcing_meuse.nc", "forcing_meuse.nc") staticmaps_sbm_gw_path = testdata(v"0.2.3", "staticmaps-sbm-groundwater.nc", "staticmaps-sbm-groundwater.nc") instates_sbm_gw_path = @@ -72,7 +67,8 @@ forcing_calendar_noleap_path = forcing_piave_path = testdata(v"0.2.9", "inmaps-era5-2010-piave.nc", "forcing-piave.nc") staticmaps_piave_path = testdata(v"0.2.9", "staticmaps-piave.nc", "staticmaps-piave.nc") instates_piave_path = testdata(v"0.2.9", "instates-piave.nc", "instates-piave.nc") -instates_piave_gwf_path = testdata(v"0.2.9", "instates-piave-gwf.nc", "instates-piave-gwf.nc") +instates_piave_gwf_path = + testdata(v"0.2.9", "instates-piave-gwf.nc", "instates-piave-gwf.nc") include("testing_utils.jl") @@ -89,7 +85,6 @@ with_logger(NullLogger()) do include("run_sbm.jl") include("run_sbm_piave.jl") include("run_sbm_gwf_piave.jl") - include("run_hbv.jl") include("run_sbm_gwf.jl") include("run.jl") include("groundwater.jl") @@ -97,7 +92,6 @@ with_logger(NullLogger()) do include("bmi.jl") include("run_sediment.jl") include("subdomains.jl") - include("run_flextopo.jl") Aqua.test_all(Wflow; ambiguities = false, persistent_tasks = false) end diff --git a/test/sbm_config.toml b/test/sbm_config.toml index 3ab5c92d0..39898a488 100644 --- a/test/sbm_config.toml +++ b/test/sbm_config.toml @@ -19,14 +19,18 @@ path_output = "outstates-moselle.nc" # if listed, the variable must be present in the NetCDF or error # if not listed, the variable can get a default value if it has one -[state.vertical] -canopystorage = "canopystorage" +[state.vertical.interception.variables] +canopy_storage = "canopystorage" + +[state.vertical.soil.variables] satwaterdepth = "satwaterdepth" -snow = "snow" -snowwater = "snowwater" tsoil = "tsoil" ustorelayerdepth = "ustorelayerdepth" +[state.vertical.snow.variables] +snow_storage = "snow" +snow_water = "snowwater" + [state.lateral.river] h = "h_river" h_av = "h_av_river" @@ -56,42 +60,52 @@ subcatchment = "wflow_subcatch" # specify the internal IDs of the parameters which vary over time # the external name mapping needs to be below together with the other mappings forcing = [ - "vertical.precipitation", - "vertical.temperature", - "vertical.potential_evaporation", + "vertical.atmospheric_forcing.precipitation", + "vertical.atmospheric_forcing.temperature", + "vertical.atmospheric_forcing.potential_evaporation", ] -cyclic = ["vertical.leaf_area_index"] +cyclic = ["vertical.vegetation_parameter_set.leaf_area_index"] + +[input.vertical.vegetation_parameter_set] +leaf_area_index = "LAI" +kext = "Kext" +storage_specific_leaf = "Sl" +storage_wood = "Swood" +rootingdepth = "RootingDepth" + +[input.vertical.runoff.parameters] +waterfrac = "WaterFrac" + +[input.vertical.interception.parameters] +e_r = "EoverR" -[input.vertical] +[input.vertical.atmospheric_forcing] +potential_evaporation = "pet" +precipitation = "precip" +temperature = "temp" + +[input.vertical.soil.parameters] c = "c" cf_soil = "cf_soil" -cfmax = "Cfmax" -e_r = "EoverR" f = "f" infiltcappath = "InfiltCapPath" infiltcapsoil = "InfiltCapSoil" -kext = "Kext" -leaf_area_index = "LAI" +water_holding_capacity = "WHC" +theta_r = "thetaR" +theta_s = "thetaS" maxleakage = "MaxLeakage" pathfrac = "PathFrac" -potential_evaporation = "pet" -precipitation = "precip" rootdistpar = "rootdistpar" -rootingdepth = "RootingDepth" soilthickness = "SoilThickness" -specific_leaf = "Sl" -storage_wood = "Swood" -temperature = "temp" + +[input.vertical.snow.parameters] tt = "TT" tti = "TTI" ttm = "TTM" -water_holding_capacity = "WHC" -waterfrac = "WaterFrac" -theta_r = "thetaR" -theta_s = "thetaS" +cfmax = "Cfmax" -[input.vertical.kv_0] +[input.vertical.soil.parameters.kv_0] netcdf.variable.name = "KsatVer" scale = 1.0 offset = 0.0 @@ -135,14 +149,18 @@ min_streamorder_land = 5 [output] path = "output_moselle.nc" -[output.vertical] -canopystorage = "canopystorage" +[output.vertical.interception.variables] +canopy_storage = "canopystorage" + +[output.vertical.soil.variables] satwaterdepth = "satwaterdepth" -snow = "snow" -snowwater = "snowwater" tsoil = "tsoil" ustorelayerdepth = "ustorelayerdepth" +[output.vertical.snow.variables] +snow_storage = "snow" +snow_water = "snowwater" + [output.lateral.river] h = "h_river" q = "q_river" @@ -170,14 +188,14 @@ coordinate.x = 6.255 coordinate.y = 50.012 name = "temp_coord" location = "temp_bycoord" -parameter = "vertical.temperature" +parameter = "vertical.atmospheric_forcing.temperature" [[netcdf.variable]] location = "temp_byindex" name = "temp_index" index.x = 100 index.y = 264 -parameter = "vertical.temperature" +parameter = "vertical.atmospheric_forcing.temperature" [csv] path = "output_moselle.csv" @@ -196,20 +214,20 @@ parameter = "lateral.river.reservoir.volume" coordinate.x = 6.255 coordinate.y = 50.012 header = "temp_bycoord" -parameter = "vertical.temperature" +parameter = "vertical.atmospheric_forcing.temperature" [[csv.column]] coordinate.x = 6.255 coordinate.y = 50.012 header = "vwc_layer2_bycoord" -parameter = "vertical.vwc" +parameter = "vertical.soil.variables.vwc" layer = 2 [[csv.column]] header = "temp_byindex" index.x = 100 index.y = 264 -parameter = "vertical.temperature" +parameter = "vertical.atmospheric_forcing.temperature" [[csv.column]] header = "Q" @@ -219,12 +237,24 @@ parameter = "lateral.river.q" [[csv.column]] header = "recharge" map = "subcatchment" -parameter = "vertical.recharge" +parameter = "vertical.soil.variables.recharge" reducer = "mean" [API] components = [ - "vertical", + "vertical.atmospheric_forcing", + "vertical.vegetation_parameter_set", + "vertical.runoff.boundary_conditions", + "vertical.runoff.variables", + "vertical.runoff.parameters", + "vertical.soil.boundary_conditions", + "vertical.soil.variables", + "vertical.soil.parameters", + "vertical.interception.variables", + "vertical.interception.parameters", + "vertical.snow.boundary_conditions", + "vertical.snow.variables", + "vertical.snow.parameters", "lateral.subsurface", "lateral.land", "lateral.river", diff --git a/test/sbm_gw.toml b/test/sbm_gw.toml index b7431955d..a18965254 100644 --- a/test/sbm_gw.toml +++ b/test/sbm_gw.toml @@ -18,14 +18,18 @@ path_output = "outstates-moselle.nc" # if listed, the variable must be present in the NetCDF or error # if not listed, the variable can get a default value if it has one -[state.vertical] -canopystorage = "canopystorage" +[state.vertical.interception.variables] +canopy_storage = "canopystorage" + +[state.vertical.soil.variables] satwaterdepth = "satwaterdepth" -snow = "snow" -snowwater = "snowwater" tsoil = "tsoil" ustorelayerdepth = "ustorelayerdepth" +[state.vertical.snow.variables] +snow_storage = "snow" +snow_water = "snowwater" + [state.lateral.river] h = "h_river" h_av = "h_av_river" @@ -52,14 +56,29 @@ subcatchment = "wflow_subcatch" # specify the internal IDs of the parameters which vary over time # the external name mapping needs to be below together with the other mappings forcing = [ - "vertical.precipitation", - "vertical.temperature", - "vertical.potential_evaporation", + "vertical.atmospheric_forcing.precipitation", + "vertical.atmospheric_forcing.temperature", + "vertical.atmospheric_forcing.potential_evaporation", ] -cyclic = ["vertical.leaf_area_index"] +cyclic = ["vertical.vegetation_parameter_set.leaf_area_index"] + +[input.vertical.atmospheric_forcing] +potential_evaporation = "pet" +precipitation = "precip" +temperature = "temp" + +[input.vertical.vegetation_parameter_set] +leaf_area_index = "LAI" +kext = "Kext" +storage_specific_leaf = "Sl" +storage_wood = "Swood" +rootingdepth = "RootingDepth" -[input.vertical] +[input.vertical.runoff.parameters] +waterfrac = "WaterFrac" + +[input.vertical.soil.parameters] c = "c" cf_soil = "cf_soil" cfmax = "Cfmax" @@ -67,26 +86,19 @@ e_r = "EoverR" f = "f" infiltcappath = "InfiltCapPath" infiltcapsoil = "InfiltCapSoil" -kext = "Kext" kv_0 = "KsatVer" -leaf_area_index = "LAI" maxleakage = "MaxLeakage" pathfrac = "PathFrac" -potential_evaporation = "pet" -precipitation = "precip" rootdistpar = "rootdistpar" rootingdepth = "RootingDepth" soilthickness = "SoilThickness" -specific_leaf = "Sl" -storage_wood = "Swood" -temperature = "temp" +theta_r = "thetaR" +theta_s = "thetaS" + +[input.vertical.snow.parameters] tt = "TT" tti = "TTI" ttm = "TTM" -water_holding_capacity = "WHC" -waterfrac = "WaterFrac" -theta_r = "thetaR" -theta_s = "thetaS" [input.lateral.river] length = "wflow_riverlength" @@ -120,14 +132,18 @@ type = "sbm" [output] path = "output_moselle.nc" -[output.vertical] -canopystorage = "canopystorage" +[output.vertical.interception.variables] +canopy_storage = "canopystorage" + +[output.vertical.soil.variables] satwaterdepth = "satwaterdepth" -snow = "snow" -snowwater = "snowwater" tsoil = "tsoil" ustorelayerdepth = "ustorelayerdepth" +[output.vertical.snow.variables] +snow_storage = "snow" +snow_water = "snowwater" + [output.lateral.river] h = "h_river" q = "q_river" @@ -152,14 +168,14 @@ coordinate.x = 6.255 coordinate.y = 50.012 name = "temp_coord" location = "temp_bycoord" -parameter = "vertical.temperature" +parameter = "vertical.atmospheric_forcing.temperature" [[netcdf.variable]] location = "temp_byindex" name = "temp_index" index.x = 100 index.y = 264 -parameter = "vertical.temperature" +parameter = "vertical.atmospheric_forcing.temperature" [csv] path = "output_moselle.csv" @@ -178,13 +194,13 @@ parameter = "lateral.river.reservoir.volume" coordinate.x = 6.255 coordinate.y = 50.012 header = "temp_bycoord" -parameter = "vertical.temperature" +parameter = "vertical.atmospheric_forcing.temperature" [[csv.column]] header = "temp_byindex" index.x = 100 index.y = 264 -parameter = "vertical.temperature" +parameter = "vertical.atmospheric_forcing.temperature" [[csv.column]] header = "Q" @@ -194,7 +210,7 @@ parameter = "lateral.river.q" [[csv.column]] header = "recharge" map = "subcatchment" -parameter = "vertical.recharge" +parameter = "vertical.soil.variables.recharge" reducer = "mean" [API] diff --git a/test/sbm_gwf_config.toml b/test/sbm_gwf_config.toml index d94b203bf..f08431236 100644 --- a/test/sbm_gwf_config.toml +++ b/test/sbm_gwf_config.toml @@ -18,8 +18,10 @@ path_output = "outstates-example-sbm-gwf.nc" # if listed, the variable must be present in the NetCDF or error # if not listed, the variable can get a default value if it has one -[state.vertical] -canopystorage = "canopystorage" +[state.vertical.interception.variables] +canopy_storage = "canopystorage" + +[state.vertical.soil.variables] satwaterdepth = "satwaterdepth" ustorelayerdepth = "ustorelayerdepth" @@ -49,21 +51,26 @@ altitude = "wflow_dem" # specify the internal IDs of the parameters which vary over time # the external name mapping needs to be below together with the other mappings forcing = [ - "vertical.precipitation", - "vertical.potential_evaporation", + "vertical.atmospheric_forcing.precipitation", + "vertical.atmospheric_forcing.potential_evaporation", ] -[input.vertical] +[input.vertical.vegetation_parameter_set] +rootingdepth = "rootingdepth" canopygapfraction = "canopygapfraction" cmax = "cmax" + +[input.vertical.interception.parameters] e_r = "EoverR" + +[input.vertical.atmospheric_forcing] +potential_evaporation = "PET" +precipitation = "P" + +[input.vertical.soil.parameters] infiltcapsoil = "InfiltCapSoil" kv_0 = "kv" -m = "M" pathfrac = "PathFrac" -potential_evaporation = "PET" -precipitation = "P" -rootingdepth = "rootingdepth" soilthickness = "soilthickness" theta_r = "thetaR" theta_s = "thetaS" @@ -102,12 +109,16 @@ type = "sbm_gwf" [output] path = "output_example-sbm-gwf.nc" -[output.vertical] -canopystorage = "canopystorage" +[output.vertical.interception.variables] +canopy_storage = "canopystorage" + +[output.vertical.soil.variables] satwaterdepth = "satwaterdepth" -soilthickness = "soilthickness" ustorelayerdepth = "ustorelayerdepth" +[output.vertical.soil.parameters] +soilthickness = "soilthickness" + [output.lateral.river] q = "q" diff --git a/test/sbm_gwf_piave_demand_config.toml b/test/sbm_gwf_piave_demand_config.toml index fa7e4cb44..a9a9cf25a 100644 --- a/test/sbm_gwf_piave_demand_config.toml +++ b/test/sbm_gwf_piave_demand_config.toml @@ -18,11 +18,26 @@ ldd = "wflow_ldd" river_location = "wflow_river" altitude = "wflow_dem" subcatchment = "wflow_subcatch" -forcing = [ "vertical.precipitation", "vertical.temperature", "vertical.potential_evaporation",] -cyclic = [ "vertical.leaf_area_index", "vertical.domestic.demand_gross", "vertical.domestic.demand_net", "vertical.industry.demand_gross", "vertical.industry.demand_net", "vertical.livestock.demand_gross", "vertical.livestock.demand_net", "vertical.paddy.irrigation_trigger", "vertical.nonpaddy.irrigation_trigger",] gauges = "wflow_gauges" gauges_grdc = "wflow_gauges_grdc" +forcing = [ + "vertical.atmospheric_forcing.precipitation", + "vertical.atmospheric_forcing.temperature", + "vertical.atmospheric_forcing.potential_evaporation", +] +cyclic = [ + "vertical.vegetation_parameter_set.leaf_area_index", + "vertical.demand.domestic.demand.demand_gross", + "vertical.demand.domestic.demand.demand_net", + "vertical.demand.industry.demand.demand_gross", + "vertical.demand.industry.demand.demand_net", + "vertical.demand.livestock.demand.demand_gross", + "vertical.demand.livestock.demand.demand_net", + "vertical.demand.paddy.parameters.irrigation_trigger", + "vertical.demand.nonpaddy.parameters.irrigation_trigger", +] + [model] type = "sbm_gwf" constanthead = true @@ -38,65 +53,81 @@ kw_land_tstep = 3600 thicknesslayers = [ 50, 100, 50, 200, 800,] river_routing = "kinematic-wave" -[state.vertical] +[state.vertical.interception.variables] +canopy_storage = "canopystorage" + +[state.vertical.soil.variables] satwaterdepth = "satwaterdepth" -snow = "snow" tsoil = "tsoil" ustorelayerdepth = "ustorelayerdepth" -snowwater = "snowwater" -canopystorage = "canopystorage" -glacierstore = "glacierstore" -[state.vertical.paddy] +[state.vertical.snow.variables] +snow_storage = "snow" +snow_water = "snowwater" + +[state.vertical.glacier.variables] +glacier_store = "glacierstore" + +[state.vertical.demand.paddy.variables] h = "h_paddy" [state.lateral.subsurface.flow.aquifer] head = "head" -[input.vertical] +[input.vertical.glacier.parameters] +glacier_frac = "wflow_glacierfrac" +g_cfmax = "G_Cfmax" +g_tt = "G_TT" +g_sifrac = "G_SIfrac" + +[input.vertical.glacier.variables] +glacier_store = "wflow_glacierstore" + +[input.vertical.vegetation_parameter_set] +leaf_area_index = "LAI" +kext = "Kext" +storage_specific_leaf = "Sl" +storage_wood = "Swood" +rootingdepth = "RootingDepth" +kc = "crop_factor" + +[input.vertical.interception.parameters] +e_r = "EoverR" + +[input.vertical.atmospheric_forcing] +potential_evaporation = "pet" +precipitation = "precip" +temperature = "temp" + +[input.vertical.runoff.parameters] +waterfrac = "WaterFrac" + +[input.vertical.soil.parameters] alpha_h1 = "alpha_h1" -altitude = "wflow_dem" c = "c" cf_soil = "cf_soil" -cfmax = "Cfmax" -e_r = "EoverR" f = "f" +h1 = "h1" +h2 = "h2" +h3_high = "h3_high" +h3_low = "h3_low" +h4 = "h4" infiltcappath = "InfiltCapPath" infiltcapsoil = "InfiltCapSoil" -kext = "Kext" -kv_0 = "KsatVer" -leaf_area_index = "LAI" -m = "M_" +theta_r = "thetaR" +theta_s = "thetaS" maxleakage = "MaxLeakage" pathfrac = "PathFrac" -potential_evaporation = "pet" -precipitation = "precip" rootdistpar = "rootdistpar" -rootingdepth = "RootingDepth" -soilminthickness = "SoilMinThickness" soilthickness = "SoilThickness_gw" -specific_leaf = "Sl" -storage_wood = "Swood" -temperature = "temp" +kv_0 = "KsatVer" +kvfrac = "kvfrac" + +[input.vertical.snow.parameters] tt = "TT" tti = "TTI" ttm = "TTM" -water_holding_capacity = "WHC" -waterfrac = "WaterFrac" -theta_s = "thetaS" -theta_r = "thetaR" -glacierstore = "wflow_glacierstore" -glacierfrac = "wflow_glacierfrac" -g_cfmax = "G_Cfmax" -g_tt = "G_TT" -g_sifrac = "G_SIfrac" -kc = "crop_factor" -kvfrac = "kvfrac" -h1 = "h1" -h2 = "h2" -h3_high = "h3_high" -h3_low = "h3_low" -h4 = "h4" +cfmax = "Cfmax" [model.water_demand] domestic = true @@ -118,27 +149,27 @@ q = "q_land" h = "h_land" h_av = "h_av_land" -[input.vertical.allocation] +[input.vertical.allocation.parameters] areas = "allocation_areas" frac_sw_used = "SurfaceWaterFrac" -[input.vertical.domestic] +[input.vertical.demand.domestic.demand] demand_gross = "dom_gross" demand_net = "dom_net" -[input.vertical.industry] +[input.vertical.demand.industry.demand] demand_gross = "ind_gross" demand_net = "ind_net" -[input.vertical.livestock] +[input.vertical.demand.livestock.demand] demand_gross = "lsk_gross" demand_net = "lsk_net" -[input.vertical.paddy] +[input.vertical.demand.paddy.parameters] irrigation_areas = "paddy_irrigation_areas" irrigation_trigger = "irrigation_trigger" -[input.vertical.nonpaddy] +[input.vertical.demand.nonpaddy.parameters] irrigation_areas = "nonpaddy_irrigation_areas" irrigation_trigger = "irrigation_trigger" @@ -169,7 +200,7 @@ path = "output-piave-gwf.nc" [output.lateral.river] q_av = "q_river" -[output.vertical] +[output.vertical.soil.variables] zi = "zi" [output.lateral.subsurface.flow.aquifer] diff --git a/test/sbm_piave_config.toml b/test/sbm_piave_config.toml index 22c5e30c8..cee9e3685 100644 --- a/test/sbm_piave_config.toml +++ b/test/sbm_piave_config.toml @@ -17,11 +17,17 @@ path_static = "staticmaps-piave.nc" ldd = "wflow_ldd" river_location = "wflow_river" subcatchment = "wflow_subcatch" -forcing = [ "vertical.precipitation", "vertical.temperature", "vertical.potential_evaporation",] -cyclic = [ "vertical.leaf_area_index",] gauges = "wflow_gauges" gauges_grdc = "wflow_gauges_grdc" +forcing = [ + "vertical.atmospheric_forcing.precipitation", + "vertical.atmospheric_forcing.temperature", + "vertical.atmospheric_forcing.potential_evaporation", +] + +cyclic = ["vertical.vegetation_parameter_set.leaf_area_index"] + [model] type = "sbm" masswasting = true @@ -36,58 +42,74 @@ kw_land_tstep = 3600 thicknesslayers = [ 50, 100, 50, 200, 800,] river_routing = "kinematic-wave" -[state.vertical] +[state.vertical.interception.variables] +canopy_storage = "canopystorage" + +[state.vertical.soil.variables] satwaterdepth = "satwaterdepth" -snow = "snow" tsoil = "tsoil" ustorelayerdepth = "ustorelayerdepth" -snowwater = "snowwater" -canopystorage = "canopystorage" -glacierstore = "glacierstore" -[input.vertical] +[state.vertical.snow.variables] +snow_storage = "snow" +snow_water = "snowwater" + +[state.vertical.glacier.variables] +glacier_store = "glacierstore" + +[input.vertical.glacier.parameters] +glacier_frac = "wflow_glacierfrac" +g_cfmax = "G_Cfmax" +g_tt = "G_TT" +g_sifrac = "G_SIfrac" + +[input.vertical.glacier.variables] +glacier_store = "wflow_glacierstore" + +[input.vertical.vegetation_parameter_set] +leaf_area_index = "LAI" +kext = "Kext" +storage_specific_leaf = "Sl" +storage_wood = "Swood" +rootingdepth = "RootingDepth" +kc = "crop_factor" + +[input.vertical.interception.parameters] +e_r = "EoverR" + +[input.vertical.atmospheric_forcing] +potential_evaporation = "pet" +precipitation = "precip" +temperature = "temp" + +[input.vertical.runoff.parameters] +waterfrac = "WaterFrac" + +[input.vertical.soil.parameters] alpha_h1 = "alpha_h1" -altitude = "wflow_dem" c = "c" cf_soil = "cf_soil" -cfmax = "Cfmax" -e_r = "EoverR" f = "f" +h1 = "h1" +h2 = "h2" +h3_high = "h3_high" +h3_low = "h3_low" +h4 = "h4" infiltcappath = "InfiltCapPath" infiltcapsoil = "InfiltCapSoil" -kext = "Kext" -kv_0 = "KsatVer" -leaf_area_index = "LAI" -m = "M_" +theta_r = "thetaR" +theta_s = "thetaS" maxleakage = "MaxLeakage" pathfrac = "PathFrac" -potential_evaporation = "pet" -precipitation = "precip" rootdistpar = "rootdistpar" -rootingdepth = "RootingDepth" -soilminthickness = "SoilMinThickness" soilthickness = "SoilThickness" -specific_leaf = "Sl" -storage_wood = "Swood" -temperature = "temp" +kv_0 = "KsatVer" + +[input.vertical.snow.parameters] tt = "TT" tti = "TTI" ttm = "TTM" -water_holding_capacity = "WHC" -waterfrac = "WaterFrac" -theta_s = "thetaS" -theta_r = "thetaR" -glacierstore = "wflow_glacierstore" -glacierfrac = "wflow_glacierfrac" -g_cfmax = "G_Cfmax" -g_tt = "G_TT" -g_sifrac = "G_SIfrac" -kc = "crop_factor" -h1 = "h1" -h2 = "h2" -h3_high = "h3_high" -h3_low = "h3_low" -h4 = "h4" +cfmax = "Cfmax" [state.lateral.river] q = "q_river" diff --git a/test/sbm_piave_demand_config.toml b/test/sbm_piave_demand_config.toml index 141ef0105..b1e530b87 100644 --- a/test/sbm_piave_demand_config.toml +++ b/test/sbm_piave_demand_config.toml @@ -17,11 +17,26 @@ path_static = "staticmaps-piave.nc" ldd = "wflow_ldd" river_location = "wflow_river" subcatchment = "wflow_subcatch" -forcing = [ "vertical.precipitation", "vertical.temperature", "vertical.potential_evaporation",] -cyclic = [ "vertical.leaf_area_index", "vertical.domestic.demand_gross", "vertical.domestic.demand_net", "vertical.industry.demand_gross", "vertical.industry.demand_net", "vertical.livestock.demand_gross", "vertical.livestock.demand_net", "vertical.paddy.irrigation_trigger", "vertical.nonpaddy.irrigation_trigger",] gauges = "wflow_gauges" gauges_grdc = "wflow_gauges_grdc" +forcing = [ + "vertical.atmospheric_forcing.precipitation", + "vertical.atmospheric_forcing.temperature", + "vertical.atmospheric_forcing.potential_evaporation", +] +cyclic = [ + "vertical.vegetation_parameter_set.leaf_area_index", + "vertical.demand.domestic.demand.demand_gross", + "vertical.demand.domestic.demand.demand_net", + "vertical.demand.industry.demand.demand_gross", + "vertical.demand.industry.demand.demand_net", + "vertical.demand.livestock.demand.demand_gross", + "vertical.demand.livestock.demand.demand_net", + "vertical.demand.paddy.parameters.irrigation_trigger", + "vertical.demand.nonpaddy.parameters.irrigation_trigger", +] + [model] type = "sbm" masswasting = true @@ -36,62 +51,78 @@ kw_land_tstep = 3600 thicknesslayers = [ 50, 100, 50, 200, 800,] river_routing = "kinematic-wave" -[state.vertical] +[state.vertical.interception.variables] +canopy_storage = "canopystorage" + +[state.vertical.soil.variables] satwaterdepth = "satwaterdepth" -snow = "snow" tsoil = "tsoil" ustorelayerdepth = "ustorelayerdepth" -snowwater = "snowwater" -canopystorage = "canopystorage" -glacierstore = "glacierstore" -[state.vertical.paddy] +[state.vertical.snow.variables] +snow_storage = "snow" +snow_water = "snowwater" + +[state.vertical.glacier.variables] +glacier_store = "glacierstore" + +[state.vertical.demand.paddy.variables] h = "h_paddy" -[input.vertical] +[input.vertical.glacier.parameters] +glacier_frac = "wflow_glacierfrac" +g_cfmax = "G_Cfmax" +g_tt = "G_TT" +g_sifrac = "G_SIfrac" + +[input.vertical.glacier.variables] +glacier_store = "wflow_glacierstore" + +[input.vertical.vegetation_parameter_set] +leaf_area_index = "LAI" +kext = "Kext" +storage_specific_leaf = "Sl" +storage_wood = "Swood" +rootingdepth = "RootingDepth" +kc = "crop_factor" + +[input.vertical.interception.parameters] +e_r = "EoverR" + +[input.vertical.atmospheric_forcing] +potential_evaporation = "pet" +precipitation = "precip" +temperature = "temp" + +[input.vertical.runoff.parameters] +waterfrac = "WaterFrac" + +[input.vertical.soil.parameters] alpha_h1 = "alpha_h1" -altitude = "wflow_dem" c = "c" cf_soil = "cf_soil" -cfmax = "Cfmax" -e_r = "EoverR" f = "f" +h1 = "h1" +h2 = "h2" +h3_high = "h3_high" +h3_low = "h3_low" +h4 = "h4" infiltcappath = "InfiltCapPath" infiltcapsoil = "InfiltCapSoil" -kext = "Kext" -kv_0 = "KsatVer" -leaf_area_index = "LAI" -m = "M_" +theta_r = "thetaR" +theta_s = "thetaS" maxleakage = "MaxLeakage" pathfrac = "PathFrac" -potential_evaporation = "pet" -precipitation = "precip" rootdistpar = "rootdistpar" -rootingdepth = "RootingDepth" -soilminthickness = "SoilMinThickness" soilthickness = "SoilThickness" -specific_leaf = "Sl" -storage_wood = "Swood" -temperature = "temp" +kv_0 = "KsatVer" +kvfrac = "kvfrac" + +[input.vertical.snow.parameters] tt = "TT" tti = "TTI" ttm = "TTM" -water_holding_capacity = "WHC" -waterfrac = "WaterFrac" -theta_s = "thetaS" -theta_r = "thetaR" -glacierstore = "wflow_glacierstore" -glacierfrac = "wflow_glacierfrac" -g_cfmax = "G_Cfmax" -g_tt = "G_TT" -g_sifrac = "G_SIfrac" -kc = "crop_factor" -kvfrac = "kvfrac" -h1 = "h1" -h2 = "h2" -h3_high = "h3_high" -h3_low = "h3_low" -h4 = "h4" +cfmax = "Cfmax" [model.water_demand] domestic = true @@ -113,27 +144,27 @@ q = "q_land" h = "h_land" h_av = "h_av_land" -[input.vertical.allocation] +[input.vertical.allocation.parameters] areas = "allocation_areas" frac_sw_used = "SurfaceWaterFrac" -[input.vertical.domestic] +[input.vertical.demand.domestic.demand] demand_gross = "dom_gross" demand_net = "dom_net" -[input.vertical.industry] +[input.vertical.demand.industry.demand] demand_gross = "ind_gross" demand_net = "ind_net" -[input.vertical.livestock] +[input.vertical.demand.livestock.demand] demand_gross = "lsk_gross" demand_net = "lsk_net" -[input.vertical.paddy] +[input.vertical.demand.paddy.parameters] irrigation_areas = "paddy_irrigation_areas" irrigation_trigger = "irrigation_trigger" -[input.vertical.nonpaddy] +[input.vertical.demand.nonpaddy.parameters] irrigation_areas = "nonpaddy_irrigation_areas" irrigation_trigger = "irrigation_trigger" @@ -157,7 +188,7 @@ path = "output-piave-demand.nc" [output.lateral.river] q_av = "q_river" -[output.vertical] +[output.vertical.soil.variables] zi = "zi" [csv] @@ -177,10 +208,10 @@ parameter = "lateral.river.q_av" coordinate.x = 12.7243 coordinate.y = 45.5851 header = "paddy_h_bycoord" -parameter = "vertical.paddy.h" +parameter = "vertical.demand.paddy.variables.h" [[csv.column]] coordinate.x = 12.7243 coordinate.y = 45.5851 header = "irri_bycoord" -parameter = "vertical.allocation.irri_alloc" \ No newline at end of file +parameter = "vertical.allocation.variables.irri_alloc" \ No newline at end of file diff --git a/test/sbm_simple.toml b/test/sbm_simple.toml index 7916cf114..b6e16d6d1 100644 --- a/test/sbm_simple.toml +++ b/test/sbm_simple.toml @@ -20,44 +20,50 @@ subcatchment = "wflow_subcatch" # specify the internal IDs of the parameters which vary over time # the external name mapping needs to be below together with the other mappings forcing = [ - "vertical.precipitation", - "vertical.temperature", - "vertical.potential_evaporation", + "vertical.atmospheric_forcing.precipitation", + "vertical.atmospheric_forcing.temperature", + "vertical.atmospheric_forcing.potential_evaporation", ] -cyclic = ["vertical.leaf_area_index"] +cyclic = ["vertical.vegetation_parameter_set.leaf_area_index"] -[input.vertical] -altitude = "wflow_dem" +[input.vertical.vegetation_parameter_set] +leaf_area_index = "LAI" +kext = "Kext" +storage_specific_leaf = "Sl" +storage_wood = "Swood" +rootingdepth = "RootingDepth" + +[input.vertical.interception.parameters] +e_r = "EoverR" + +[input.vertical.atmospheric_forcing] +potential_evaporation = "pet" +precipitation = "precip" +temperature = "temp" + +[input.vertical.runoff.parameters] +waterfrac = "WaterFrac" + +[input.vertical.soil.parameters] c = "c" cf_soil = "cf_soil" -cfmax = "Cfmax" -e_r = "EoverR" +f = "f" infiltcappath = "InfiltCapPath" infiltcapsoil = "InfiltCapSoil" -kext = "Kext" -kv_0 = "KsatVer" -leaf_area_index = "LAI" -m = "M" +theta_r = "thetaR" +theta_s = "thetaS" maxleakage = "MaxLeakage" pathfrac = "PathFrac" -potential_evaporation = "pet" -precipitation = "precip" rootdistpar = "rootdistpar" -rootingdepth = "RootingDepth" -soilminthickness = "SoilMinThickness" soilthickness = "SoilThickness" -specific_leaf = "Sl" -storage_wood = "Swood" -temperature = "temp" +kv_0 = "KsatVer" + +[input.vertical.snow.parameters] tt = "TT" tti = "TTI" ttm = "TTM" -w_soil = "wflow_soil" -water_holding_capacity = "WHC" -waterfrac = "WaterFrac" -theta_r = "thetaR" -theta_s = "thetaS" +cfmax = "Cfmax" [input.lateral.river] length = "wflow_riverlength" @@ -97,5 +103,5 @@ parameter = "lateral.river.q" [[csv.column]] header = "recharge" -parameter = "vertical.recharge" +parameter = "vertical.soil.variables.recharge" reducer = "mean" diff --git a/test/sbm_swf_config.toml b/test/sbm_swf_config.toml index 92872f36f..79ec88b3c 100644 --- a/test/sbm_swf_config.toml +++ b/test/sbm_swf_config.toml @@ -14,14 +14,18 @@ dir_output = "data/output" # if listed, the variable must be present in the NetCDF or error # if not listed, the variable can get a default value if it has one -[state.vertical] -canopystorage = "canopystorage" +[state.vertical.interception.variables] +canopy_storage = "canopystorage" + +[state.vertical.soil.variables] satwaterdepth = "satwaterdepth" -snow = "snow" -snowwater = "snowwater" tsoil = "tsoil" ustorelayerdepth = "ustorelayerdepth" +[state.vertical.snow.variables] +snow_storage = "snow" +snow_water = "snowwater" + [state.lateral.river] h = "h_river" h_av = "h_av_river" @@ -52,41 +56,50 @@ subcatchment = "wflow_subcatch" # specify the internal IDs of the parameters which vary over time # the external name mapping needs to be below together with the other mappings forcing = [ - "vertical.precipitation", - "vertical.temperature", - "vertical.potential_evaporation", + "vertical.atmospheric_forcing.precipitation", + "vertical.atmospheric_forcing.temperature", + "vertical.atmospheric_forcing.potential_evaporation", ] -cyclic = ["vertical.leaf_area_index"] +cyclic = ["vertical.vegetation_parameter_set.leaf_area_index"] + +[input.vertical.vegetation_parameter_set] +leaf_area_index = "LAI" +kext = "Kext" +storage_specific_leaf = "Sl" +storage_wood = "Swood" +rootingdepth = "RootingDepth" + +[input.vertical.interception.parameters] +e_r = "EoverR" + +[input.vertical.atmospheric_forcing] +potential_evaporation = "pet" +precipitation = "precip" +temperature = "temp" -[input.vertical] +[input.vertical.runoff.parameters] +waterfrac = "WaterFrac" + +[input.vertical.soil.parameters] c = "c" cf_soil = "cf_soil" -cfmax = "Cfmax" -e_r = "EoverR" f = "f" infiltcappath = "InfiltCapPath" infiltcapsoil = "InfiltCapSoil" -kext = "Kext" -kv_0 = "KsatVer" -leaf_area_index = "LAI" +theta_r = "thetaR" +theta_s = "thetaS" maxleakage = "MaxLeakage" pathfrac = "PathFrac" -potential_evaporation = "pet" -precipitation = "precip" rootdistpar = "rootdistpar" -rootingdepth = "RootingDepth" soilthickness = "SoilThickness" -specific_leaf = "Sl" -storage_wood = "Swood" -temperature = "temp" +kv_0 = "KsatVer" + +[input.vertical.snow.parameters] tt = "TT" tti = "TTI" ttm = "TTM" -water_holding_capacity = "WHC" -waterfrac = "WaterFrac" -theta_r = "thetaR" -theta_s = "thetaS" +cfmax = "Cfmax" [input.lateral.river] length = "wflow_riverlength" @@ -127,14 +140,18 @@ type = "sbm" [output] path = "output_moselle_swf.nc" -[output.vertical] -canopystorage = "canopystorage" +[output.vertical.interception.variables] +canopy_storage = "canopystorage" + +[output.vertical.soil.variables] satwaterdepth = "satwaterdepth" -snow = "snow" -snowwater = "snowwater" tsoil = "tsoil" ustorelayerdepth = "ustorelayerdepth" +[output.vertical.snow.variables] +snow_storage = "snow" +snow_water = "snowwater" + [output.lateral.river] h = "h_river" h_av = "hav_river" diff --git a/test/subdomains.jl b/test/subdomains.jl index 12b5170b3..126e6f900 100644 --- a/test/subdomains.jl +++ b/test/subdomains.jl @@ -19,7 +19,7 @@ subbas_order, indices_subbas, topo_subbas = Wflow.kinwave_set_subdomains( min_sto_land, ) -Wflow.close_files(model, delete_output = false) +Wflow.close_files(model; delete_output = false) if nthreads() == 1 @testset "Nonparallel subdomains kinematic wave (nthreads = 1)" begin diff --git a/test/testing_utils.jl b/test/testing_utils.jl index 06a660767..c7faad0e9 100644 --- a/test/testing_utils.jl +++ b/test/testing_utils.jl @@ -11,14 +11,14 @@ # https://github.com/stevengj/18S096-iap17/blob/master/pset3/pset3-solutions.ipynb # n coefficients of the Taylor series of E₁(z) + log(z), in type T: -function E1_taylor_coefficients(::Type{T}, n::Integer) where {T<:Number} +function E1_taylor_coefficients(::Type{T}, n::Integer) where {T <: Number} n < 0 && throw(ArgumentError("$n ≥ 0 is required")) n == 0 && return T[] n == 1 && return T[-eulergamma] # iteratively compute the terms in the series, starting with k=1 term::T = 1 terms = T[-eulergamma, term] - for k = 2:n + for k in 2:n term = -term * (k - 1) / (k * k) push!(terms, term) end @@ -40,10 +40,10 @@ end # for numeric-literal coefficients: simplify to a ratio of two polynomials: # return (p,q): the polynomials p(x) / q(x) corresponding to E1_cf(x, a...), # but without the exp(-x) term -function E1_cfpoly(n::Integer, ::Type{T} = BigInt) where {T<:Real} +function E1_cfpoly(n::Integer, ::Type{T} = BigInt) where {T <: Real} q = Polynomials.Polynomial(T[1]) p = x = Polynomials.Polynomial(T[0, 1]) - for i = n:-1:1 + for i in n:-1:1 p, q = x * p + (1 + i) * q, p # from cf = x + (1+i)/cf = x + (1+i)*q/p p, q = p + i * q, p # from cf = 1 + i/cf = 1 + i*q/p end @@ -65,7 +65,7 @@ macro E1_cf64(z, n::Integer) end # exponential integral function E₁(z) -function expint(z::Union{Float64,Complex{Float64}}) +function expint(z::Union{Float64, Complex{Float64}}) xSq = real(z)^2 ySq = imag(z)^2 if real(z) > 0 && xSq + 0.233 * ySq ≥ 7.84 # use cf expansion, ≤ 30 terms @@ -81,15 +81,24 @@ function expint(z::Union{Float64,Complex{Float64}}) return @E1_cf64 z 30 else # use Taylor expansion, ≤ 37 terms rSq = xSq + ySq - return rSq ≤ 0.36 ? - ( - rSq ≤ 2.8e-3 ? (rSq ≤ 2e-7 ? @E1_taylor64(z, 4) : @E1_taylor64(z, 8)) : - @E1_taylor64(z, 15) - ) : @E1_taylor64(z, 37) + return if rSq ≤ 0.36 + ( + if rSq ≤ 2.8e-3 + (rSq ≤ 2e-7 ? @E1_taylor64(z, 4) : @E1_taylor64(z, 8)) + else + @E1_taylor64(z, 15) + end + ) + else + @E1_taylor64(z, 37) + end end end -expint(z::Union{T,Complex{T},Rational{T},Complex{Rational{T}}}) where {T<:Integer} = - expint(float(z)) +function expint( + z::Union{T, Complex{T}, Rational{T}, Complex{Rational{T}}}, +) where {T <: Integer} + return expint(float(z)) +end ###################################################################### # exponential integral Eₙ(z) @@ -102,7 +111,7 @@ function expint(n::Integer, z) zinv = inv(z) exp_minus_z = exp(-z) Ei = zinv * exp_minus_z - for i = 1:-n + for i in 1:(-n) Ei = zinv * (exp_minus_z + i * Ei) end return Ei @@ -111,7 +120,7 @@ function expint(n::Integer, z) exp_minus_z = exp(-z) Ei = expint(z) Ei *= !isinf(Ei) - for i = 2:n + for i in 2:n Ei = (exp_minus_z - z * Ei) / (i - 1) end return Ei @@ -130,10 +139,10 @@ function csv_first_row(path) names = Tuple(Symbol.(split(header, ','))) ncol = length(names) # this assumes the first column is a time, the rest a float - types = Tuple{DateTime,fill(Float64, ncol - 1)...} + types = Tuple{DateTime, fill(Float64, ncol - 1)...} parts = split(dataline, ',') values = parse.(Float64, parts[2:end]) - row = NamedTuple{names,types}((DateTime(parts[1]), values...)) + row = NamedTuple{names, types}((DateTime(parts[1]), values...)) return row end diff --git a/test/vertical_process.jl b/test/vertical_process.jl index 9ce59fb45..432b2844e 100644 --- a/test/vertical_process.jl +++ b/test/vertical_process.jl @@ -9,13 +9,13 @@ using Dates @test all( isapprox.( Wflow.rainfall_interception_modrut(8.6, 3.8, 1.5, 0.45, 2.8), - (4.343, 3.87, 0.387, 0.0, 3.8, 2.043), + (3.87, 3.8, 0.387, 2.043), ), ) @test Wflow.head_brooks_corey(0.25, 0.6, 0.15, 10.5, -10.0) ≈ -90.6299820833844 - @test Wflow.feddes_h3(-300.0, -600.0, 3.5, Second(86400)) ≈ -412.5 - @test Wflow.feddes_h3(-300.0, -600.0, 0.5, Second(86400)) == -600.0 - @test Wflow.feddes_h3(-300.0, -600.0, 6.0, Second(86400)) == -300.0 + @test Wflow.feddes_h3(-300.0, -600.0, 3.5, 86400.0) ≈ -412.5 + @test Wflow.feddes_h3(-300.0, -600.0, 0.5, 86400.0) == -600.0 + @test Wflow.feddes_h3(-300.0, -600.0, 6.0, 86400.0) == -300.0 @test Wflow.rwu_reduction_feddes(0.0, -10.0, -100.0, -300.0, -15000.0, 0.0) == 0.0 @test Wflow.rwu_reduction_feddes(0.0, -10.0, -100.0, -300.0, -15000.0, 1.0) == 1.0 @test Wflow.rwu_reduction_feddes(-90.0, -10.0, -100.0, -412.5, -15000.0, 0.0) ≈ @@ -24,12 +24,7 @@ using Dates @test Wflow.rwu_reduction_feddes(-12000.0, -10.0, -100.0, -412.5, -15000.0, 0.0) ≈ 0.20565552699228792 @test Wflow.rwu_reduction_feddes(-16000.0, -10.0, -100.0, -412.5, -15000.0, 0.0) == 0.0 - @test all( - isapprox.( - Wflow.infiltration(27.5, 0.2, 0.038, 8.9, 50.0, 5.0, 23.5, false, false), - (23.5, 19.14814814814815, 4.351851851851852, 22.0, 5.5, 0.5, 1.0), - ), - ) + @test all(isapprox.(Wflow.infiltration(27.5, 0.2, 50.0, 5.0, 23.5, 1.0), (23.5, 0.5))) @test all( isapprox.( Wflow.unsatzone_flow_layer(43.5, 256.0, 135.0, 12.6), @@ -44,14 +39,20 @@ using Dates ) @test all( isapprox.( - Wflow.snowpack_hbv(201.5, 15.0, 30.1, 0.54, 2.0, 0.0, 0.0, 2.5, 0.10), - (207.073, 20.707300000000004, 1.35, 18.819699999999997, 6.923), + Wflow.precipitation_hbv(30.1, 0.54, 2.0, 0.0), + (6.923, 23.177000000000003), + ), + ) + @test all( + isapprox.( + Wflow.snowpack_hbv(201.5, 15.0, 6.923, 23.177, 0.54, 0.0, 2.5, 0.10), + (207.073, 20.707300000000004, 227.7803, 1.35, 18.819699999999997), ), ) @test Wflow.scurve(2.0, 0.0, 3.0, 2.5) ≈ 0.3325863502664285 @test all( isapprox.( - Wflow.glacier_hbv(0.35, 500.0, 9.5, 5.0, 0.0, 3.4, 0.2, Second(Day(1))), + Wflow.glacier_hbv(0.35, 500.0, 9.5, 5.0, 0.0, 3.4, 0.2, 8.0), (8.835, 1.9, 484.9, 17.0), ), ) From ab878734744baa73b90edae1f372f7c8cf56ee91 Mon Sep 17 00:00:00 2001 From: JoostBuitink <44062204+JoostBuitink@users.noreply.github.com> Date: Tue, 5 Nov 2024 08:39:42 +0100 Subject: [PATCH 26/27] remove hbv_config from precompile --- build/create_binaries/precompile.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/build/create_binaries/precompile.jl b/build/create_binaries/precompile.jl index 78ea170d1..984017d8c 100644 --- a/build/create_binaries/precompile.jl +++ b/build/create_binaries/precompile.jl @@ -8,5 +8,4 @@ testdir = abspath(dirname(pathof(Wflow)), "..", "test") Wflow.run(joinpath(testdir, "sbm_config.toml")) Wflow.run(joinpath(testdir, "sbm_gwf_config.toml")) -Wflow.run(joinpath(testdir, "hbv_config.toml")) Wflow.run(joinpath(testdir, "sediment_config.toml")) From 2a63e9572ecb2fef4719d5caa661bd4bd48bfdd9 Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Wed, 6 Nov 2024 08:44:04 +0100 Subject: [PATCH 27/27] Update changelog (#493) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update changelog As internal variable names with non-ASCII characters have been replaced by ASCII equivalents, the TOML keys `kv₀`, `θᵣ` and `θₛ` are not supported anymore. This has been added to changelog. * Remove double quotes TOML keys in docs --- docs/src/changelog.md | 5 ++++- docs/src/user_guide/step2_settings_file.md | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/src/changelog.md b/docs/src/changelog.md index 72e5ae536..7c7f4baa2 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -102,7 +102,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - For improved code readability it is now discouraged to use non-ASCII characters for the names of variables, structs, functions and macros. Using the non-ASCII character for built-in operators is still allowed. This change in naming convention is now in effect and - all invalid uses of non-ASCII characters have been replaced by ASCII equivalents. + all invalid uses of non-ASCII characters have been replaced by ASCII equivalents. This + change also impacts the TOML keys `kv₀`, `θᵣ` and `θₛ`. These keys have been replaced with + the ASCII versions `kv_0`, `theta_r` and `theta_s` in v0.4.0 and the old keys are not + supported anymore. - Docs: 1) improved description of different model configurations in model-setup.md, also in relation to hydromt\_wflow in docs, 2) citing info related to wflow\_sbm publication in Geosci. Model Dev. (from in review to published). diff --git a/docs/src/user_guide/step2_settings_file.md b/docs/src/user_guide/step2_settings_file.md index f2e5095ef..649e88486 100644 --- a/docs/src/user_guide/step2_settings_file.md +++ b/docs/src/user_guide/step2_settings_file.md @@ -152,7 +152,7 @@ e_r = "EoverR" infiltcappath = "InfiltCapPath" infiltcapsoil = "InfiltCapSoil" kext = "Kext" -"kv_0" = "KsatVer" +kv_0 = "KsatVer" leaf_area_index = "LAI" # Cyclic variable m = "M" maxleakage = "MaxLeakage" @@ -172,8 +172,8 @@ ttm = "TTM" w_soil = "wflow_soil" water_holding_capacity = "WHC" waterfrac = "WaterFrac" -"theta_r" = "thetaR" -"theta_s" = "thetaS" +theta_r = "thetaR" +theta_s = "thetaS" [input.lateral.river] length = "wflow_riverlength"