From de91ed97d7ea1becafd5397d1566731f441a311e Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Mon, 1 Jul 2024 10:29:36 +0200 Subject: [PATCH 01/42] 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> --- build/create_binaries/download_test_data.jl | 7 +- docs/make.jl | 2 - docs/src/changelog.md | 9 + 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 | 158 ---- 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 | 2 +- docs/src/user_guide/model-setup.md | 11 - docs/src/user_guide/sample_data.md | 38 - docs/src/user_guide/step1_requirements.md | 7 +- docs/src/user_guide/step2_settings_file.md | 26 +- src/Wflow.jl | 10 - src/bmi.jl | 4 - src/flextopo.jl | 882 -------------------- src/flextopo_model.jl | 740 ---------------- src/flow.jl | 37 +- src/hbv.jl | 239 ------ src/hbv_model.jl | 449 ---------- src/io.jl | 25 +- src/states.jl | 20 - src/utils.jl | 7 +- test/flextopo_config.toml | 523 ------------ test/hbv_config.toml | 137 --- test/io.jl | 8 - test/run_flextopo.jl | 116 --- test/run_hbv.jl | 66 -- test/runtests.jl | 7 - 36 files changed, 50 insertions(+), 4185 deletions(-) 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 delete mode 100644 src/flextopo.jl delete mode 100644 src/flextopo_model.jl delete mode 100644 src/hbv.jl delete mode 100644 src/hbv_model.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/build/create_binaries/download_test_data.jl b/build/create_binaries/download_test_data.jl index d61310f3b..8ad77e30f 100644 --- a/build/create_binaries/download_test_data.jl +++ b/build/create_binaries/download_test_data.jl @@ -20,12 +20,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 = @@ -43,7 +39,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/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 a0bd2349c..6133e9600 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -5,6 +5,15 @@ 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). +## v1.0.0 + +### Fixed + +### Changed +- Removed vertical concepts `HBV` and `FLEXTopo`. + +### Added + ## [unreleased] ### 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 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 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 f2872153c..6baf6a2ec 100644 --- a/docs/src/model_docs/model_configurations.md +++ b/docs/src/model_docs/model_configurations.md @@ -123,128 +123,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 @@ -284,8 +162,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. @@ -323,7 +200,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 02477283c..1edea2f83 100644 --- a/docs/src/model_docs/params_vertical.md +++ b/docs/src/model_docs/params_vertical.md @@ -135,164 +135,6 @@ profile `kv` is used and `z_layered` is required as input. | `waterlevel_river` | water level river | mm | - | | `total_storage` | total water storage (excluding floodplains, lakes and reservoirs) | mm | - | - -## [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 ebda6144b..cbb8f1940 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 in three parameters. `glacierfrac` is a parameter that gives the fraction of each grid cell covered by a glacier 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 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 5575facba..000000000 --- a/src/flextopo_model.jl +++ /dev/null @@ -1,740 +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, - xl = xl, - yl = 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} - @unpack lateral, vertical, network, clock, 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} - @unpack 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 31fa480d7..77b058bf0 100644 --- a/src/flow.jl +++ b/src/flow.jl @@ -372,7 +372,8 @@ function stable_timestep(sf::S) where {S<:SurfaceFlow} courant = zeros(n) for v = 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))) + sf.cel[v] = + 1.0 / (sf.alpha[v] * sf.beta * pow(sf.q[v], (sf.beta - 1.0))) courant[v] = (sf.dt / sf.dl[v]) * sf.cel[v] end end @@ -1153,8 +1154,8 @@ 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) + sw.rivercells[i] == 0 ? + sw.alpha * min(sw.xl[i], sw.yl[i]) / sqrt(sw.g * sw.h[i]) : T(Inf) dt_min = min(dt, dt_min) end dt_min = isinf(dt_min) ? T(10.0) : dt_min @@ -1604,12 +1605,12 @@ function initialize_floodplain_1d( end """ - set_river_inwater(model::Model{N,L,V,R,W,T}, ssf_toriver) where {N,L,V<:SBM,R,W,T} + set_river_inwater(model, ssf_toriver) -Set `inwater` of the lateral river component for a `Model` with vertical `SBM` concept. -`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<:SBM,R,W,T} +function set_river_inwater(model, ssf_toriver) @unpack lateral, vertical, network = model inds = network.index_river @@ -1628,17 +1629,6 @@ function set_river_inwater(model::Model{N,L,V,R,W,T}, ssf_toriver) where {N,L,V< ) 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) - @unpack 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} @@ -1671,17 +1661,6 @@ function set_land_inwater(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmMode lateral.land.dt end -""" - set_land_inwater(model) - -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.land.inwater .= - (vertical.runoff .* network.land.xl .* network.land.yl .* 0.001) ./ lateral.land.dt -end - # Computation of inflow from the lateral components `land` and `subsurface` to water bodies # depends on the routing scheme (see different `get_inflow_waterbody` below). For the river # kinematic wave, the variables `to_river` can be excluded, because this part is added to 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 aab5cbbd7..000000000 --- a/src/hbv_model.jl +++ /dev/null @@ -1,449 +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, - xl = xl, - yl = 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} - @unpack lateral, vertical, network, clock, config = model - - inds_riv = network.index_river - - # 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} - @unpack 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 - @unpack 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/io.jl b/src/io.jl index 16d89baed..60c8c0dae 100644 --- a/src/io.jl +++ b/src/io.jl @@ -464,12 +464,6 @@ function set_extradim_netcdf( 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) return nothing @@ -1437,7 +1431,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 @@ -1522,8 +1516,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 +1559,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 @@ -1628,29 +1614,24 @@ function read_y_axis(ds::CFDataset)::Vector{Float64} 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 @unpack vertical = model if haskey(var, "layer") inds = collect(1:vertical.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) 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/states.jl b/src/states.jl index 9af01cc0e..2c5eb4d70 100644 --- a/src/states.jl +++ b/src/states.jl @@ -29,26 +29,6 @@ function get_vertical_states(model_type::AbstractString; snow = false, glacier = else vertical_states = (:satwaterdepth, :ustorelayerdepth, :canopystorage) 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 = () else diff --git a/src/utils.jl b/src/utils.jl index 57563b7b6..c91442e97 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -154,8 +154,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 @@ -249,8 +247,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 @@ -810,7 +806,8 @@ end 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] + ssf.ssf[i] = + ssf.kh[i] * (ssf.soilthickness[i] - ssf.zi[i]) * ssf.slope[i] * ssf.dw[i] kh_max = 0.0 for j = 1:sbm.nlayers[i] if j <= sbm.nlayers_kv[i] 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/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/io.jl b/test/io.jl index f692efed3..73d681ee6 100644 --- a/test/io.jl +++ b/test/io.jl @@ -47,14 +47,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 diff --git a/test/run_flextopo.jl b/test/run_flextopo.jl deleted file mode 100644 index cfb4bbffb..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) -@unpack 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) -@unpack 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 12d6eb169..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) -@unpack 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/runtests.jl b/test/runtests.jl index 18771d1de..440c640a3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -38,11 +38,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 = @@ -60,7 +56,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 = @@ -85,7 +80,6 @@ with_logger(NullLogger()) do include("vertical_process.jl") include("reservoir_lake.jl") include("run_sbm.jl") - include("run_hbv.jl") include("run_sbm_gwf.jl") include("run.jl") include("groundwater.jl") @@ -93,7 +87,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) end From 5107824e07944339839e0974a2112dad7dca2e28 Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Tue, 2 Jul 2024 10:03:37 +0200 Subject: [PATCH 02/42] 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 --- .github/workflows/CI.yml | 1 + .github/workflows/CIWflowServer.yml | 1 + docs/src/changelog.md | 3 + src/Wflow.jl | 4 - src/bmi.jl | 155 +++++++++++-- src/flow.jl | 290 ++++++++++++------------- src/groundwater/aquifer.jl | 9 +- src/groundwater/boundary_conditions.jl | 8 +- src/reservoir_lake.jl | 9 +- src/sbm.jl | 53 ++--- src/sediment.jl | 16 +- test/reservoir_lake.jl | 100 +++++---- test/run_sbm.jl | 77 ++++++- test/run_sbm_gwf.jl | 17 ++ test/run_sediment.jl | 16 ++ 15 files changed, 492 insertions(+), 267 deletions(-) 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/docs/src/changelog.md b/docs/src/changelog.md index 6133e9600..9cc3d318e 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Removed vertical concepts `HBV` and `FLEXTopo`. +- Removed metadata macros `exchange`, `grid_type` and `grid_location`. The macro `grid_type` + is not required because this functionality is already part of `BMI`. The macros `exchange` + and `grid_location` are replaced by functions resulting in cleaner code. ### Added diff --git a/src/Wflow.jl b/src/Wflow.jl index 199d2d0a1..651df3e21 100644 --- a/src/Wflow.jl +++ b/src/Wflow.jl @@ -23,10 +23,6 @@ using LoopVectorization using IfElse @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 const BMI = BasicModelInterface const Float = Float64 diff --git a/src/bmi.jl b/src/bmi.jl index 040a474b0..2ea9f0395 100644 --- a/src/bmi.jl +++ b/src/bmi.jl @@ -116,22 +116,23 @@ 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, "]")) + if exchange(param(model, c), name) == 1 + 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, "]")) + end + else + push!(var_names, var) end - elseif ndims(model_var) > 1 - for i = 1:length(first(model_var)) - push!(var_names, string(var, "[", i, "]")) - end - else - push!(var_names, var) end end end @@ -152,7 +153,6 @@ 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])) return if :reservoir in key 0 @@ -199,8 +199,9 @@ 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) + type = param(model, key[1:end-1]) + if exchange(type, key[end]) == 1 + return grid_location(type, key[end]) else error("$name not listed as variable for BMI exchange") end @@ -409,3 +410,125 @@ end function get_start_unix_time(model::Model) datetime2unix(DateTime(model.config.starttime)) end + +# Exchange and grid location functions. +exchange(::Nothing, var) = 0 +grid_location(::Nothing, var) = "none" + +function exchange(::SurfaceFlow, var) + if var in (:dt, :beta, :its, :alpha_pow, :reservoir, :lake, :kinwave_it) + 0 + else + 1 + end +end + +function grid_location(::SurfaceFlow, var) + if var in (:dt, :its, :kinwave_it) + "none" + else + "node" + end +end + +exchange(::Union{LateralSSF,GroundwaterExchange}, var) = var == :dt ? 0 : 1 +grid_location(::Union{LateralSSF,GroundwaterExchange}, var) = var == :dt ? "none" : "node" + +function exchange(::ShallowWaterRiver, var) + if var in ( + :dt, + :n, + :ne, + :g, + :alpha, + :h_thresh, + :froude_limit, + :reservoir_index, + :lake_index, + :reservoir, + :lake, + :floodplain, + ) + 0 + else + 1 + end +end + +function grid_location(::ShallowWaterRiver, var) + if var in (:n, :ne, :dt, :froude_limit) + "none" + elseif var in ( + :active_e, + :q, + :q0, + :q_av, + :mannings_n_sq, + :zs_max, + :hf, + :dl_at_link, + :width_at_link, + :a, + :r, + ) + "edge" + else + "node" + end +end + +function exchange(::ShallowWaterLand, var) + if var in (:n, :g, :theta, :alpha, :h_thresh, :dt, :froude_limit) + 0 + else + 1 + end +end + +function grid_location(::ShallowWaterLand, var) + if var in (:n, :dt, :froude_limit) + "none" + elseif var in (:xwidth, :ywidth, :qy0, :qx0, :qx, :qy, :zx_max, :zy_max, :mannings_n_sq) + "edge" + else + "node" + end +end + +exchange(::FloodPlainProfile, var) = 1 +grid_location(::FloodPlainProfile, var) = "node" + +exchange(::FloodPlain, var) = var == :profile ? 0 : 1 +function grid_location(::FloodPlain, var) + if var in (:mannings_n_sq, :a, :r, :hf, :zb_max, :q0, :q, :q_av, :hf_index) + "edge" + else + "node" + end + +end + +exchange(::SimpleReservoir, var) = var == :dt ? 0 : 1 +grid_location(::SimpleReservoir, var) = var == :dt ? "none" : "node" + +exchange(::Lake, var) = var == :dt ? 0 : 1 +grid_location(::Lake, var) = var == :dt ? "none" : "node" + +exchange(::SBM, var) = var in (:n, :dt, :maxlayers) ? 0 : 1 +grid_location(::SBM, var) = var in (:n, :dt, :maxlayers) ? "none" : "node" + +exchange(::Union{LandSediment,OverlandFlowSediment}, var) = var == :n ? 0 : 1 +grid_location(::Union{LandSediment,OverlandFlowSediment}, var) = var == :n ? "none" : "node" + +exchange(::RiverSediment, var) = var in (:n, :dt) ? 0 : 1 +grid_location(::RiverSediment, var) = var in (:n, :dt) ? "none" : "node" + + +exchange(::Aquifer, var) = 1 +grid_location(::Aquifer, var) = "node" + +exchange(::ConstantHead, var) = 0 +grid_location(::ConstantHead, var) = "node" + +exchange(::AquiferBoundaryCondition, var) = 1 +grid_location(::AquiferBoundaryCondition, var) = "node" diff --git a/src/flow.jl b/src/flow.jl index 77b058bf0..00e3475be 100644 --- a/src/flow.jl +++ b/src/flow.jl @@ -1,35 +1,34 @@ abstract type SurfaceFlow end -@get_units @exchange @grid_type @grid_location @with_kw struct SurfaceFlowRiver{T,R,L} <: - 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⁻¹] - 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 - kinwave_it::Bool | "-" | 0 | "none" | "none" # Boolean for iterations kinematic wave +@get_units @with_kw struct SurfaceFlowRiver{T,R,L} <: 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⁻¹] + 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" # 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 + kinwave_it::Bool | "-" # Boolean for iterations kinematic wave # TODO unclear why this causes a MethodError # function SurfaceFlow{T,R,L}(args...) where {T,R,L} @@ -38,29 +37,28 @@ abstract type SurfaceFlow end # end 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 @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 | "s" # 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) @@ -389,16 +387,16 @@ 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⁻¹] +@get_units @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⁻¹] 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 | "d" # 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) @@ -480,57 +478,57 @@ function update(ssf::LateralSSF, network, frac_toriver, ksat_profile) end 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 @with_kw struct GroundwaterExchange{T} + dt::T | "d" # 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} - 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⁻¹] - 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 +@get_units @with_kw struct ShallowWaterRiver{T,R,L,F} + n::Int | "-" # number of cells + ne::Int | "-" # number of edges/links + active_n::Vector{Int} | "-" # active nodes + active_e::Vector{Int} | "-" # active edges/links + g::T | "m s-2" # acceleration due to gravity + alpha::T | "-" # stability coefficient (Bates et al., 2010) + h_thresh::T | "m" # depth threshold for calculating flow + dt::T | "s" # model time step [s] + q::Vector{T} | "m3 s-1" # river discharge (subgrid channel) + q0::Vector{T} | "m3 s-1" # river discharge (subgrid channel) at previous time step + q_av::Vector{T} | "m3 s-1" # 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" # 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" # 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" # 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" # river length at edge/link + width::Vector{T} | "m" # river width + width_at_link::Vector{T} | "m" # river width at edge/link + a::Vector{T} | "m2" # flow area at edge/link + r::Vector{T} | "m" # 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⁻¹] + 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 end function initialize_shallowwater_river( @@ -983,33 +981,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 @with_kw struct ShallowWaterLand{T} + n::Int | "-" # number of cells + xl::Vector{T} | "m" # cell length x direction + yl::Vector{T} | "m" # cell length y direction + xwidth::Vector{T} | "m" # effective flow width x direction (floodplain) + ywidth::Vector{T} | "m" # effective flow width y direction (floodplain) + g::T | "m2 s-1" # acceleration due to gravity + theta::T | "-" # weighting factor (de Almeida et al., 2012) + alpha::T | "-" # stability coefficient (de Almeida et al., 2012) + h_thresh::T | "m" # depth threshold for calculating flow + dt::T | "s" # model time step [s] + qy0::Vector{T} | "m3 s-1" # flow in y direction at previous time step + qx0::Vector{T} | "m3 s-1" # flow in x direction at previous time step + qx::Vector{T} | "m3 s-1" # flow in x direction + qy::Vector{T} | "m3 s-1" # flow in y direction + zx_max::Vector{T} | "m" # maximum cell elevation (x direction) + zy_max::Vector{T} | "m" # maximum cell elevation (y direction) + mannings_n_sq::Vector{T} | "(s m-1/3)2" # 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( @@ -1366,30 +1364,30 @@ 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 +@get_units @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 @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" # 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" # flow area + r::Vector{T} | "m" # hydraulic radius + hf::Vector{T} | "m" # water depth at edge/link + zb_max::Vector{T} | "m" # maximum bankfull elevation (edge/link) + q0::Vector{T} | "m3 s-1" # discharge at previous time step + q::Vector{T} | "m3 s-1" # discharge + q_av::Vector{T} | "m" # average river discharge + hf_index::Vector{Int} | "-" # index with `hf` above depth threshold end "Determine the initial floodplain volume" diff --git a/src/groundwater/aquifer.jl b/src/groundwater/aquifer.jl index d650bf799..34a59439c 100644 --- a/src/groundwater/aquifer.jl +++ b/src/groundwater/aquifer.jl @@ -86,7 +86,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 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] @@ -109,7 +109,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 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] @@ -122,7 +122,6 @@ 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 @@ -306,13 +305,11 @@ function flux!(Q, aquifer, connectivity, conductivity_profile) return Q end - -@get_units @exchange @grid_type @grid_location struct ConstantHead{T} +@get_units struct ConstantHead{T} head::Vector{T} | "m" index::Vector{Int} | "-" end - """ stable_timestep(aquifer) diff --git a/src/groundwater/boundary_conditions.jl b/src/groundwater/boundary_conditions.jl index 06a04897e..39de1226f 100644 --- a/src/groundwater/boundary_conditions.jl +++ b/src/groundwater/boundary_conditions.jl @@ -13,7 +13,7 @@ end check_flux(flux, aquifer::ConfinedAquifer, index::Int) = flux -@get_units @exchange @grid_type @grid_location struct River{T} <: AquiferBoundaryCondition +@get_units struct River{T} <: AquiferBoundaryCondition stage::Vector{T} | "m" infiltration_conductance::Vector{T} | "m2 d-1" exfiltration_conductance::Vector{T} | "m2 d-1" @@ -41,8 +41,7 @@ function flux!(Q, river::River, aquifer) end -@get_units @exchange @grid_type @grid_location struct Drainage{T} <: - AquiferBoundaryCondition +@get_units struct Drainage{T} <: AquiferBoundaryCondition elevation::Vector{T} | "m" conductance::Vector{T} | "m2 d-1" flux::Vector{T} | "m3 d-1" @@ -80,8 +79,7 @@ function flux!(Q, headboundary::HeadBoundary, aquifer) end -@get_units @exchange @grid_type @grid_location struct Recharge{T} <: - AquiferBoundaryCondition +@get_units struct Recharge{T} <: AquiferBoundaryCondition rate::Vector{T} | "m d-1" flux::Vector{T} | "m3 d-1" index::Vector{Int} | "-" diff --git a/src/reservoir_lake.jl b/src/reservoir_lake.jl index 0b6acf768..b0ba2611c 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 @with_kw struct SimpleReservoir{T} + dt::T | "s" # 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 @@ -204,8 +203,8 @@ function update(res::SimpleReservoir, i, inflow, timestepsecs) return res 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 @with_kw struct Lake{T} + dt::T | "s" # 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³] diff --git a/src/sbm.jl b/src/sbm.jl index 352ab5c08..7e2676a16 100644 --- a/src/sbm.jl +++ b/src/sbm.jl @@ -1,10 +1,10 @@ -@get_units @exchange @grid_type @grid_location @with_kw struct SBM{T,N,M} +@get_units @with_kw struct SBM{T,N,M} # Model time step [s] - dt::T | "s" | 0 | "none" | "none" + dt::T | "s" # Maximum number of soil layers - maxlayers::Int | "-" | 0 | "none" | "none" + maxlayers::Int | "-" # number of cells - n::Int | "-" | 0 | "none" | "none" + n::Int | "-" # Number of soil layers nlayers::Vector{Int} | "-" # Number of unsaturated soil layers @@ -213,7 +213,6 @@ end end - 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 @@ -348,31 +347,13 @@ function initialize_sbm(nc, config, riverfrac, inds) 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, - ) + 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) + 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) soilthickness = ncread( @@ -1002,8 +983,10 @@ function update_until_recharge(sbm::SBM, config) 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)) + 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 @@ -1097,7 +1080,10 @@ function update_after_subsurfaceflow(sbm::SBM, zi, exfiltsatwater) 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], + ( + 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 @@ -1114,7 +1100,8 @@ function update_after_subsurfaceflow(sbm::SBM, zi, exfiltsatwater) usld[k] end - rootstore_sat = max(0.0, sbm.rootingdepth[i] - zi[i]) * (sbm.theta_s[i] - sbm.theta_r[i]) + 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 diff --git a/src/sediment.jl b/src/sediment.jl index dd708a32a..4d6edd153 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 @with_kw struct LandSediment{T} # number of cells - n::Int | "-" | 0 | "none" | "none" + n::Int | "-" ### Soil erosion part ### # length of cells in y direction [m] yl::Vector{T} | "m" @@ -591,9 +591,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 @with_kw struct OverlandFlowSediment{T} # number of cells - n::Int | "-" | 0 | "none" | "none" + n::Int | "-" # Filter with river cells rivcell::Vector{T} | "-" # Total eroded soil [ton Δt⁻¹] @@ -634,7 +634,6 @@ end end end - function partial_update!(inland, rivcell, eroded) no_erosion = zero(eltype(eroded)) for i in eachindex(inland) @@ -678,11 +677,11 @@ function update(ols::OverlandFlowSediment, network, config) end ### River transport and processes ### -@get_units @exchange @grid_type @grid_location @with_kw struct RiverSediment{T} +@get_units @with_kw struct RiverSediment{T} # number of cells - n::Int | "-" | 0 | "none" | "none" + n::Int | "-" # 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 +781,6 @@ end # end end - function initialize_riversed(nc, config, riverwidth, riverlength, inds_riv) # Initialize river parameters nriv = length(inds_riv) diff --git a/test/reservoir_lake.jl b/test/reservoir_lake.jl index bc2446f92..51201e337 100644 --- a/test/reservoir_lake.jl +++ b/test/reservoir_lake.jl @@ -1,23 +1,22 @@ -@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], - ) - +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 @@ -29,29 +28,37 @@ @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], - ) +@testset "Exchange and grid location reservoir" begin + @test Wflow.exchange(res, :dt) == 0 + @test Wflow.exchange(res, :volume) == 1 + @test Wflow.exchange(res, :outflow) == 1 + @test Wflow.grid_location(res, :dt) == "none" + @test Wflow.grid_location(res, :volume) == "node" + @test Wflow.grid_location(res, :outflow) == "node" +end +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 lake.outflow[1] ≈ 85.14292808113598 @test lake.totaloutflow[1] ≈ 7.356348986210149e6 @@ -62,6 +69,15 @@ end @test lake.actevap[1] ≈ 3.2 end +@testset "Exchange and grid location lake" begin + @test Wflow.exchange(lake, :dt) == 0 + @test Wflow.exchange(lake, :storage) == 1 + @test Wflow.exchange(lake, :outflow) == 1 + @test Wflow.grid_location(lake, :dt) == "none" + @test Wflow.grid_location(lake, :storage) == "node" + @test Wflow.grid_location(lake, :outflow) == "node" +end + datadir = joinpath(@__DIR__, "data") sh = [ Wflow.read_sh_csv(joinpath(datadir, "input", "lake_sh_1.csv")), diff --git a/test/run_sbm.jl b/test/run_sbm.jl index 62fdfba99..76db24547 100644 --- a/test/run_sbm.jl +++ b/test/run_sbm.jl @@ -138,6 +138,42 @@ end @test res.evaporation[1] ≈ 0.5400000810623169f0 end +@testset "Exchange and grid location SBM" begin + sbm = model.vertical + @test Wflow.exchange(sbm, :n) == 0 + @test Wflow.exchange(sbm, :rootingdepth) == 1 + @test Wflow.grid_location(sbm, :dt) == "none" + @test Wflow.grid_location(sbm, :rootingdepth) == "node" + @test Wflow.grid_location(sbm, :runoff) == "node" +end + +@testset "Exchange and grid location subsurface flow" begin + ssf = model.lateral.subsurface + @test Wflow.exchange(ssf, :dt) == 0 + @test Wflow.exchange(ssf, :ssf) == 1 + @test Wflow.exchange(ssf, :slope) == 1 + @test Wflow.grid_location(ssf, :dt) == "none" + @test Wflow.grid_location(ssf, :ssf) == "node" + @test Wflow.grid_location(ssf, :slope) == "node" +end + +@testset "Exchange and grid location kinematic wave" begin + land = model.lateral.land + @test Wflow.exchange(land, :dt) == 0 + @test Wflow.exchange(land, :q_av) == 1 + @test Wflow.exchange(land, :inwater) == 1 + @test Wflow.grid_location(land, :dt) == "none" + @test Wflow.grid_location(land, :q_av) == "node" + @test Wflow.grid_location(land, :inwater) == "node" + river = model.lateral.river + @test Wflow.exchange(river, :dt) == 0 + @test Wflow.exchange(river, :q_av) == 1 + @test Wflow.exchange(river, :inwater) == 1 + @test Wflow.grid_location(river, :dt) == "none" + @test Wflow.grid_location(river, :q_av) == "node" + @test Wflow.grid_location(river, :inwater) == "node" +end + # set these variables for comparison in "changed dynamic parameters" precip = copy(model.vertical.precipitation) evap = copy(model.vertical.potential_evaporation) @@ -287,6 +323,19 @@ model = Wflow.run_timestep(model) @test h[[26, 35, 631]] ≈ [0.07361854999908582f0, 0.009155393111676267f0, 0.0007258741013439351f0] end + +@testset "Exchange and grid location local inertial overland flow" begin + land = model.lateral.land + @test Wflow.exchange(land, :dt) == 0 + @test Wflow.exchange(land, :g) == 0 + @test Wflow.exchange(land, :h) == 1 + @test Wflow.exchange(land, :qx) == 1 + @test Wflow.grid_location(land, :dt) == "none" + @test Wflow.grid_location(land, :g) == "node" + @test Wflow.grid_location(land, :h) == "node" + @test Wflow.grid_location(land, :qx) == "edge" +end + Wflow.close_files(model, delete_output = false) # test local-inertial option for river flow including 1D floodplain schematization @@ -410,7 +459,33 @@ model = Wflow.run_timestep(model) @test h[1622] ≈ 0.001987887580593841f0 @test h[43] ≈ 0.436641524481545f0 @test h[501] ≈ 0.05665942153713204f0 - @test h[5808] ≈ 0.005933025055544241f0 + @test h[5808] ≈ 0.005933025055544241f0 +end + +@testset "Exchange and grid location local inertial river flow" begin + river = model.lateral.river + @test Wflow.exchange(river, :dt) == 0 + @test Wflow.exchange(river, :g) == 0 + @test Wflow.exchange(river, :h) == 1 + @test Wflow.exchange(river, :q_av) == 1 + @test Wflow.grid_location(river, :dt) == "none" + @test Wflow.grid_location(river, :g) == "node" + @test Wflow.grid_location(river, :h) == "node" + @test Wflow.grid_location(river, :q_av) == "edge" +end + +@testset "Exchange and grid location floodplain" begin + floodplain = model.lateral.river.floodplain + @test Wflow.exchange(floodplain.profile, :depth) == 1 + @test Wflow.exchange(floodplain.profile, :a) == 1 + @test Wflow.grid_location(floodplain.profile, :depth) == "node" + @test Wflow.grid_location(floodplain.profile, :a) == "node" + @test Wflow.exchange(floodplain, :profile) == 0 + @test Wflow.exchange(floodplain, :h) == 1 + @test Wflow.exchange(floodplain, :q) == 1 + @test Wflow.grid_location(floodplain, :profile) == "node" + @test Wflow.grid_location(floodplain, :h) == "node" + @test Wflow.grid_location(floodplain, :q) == "edge" end # set boundary condition local inertial routing from netCDF file diff --git a/test/run_sbm_gwf.jl b/test/run_sbm_gwf.jl index c2d422f03..53c9d22fc 100644 --- a/test/run_sbm_gwf.jl +++ b/test/run_sbm_gwf.jl @@ -186,4 +186,21 @@ end @test gw.drain.flux[1] ≈ 0.0 @test gw.recharge.rate[19] ≈ -0.0014241196552847502f0 end + +@testset "Exchange and grid location aquifer, recharge and constant head" begin + aquifer = model.lateral.subsurface.flow.aquifer + @test Wflow.exchange(aquifer, :head) == 1 + @test Wflow.exchange(aquifer, :k) == 1 + @test Wflow.grid_location(aquifer, :head) == "node" + @test Wflow.grid_location(aquifer, :k) == "node" + recharge = model.lateral.subsurface.recharge + @test Wflow.exchange(recharge, :rate) == 1 + @test Wflow.exchange(recharge, :flux) == 1 + @test Wflow.grid_location(recharge, :rate) == "node" + @test Wflow.grid_location(recharge, :flux) == "node" + constanthead = model.lateral.subsurface.flow.constanthead + @test Wflow.exchange(constanthead, :head) == 0 + @test Wflow.grid_location(constanthead, :head) == "node" +end + Wflow.close_files(model, delete_output = false) diff --git a/test/run_sediment.jl b/test/run_sediment.jl index 7d009bddc..1d44f79c5 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -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) == 0 + @test Wflow.exchange(model.vertical, :erosk) == 1 + @test Wflow.exchange(model.vertical, :leaf_area_index) == 1 + @test Wflow.grid_location(model.vertical, :n) == "none" + @test Wflow.grid_location(model.vertical, :erosk) == "node" + @test Wflow.grid_location(model.vertical, :leaf_area_index) == "node" + land = model.lateral.land + @test Wflow.exchange(land, :n) == 0 + @test Wflow.exchange(land, :soilloss) == 1 + @test Wflow.exchange(land, :inlandsed) == 1 + @test Wflow.grid_location(land, :n) == "none" + @test Wflow.grid_location(land, :soilloss) == "node" + @test Wflow.grid_location(land, :inlandsed) == "node" +end + Wflow.close_files(model) From d21c9308931f73c76a385d03784255961682a1c3 Mon Sep 17 00:00:00 2001 From: Carlos Fernando Baptista Date: Mon, 15 Jul 2024 12:56:19 +0200 Subject: [PATCH 03/42] 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 --- .JuliaFormatter.toml | 12 ++ .gitignore | 3 - .vscode/settings.json | 9 ++ build/wflow_cli/test/runtests.jl | 18 +-- server/src/WflowServer.jl | 8 +- server/src/bmi_service.jl | 2 +- server/src/server.jl | 14 +- server/test/client.jl | 3 +- server/test/runtests.jl | 12 +- src/Wflow.jl | 20 +-- src/bmi.jl | 101 ++++++++------- src/flow.jl | 173 ++++++++++++++----------- src/groundwater/aquifer.jl | 32 ++--- src/groundwater/boundary_conditions.jl | 11 -- src/groundwater/connectivity.jl | 5 +- src/horizontal_process.jl | 30 +++-- src/io.jl | 136 ++++++++++--------- src/logging.jl | 4 +- src/reservoir_lake.jl | 23 ++-- src/sbm.jl | 71 +++++----- src/sbm_gwf_model.jl | 11 +- src/sbm_model.jl | 34 ++--- src/sediment.jl | 19 +-- src/sediment_model.jl | 11 +- src/states.jl | 5 +- src/subdomains.jl | 23 ++-- src/utils.jl | 27 ++-- src/vertical_process.jl | 8 +- test/bmi.jl | 10 +- test/groundwater.jl | 54 ++++---- test/horizontal_process.jl | 15 +-- test/io.jl | 11 +- test/reservoir_lake.jl | 10 +- test/run.jl | 1 - test/run_sbm.jl | 14 +- test/run_sbm_gwf.jl | 8 +- test/runtests.jl | 5 +- test/subdomains.jl | 2 +- test/testing_utils.jl | 41 +++--- 39 files changed, 504 insertions(+), 492 deletions(-) create mode 100644 .JuliaFormatter.toml create mode 100644 .vscode/settings.json 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/.gitignore b/.gitignore index 7312d368f..a1ede6c59 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/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/server/src/WflowServer.jl b/server/src/WflowServer.jl index c96c7731e..65b05ea30 100644 --- a/server/src/WflowServer.jl +++ b/server/src/WflowServer.jl @@ -1,8 +1,8 @@ module WflowServer -import ZMQ -import JSON3 -import StructTypes -import Wflow +using ZMQ: ZMQ +using JSON3: JSON3 +using StructTypes: StructTypes +using Wflow: Wflow include("bmi_service.jl") include("server.jl") diff --git a/server/src/bmi_service.jl b/server/src/bmi_service.jl index 3a344179a..4a87d8f1e 100644 --- a/server/src/bmi_service.jl +++ b/server/src/bmi_service.jl @@ -176,7 +176,7 @@ struct SaveState fn::String end -function wflow_bmi(m::Initialize, model::Union{Wflow.Model,Nothing}) +function wflow_bmi(m::Initialize, model::Union{Wflow.Model, Nothing}) model = getfield(Wflow.BMI, Symbol(m.fn))(Wflow.Model, m.config_file) return model end diff --git a/server/src/server.jl b/server/src/server.jl index 4e183af90..0ab9f35db 100644 --- a/server/src/server.jl +++ b/server/src/server.jl @@ -39,28 +39,28 @@ const map_structs = Dict( ) mutable struct ModelHandler - model::Union{Wflow.Model,Nothing} + model::Union{Wflow.Model, Nothing} end "Shutdown ZMQ server" function shutdown(s::ZMQ.Socket, ctx::ZMQ.Context) @info "Shutting down Wflow ZMQ server on request..." ZMQ.close(s) - ZMQ.close(ctx) + return ZMQ.close(ctx) end "Error response ZMQ server" function response(err::AbstractString, s::ZMQ.Socket) @info "Send error response" - resp = Dict{String,String}("status" => "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 d5eaa70a9..871ff929a 100644 --- a/server/test/client.jl +++ b/server/test/client.jl @@ -52,7 +52,8 @@ 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.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"] 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/src/Wflow.jl b/src/Wflow.jl index 651df3e21..ebeeca222 100644 --- a/src/Wflow.jl +++ b/src/Wflow.jl @@ -26,8 +26,8 @@ using IfElse const BMI = BasicModelInterface 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"]) @@ -43,7 +43,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) @@ -77,7 +77,7 @@ function Clock(config, reader) end starttime = cftime(config.starttime, calendar) - Clock(starttime, 0, dt) + return Clock(starttime, 0, dt) end include("io.jl") @@ -88,7 +88,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 @@ -184,7 +184,7 @@ function run(config::Config) error("unknown model type") end load_fixed_forcing(model) - run(model) + return run(model) end function run_timestep(model::Model; update_func = update, write_model_output = true) @@ -212,7 +212,7 @@ 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() @@ -233,7 +233,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 @@ -242,7 +242,7 @@ 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 @@ -258,7 +258,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 2ea9f0395..c51fca8d5 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,), @@ -49,12 +49,12 @@ function BMI.update(model::Model; run = nothing) model = run_timestep(model) elseif run == "sbm_until_recharge" model = run_timestep( - model, + model; update_func = update_until_recharge, write_model_output = false, ) elseif run == "sbm_after_subsurfaceflow" - model = run_timestep(model, update_func = update_after_subsurfaceflow) + model = run_timestep(model; update_func = update_after_subsurfaceflow) end return model end @@ -73,7 +73,7 @@ function BMI.update_until(model::Model, time::Float64) ) error(error_message) end - for _ = 1:steps + for _ in 1:steps model = run_timestep(model) end return model @@ -87,7 +87,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) @@ -96,11 +96,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 """ @@ -123,11 +123,11 @@ function BMI.get_input_var_names(model::Model) var = string(c, ".", name) model_var = param(model, var) if eltype(model_var) <: SVector - for i = 1:length(first(model_var)) + for i in 1:length(first(model_var)) push!(var_names, string(var, "[", i, "]")) end elseif ndims(model_var) > 1 - for i = 1:length(first(model_var)) + for i in 1:length(first(model_var)) push!(var_names, string(var, "[", i, "]")) end else @@ -146,14 +146,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 - type = typeof(param(model, key[1:end-1])) + if exchange(param(model, key[1:(end - 1)]), key[end]) == 1 + type = typeof(param(model, key[1:(end - 1)])) return if :reservoir in key 0 elseif :lake in key @@ -176,13 +176,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[1:(end - 1)]), key[end]) == 1 + get_units(param(model, key[1:(end - 1)]), key[end]) else error("$name not listed as variable for BMI exchange") end @@ -190,16 +190,16 @@ 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, "["))) - type = param(model, key[1:end-1]) + type = param(model, key[1:(end - 1)]) if exchange(type, key[end]) == 1 return grid_location(type, key[end]) else @@ -215,7 +215,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 +227,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 +247,7 @@ function BMI.get_value_ptr(model::Model, name::String) @unpack 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[1:(end - 1)]), key[end]) == 1 n = length(active_indices(network, key)) if occursin("[", name) ind = tryparse(Int, split(s[end], "]")[1]) @@ -270,7 +274,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 +285,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 +304,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 +326,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} @unpack reader, config = model @unpack dataset = reader sel = active_indices(model.network, grids[grid]) @@ -328,7 +336,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} @unpack reader, config = model @unpack dataset = reader sel = active_indices(model.network, grids[grid]) @@ -368,21 +376,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)") @@ -404,11 +412,11 @@ 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 and grid location functions. @@ -431,8 +439,8 @@ function grid_location(::SurfaceFlow, var) end end -exchange(::Union{LateralSSF,GroundwaterExchange}, var) = var == :dt ? 0 : 1 -grid_location(::Union{LateralSSF,GroundwaterExchange}, var) = var == :dt ? "none" : "node" +exchange(::Union{LateralSSF, GroundwaterExchange}, var) = var == :dt ? 0 : 1 +grid_location(::Union{LateralSSF, GroundwaterExchange}, var) = var == :dt ? "none" : "node" function exchange(::ShallowWaterRiver, var) if var in ( @@ -505,7 +513,6 @@ function grid_location(::FloodPlain, var) else "node" end - end exchange(::SimpleReservoir, var) = var == :dt ? 0 : 1 @@ -517,13 +524,13 @@ grid_location(::Lake, var) = var == :dt ? "none" : "node" exchange(::SBM, var) = var in (:n, :dt, :maxlayers) ? 0 : 1 grid_location(::SBM, var) = var in (:n, :dt, :maxlayers) ? "none" : "node" -exchange(::Union{LandSediment,OverlandFlowSediment}, var) = var == :n ? 0 : 1 -grid_location(::Union{LandSediment,OverlandFlowSediment}, var) = var == :n ? "none" : "node" +exchange(::Union{LandSediment, OverlandFlowSediment}, var) = var == :n ? 0 : 1 +grid_location(::Union{LandSediment, OverlandFlowSediment}, var) = + var == :n ? "none" : "node" exchange(::RiverSediment, var) = var in (:n, :dt) ? 0 : 1 grid_location(::RiverSediment, var) = var in (:n, :dt) ? "none" : "node" - exchange(::Aquifer, var) = 1 grid_location(::Aquifer, var) = "node" diff --git a/src/flow.jl b/src/flow.jl index 00e3475be..1e8eb22ac 100644 --- a/src/flow.jl +++ b/src/flow.jl @@ -1,7 +1,7 @@ abstract type SurfaceFlow end -@get_units @with_kw struct SurfaceFlowRiver{T,R,L} <: SurfaceFlow +@get_units @with_kw struct SurfaceFlowRiver{T, R, L} <: 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⁻⅓] @@ -71,7 +71,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, @@ -146,7 +146,7 @@ function initialize_surfaceflow_river( n = length(inds) - sf_river = SurfaceFlowRiver( + sf_river = SurfaceFlowRiver(; beta = Float(0.6), sl = sl, n = n_river, @@ -179,7 +179,6 @@ function initialize_surfaceflow_river( return sf_river end - function update(sf::SurfaceFlowLand, network, frac_toriver) @unpack graph, subdomain_order, topo_subdomain, indices_subdomain, upstream_nodes = network @@ -196,10 +195,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 @@ -243,11 +242,10 @@ function update(sf::SurfaceFlowLand, network, frac_toriver) sf.q_av ./= its sf.h_av ./= its sf.to_river ./= its - sf.volume .= sf.dl .* sf.width .* sf.h + return sf.volume .= sf.dl .* sf.width .* sf.h end function update(sf::SurfaceFlowRiver, network, doy) - @unpack graph, subdomain_order, topo_subdomain, indices_subdomain, upstream_nodes = network @@ -275,10 +273,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 @@ -356,10 +354,10 @@ function update(sf::SurfaceFlowRiver, network, doy) end sf.q_av ./= its sf.h_av ./= its - sf.volume .= sf.dl .* sf.width .* sf.h + return sf.volume .= sf.dl .* sf.width .* sf.h 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 @@ -368,7 +366,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))) @@ -414,13 +412,12 @@ end end end - function update(ssf::LateralSSF, network, frac_toriver, ksat_profile) @unpack subdomain_order, topo_subdomain, indices_subdomain, upstream_nodes = 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 @@ -486,7 +483,7 @@ end ssf::Vector{T} | "m3 d-1" # Subsurface flow [m³ d⁻¹] end -@get_units @with_kw struct ShallowWaterRiver{T,R,L,F} +@get_units @with_kw struct ShallowWaterRiver{T, R, L, F} n::Int | "-" # number of cells ne::Int | "-" # number of edges/links active_n::Vector{Int} | "-" # active nodes @@ -651,7 +648,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]) @@ -667,7 +664,7 @@ function initialize_shallowwater_river( waterbody = !=(0).(reservoir_index .+ lake_index) active_index = findall(x -> x == 0, waterbody) - sw_river = ShallowWaterRiver( + sw_river = ShallowWaterRiver(; n = n, ne = _ne, active_n = active_index, @@ -724,7 +721,6 @@ function get_inflow_waterbody(sw::ShallowWaterRiver, src_edge) end function shallowwater_river_update(sw::ShallowWaterRiver, network, dt, doy, update_h) - @unpack nodes_at_link, links_at_node = network sw.q0 .= sw.q @@ -781,7 +777,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] @@ -881,7 +877,6 @@ function shallowwater_river_update(sw::ShallowWaterRiver, network, dt, doy, upda 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] = sw.volume[i] + (q_src - q_dst + sw.inwater[i]) * dt @@ -1047,7 +1042,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. @@ -1071,7 +1066,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]) @@ -1098,7 +1093,7 @@ function initialize_shallowwater_land( indices_reverse[inds_riv], ) - sw_land = ShallowWaterLand{Float}( + sw_land = ShallowWaterLand{Float}(; n = n, xl = xlength, yl = ylength, @@ -1140,7 +1135,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 @@ -1150,10 +1145,12 @@ 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 @@ -1167,7 +1164,6 @@ function update( doy; update_h = false, ) where {T} - @unpack nodes_at_link, links_at_node = network.river if !isnothing(swr.reservoir) @@ -1198,7 +1194,7 @@ function update( end swr.q_av ./= swr.dt swr.h_av ./= swr.dt - sw.h_av ./= sw.dt + return sw.h_av ./= sw.dt end function shallowwater_update( @@ -1207,7 +1203,6 @@ function shallowwater_update( network, dt, ) where {T} - indices = network.land.staggered_indices inds_riv = network.land.index_river @@ -1217,7 +1212,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] @@ -1226,7 +1221,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) @@ -1266,7 +1260,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) @@ -1303,7 +1296,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] @@ -1364,15 +1357,15 @@ 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 @with_kw struct FloodPlainProfile{T,N} +@get_units @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) + 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 @with_kw struct FloodPlain{T,P} +@get_units @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" # manning's roughness squared @@ -1392,7 +1385,7 @@ 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( @@ -1468,7 +1461,6 @@ function initialize_floodplain_1d( n_edges, nodes_at_link, ) - n_floodplain = ncread( nc, config, @@ -1506,31 +1498,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 @@ -1542,8 +1535,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)", @@ -1558,7 +1551,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, @@ -1570,7 +1563,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 = @@ -1582,7 +1575,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, @@ -1632,7 +1625,9 @@ 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} +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 do_drains = get(config.model, "drains", false)::Bool @@ -1642,7 +1637,7 @@ function set_land_inwater(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmGwfM -lateral.subsurface.drain.flux ./ tosecond(basetimestep) end - lateral.land.inwater .= + return lateral.land.inwater .= (vertical.net_runoff .* network.land.xl .* network.land.yl .* 0.001) ./ lateral.land.dt .+ drainflux end @@ -1652,9 +1647,11 @@ 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} +function set_land_inwater( + model::Model{N, L, V, R, W, T}, +) where {N, L, V, R, W, T <: SbmModel} @unpack lateral, vertical, network = model - lateral.land.inwater .= + return lateral.land.inwater .= (vertical.net_runoff .* network.land.xl .* network.land.yl .* 0.001) ./ lateral.land.dt end @@ -1676,8 +1673,8 @@ 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`. """ 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} + 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 inds = network.index_river @@ -1702,8 +1699,15 @@ Set inflow from the subsurface and land components to a water body (reservoir or `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} + 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 inds = network.index_river @@ -1731,8 +1735,15 @@ Set inflow from the subsurface and land components to a water body (reservoir or `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} + 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 inds = network.index_river @@ -1761,7 +1772,7 @@ function surface_routing(model; ssf_toriver = 0.0) # run river flow set_river_inwater(model, ssf_toriver) set_inflow_waterbody(model) - update(lateral.river, network.river, julian_day(clock.time - clock.dt)) + return update(lateral.river, network.river, julian_day(clock.time - clock.dt)) end """ @@ -1774,10 +1785,16 @@ Run surface routing (land and river) for a model type that contains the lateral `ShallowWaterLand` and `ShallowWaterRiver`. """ function surface_routing( - model::Model{N,L,V,R,W,T}; + 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, +} @unpack lateral, vertical, network, clock = model @. lateral.land.runoff = ( @@ -1790,5 +1807,5 @@ function surface_routing( ) ) set_inflow_waterbody(model) - update(lateral.land, lateral.river, network, julian_day(clock.time - clock.dt)) + return update(lateral.land, lateral.river, network, julian_day(clock.time - clock.dt)) end diff --git a/src/groundwater/aquifer.jl b/src/groundwater/aquifer.jl index 34a59439c..3b8bb8380 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 @@ -97,7 +95,6 @@ transmissivity). conductance::Vector{T} | "m2 d-1" # Confined aquifer conductance is constant end - """ UnconfinedAquifer{T} <: Aquifer @@ -125,7 +122,6 @@ end storativity(A::UnconfinedAquifer) = A.specific_yield storativity(A::ConfinedAquifer) = A.storativity - """ harmonicmean_conductance(kH1, kH2, l1, l2, width) @@ -149,15 +145,13 @@ 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 - """ horizontal_conductance(i, j, nzi, aquifer, C) @@ -172,7 +166,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] @@ -192,8 +186,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] @@ -203,7 +200,6 @@ function initialize_conductance!(aquifer::A, connectivity::Connectivity) where { end end - function conductance( aquifer::ConfinedAquifer, i, @@ -215,7 +211,6 @@ function conductance( return aquifer.conductance[nzi] end - """ conductance(aquifer::UnconfinedAquifer, connectivity::Connectivity) @@ -250,7 +245,6 @@ function conductance( conductivity_profile::String, connectivity::Connectivity, ) - if conductivity_profile == "exponential" # Extract required variables zi1 = aquifer.top[i] - aquifer.head[i] @@ -292,7 +286,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 @@ -340,7 +334,6 @@ end minimum_head(aquifer::ConfinedAquifer) = aquifer.head minimum_head(aquifer::UnconfinedAquifer) = max.(aquifer.head, aquifer.bottom) - 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) @@ -355,8 +348,7 @@ function update(gwf, Q, dt, conductivity_profile) return gwf end - -Base.@kwdef struct GroundwaterFlow{A,B} +Base.@kwdef struct GroundwaterFlow{A, B} aquifer::A connectivity::Connectivity constanthead::ConstantHead @@ -366,8 +358,8 @@ Base.@kwdef struct GroundwaterFlow{A,B} connectivity, constanthead, boundaries::Vector{B}, - ) where {A<:Aquifer,B<:AquiferBoundaryCondition} + ) where {A <: Aquifer, B <: AquiferBoundaryCondition} initialize_conductance!(aquifer, connectivity) - new{A,B}(aquifer, connectivity, constanthead, boundaries) + return new{A, B}(aquifer, connectivity, constanthead, boundaries) end end diff --git a/src/groundwater/boundary_conditions.jl b/src/groundwater/boundary_conditions.jl index 39de1226f..302caa3b9 100644 --- a/src/groundwater/boundary_conditions.jl +++ b/src/groundwater/boundary_conditions.jl @@ -8,11 +8,9 @@ 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 struct River{T} <: AquiferBoundaryCondition stage::Vector{T} | "m" infiltration_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,7 +37,6 @@ function flux!(Q, river::River, aquifer) return Q end - @get_units struct Drainage{T} <: AquiferBoundaryCondition elevation::Vector{T} | "m" conductance::Vector{T} | "m2 d-1" @@ -48,7 +44,6 @@ end index::Vector{Int} | "-" end - function flux!(Q, drainage::Drainage, aquifer) for (i, index) in enumerate(drainage.index) cond = drainage.conductance[i] @@ -59,7 +54,6 @@ function flux!(Q, drainage::Drainage, aquifer) return Q end - @get_units struct HeadBoundary{T} <: AquiferBoundaryCondition head::Vector{T} | "m" conductance::Vector{T} | "m2 d-1" @@ -67,7 +61,6 @@ end index::Vector{Int} | "-" end - function flux!(Q, headboundary::HeadBoundary, aquifer) for (i, index) in enumerate(headboundary.index) cond = headboundary.conductance[i] @@ -78,14 +71,12 @@ function flux!(Q, headboundary::HeadBoundary, aquifer) return Q end - @get_units 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] = @@ -95,14 +86,12 @@ function flux!(Q, recharge::Recharge, aquifer) return Q end - @get_units 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/horizontal_process.jl b/src/horizontal_process.jl index 2d10333ce..eb1e52316 100644 --- a/src/horizontal_process.jl +++ b/src/horizontal_process.jl @@ -75,7 +75,8 @@ function ssf_water_table_depth(ssf, kh_0, slope, f, d, dw, z_exp, ksat_profile) 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 + 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 @@ -127,7 +128,6 @@ function kinematic_wave_ssf( z_exp, ksat_profile, ) - epsilon = 1.0e-12 max_iters = 3000 @@ -194,7 +194,6 @@ function kinematic_wave_ssf( zi = clamp(zi, 0.0, d) return ssf, zi, exfilt - end end @@ -207,8 +206,20 @@ 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, + kh, + slope, + theta_e, + d, + dt, + dx, + dw, + ssfmax, +) epsilon = 1.0e-12 max_iters = 3000 @@ -298,7 +309,7 @@ end Non mutating version of `accucapacitystate!`. """ function accucapacitystate(material, network, capacity) - accucapacitystate!(copy(material), network, capacity) + return accucapacitystate!(copy(material), network, capacity) end """ @@ -338,7 +349,7 @@ end Non mutating version of `accucapacityflux!`. """ function accucapacityflux(material, network, capacity) - accucapacityflux!(zero(material), copy(material), network, capacity) + return accucapacityflux!(zero(material), copy(material), network, capacity) end """ @@ -373,7 +384,6 @@ function local_inertial_flow( froude_limit, dt, ) - slope = (zs1 - zs0) / length pow_R = cbrt(R * R * R * R) unit = one(hf) @@ -411,15 +421,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 60c8c0dae..067bf55dc 100644 --- a/src/io.jl +++ b/src/io.jl @@ -10,7 +10,7 @@ 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 +34,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) @@ -127,7 +127,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,7 +178,7 @@ function get_at(ds::CFDataset, varname::AbstractString, i) end function get_param_res(model) - Dict( + return Dict( symbols"vertical.precipitation" => model.lateral.river.reservoir.precipitation, symbols"vertical.potential_evaporation" => model.lateral.river.reservoir.evaporation, @@ -186,7 +186,7 @@ function get_param_res(model) end function get_param_lake(model) - Dict( + return Dict( symbols"vertical.precipitation" => model.lateral.river.lake.precipitation, symbols"vertical.potential_evaporation" => model.lateral.river.lake.evaporation, ) @@ -368,7 +368,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 +402,7 @@ function setup_scalar_netcdf( ds, "time", Float64, - ("time",), + ("time",); attrib = ["units" => time_units, "calendar" => calendar], ) set_extradim_netcdf(ds, extra_dim) @@ -413,7 +413,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 +422,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 +432,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 +440,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,8 +456,8 @@ 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 @@ -465,7 +465,7 @@ function set_extradim_netcdf( attributes = ["long_name" => "layer_index", "standard_name" => "layer_index", "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 @@ -484,7 +484,6 @@ function setup_grid_netcdf( float_type = Float32, deflatelevel = 0, ) - ds = create_tracked_netcdf(path) defDim(ds, "time", Inf) # unlimited if sizeinmetres @@ -492,7 +491,7 @@ function setup_grid_netcdf( ds, "x", ncx, - ("x",), + ("x",); attrib = [ "long_name" => "x coordinate of projection", "standard_name" => "projection_x_coordinate", @@ -505,7 +504,7 @@ function setup_grid_netcdf( ds, "y", ncy, - ("y",), + ("y",); attrib = [ "long_name" => "y coordinate of projection", "standard_name" => "projection_y_coordinate", @@ -520,7 +519,7 @@ function setup_grid_netcdf( ds, "lon", ncx, - ("lon",), + ("lon",); attrib = [ "long_name" => "longitude", "standard_name" => "longitude", @@ -532,7 +531,7 @@ function setup_grid_netcdf( ds, "lat", ncy, - ("lat",), + ("lat",); attrib = [ "long_name" => "latitude", "standard_name" => "latitude", @@ -547,7 +546,7 @@ function setup_grid_netcdf( ds, "time", Float64, - ("time",), + ("time",); attrib = ["units" => time_units, "calendar" => calendar], deflatelevel = deflatelevel, ) @@ -559,7 +558,7 @@ function setup_grid_netcdf( ds, key, float_type, - ("x", "y", "time"), + ("x", "y", "time"); attrib = ["_FillValue" => float_type(NaN)], deflatelevel = deflatelevel, ) @@ -569,7 +568,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, ) @@ -585,7 +584,7 @@ function setup_grid_netcdf( ds, key, float_type, - ("lon", "lat", "time"), + ("lon", "lat", "time"); attrib = ["_FillValue" => float_type(NaN)], deflatelevel = deflatelevel, ) @@ -595,7 +594,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, ) @@ -617,27 +616,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) @@ -666,7 +665,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." @@ -689,7 +688,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)) @@ -705,8 +704,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)) @@ -720,9 +719,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 @@ -753,7 +752,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)) @@ -866,7 +865,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) @@ -881,7 +880,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) @@ -889,7 +888,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) @@ -937,12 +935,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 @@ -966,7 +964,7 @@ function prepare_writer( ) else ds_outstate = nothing - state_map = Dict{String,Any}() + state_map = Dict{String, Any}() nc_state_path = nothing end @@ -1031,7 +1029,6 @@ function prepare_writer( csv_io = IOBuffer() end - return Writer( ds, output_map, @@ -1070,7 +1067,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 @@ -1088,7 +1085,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 @unpack par, vector = val sel = active_indices(network, par) @@ -1102,7 +1099,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) @@ -1207,7 +1204,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, @@ -1238,13 +1235,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 @@ -1320,7 +1317,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" @@ -1357,7 +1354,7 @@ 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) @@ -1371,14 +1368,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) @@ -1420,7 +1417,6 @@ using `nc_dim_name`. """ nc_dim(ds::CFDataset, name) = ds[nc_dim_name(ds, name)] - """ internal_dim_name(name::Symbol) @@ -1491,7 +1487,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 @@ -1595,7 +1591,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 """ @@ -1611,14 +1607,14 @@ 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` based on `model`" function get_index_dimension(var, model)::Int @unpack vertical = model if haskey(var, "layer") - inds = collect(1:vertical.maxlayers) + inds = collect(1:(vertical.maxlayers)) index = inds[var["layer"]] else error("Unrecognized or missing dimension name to index $(var)") @@ -1630,7 +1626,7 @@ end 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] else error("Unrecognized or missing dimension name to index $(var)") 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/reservoir_lake.jl b/src/reservoir_lake.jl index b0ba2611c..886495660 100644 --- a/src/reservoir_lake.jl +++ b/src/reservoir_lake.jl @@ -131,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, @@ -207,14 +207,14 @@ end dt::T | "s" # 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³] @@ -362,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)) @@ -397,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, @@ -429,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) @@ -445,7 +444,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 @@ -459,7 +458,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) @@ -481,7 +479,6 @@ This is called from within the kinematic wave loop, therefore updating only for element rather than all at once. """ function update(lake::Lake, i, inflow, doy, timestepsecs) - lo = lake.lowerlake_ind[i] has_lowerlake = lo != 0 @@ -518,7 +515,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 @@ -578,7 +574,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 diff --git a/src/sbm.jl b/src/sbm.jl index 7e2676a16..8c71f2958 100644 --- a/src/sbm.jl +++ b/src/sbm.jl @@ -1,4 +1,4 @@ -@get_units @with_kw struct SBM{T,N,M} +@get_units @with_kw struct SBM{T, N, M} # Model time step [s] dt::T | "s" # Maximum number of soil layers @@ -20,17 +20,17 @@ # 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}} | "-" + kv::Vector{SVector{N, T}} | "-" # Muliplication factor [-] applied to kv_z (vertical flow) - kvfrac::Vector{SVector{N,T}} | "-" + 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" + act_thickl::Vector{SVector{N, T}} | "mm" # Cumulative sum of soil layers [mm], starting at soil surface (0) - sumlayers::Vector{SVector{M,T}} | "mm" + sumlayers::Vector{SVector{M, T}} | "mm" # Infiltration capacity of the compacted areas [mm Δt⁻¹] infiltcappath::Vector{T} # Soil infiltration capacity [mm Δt⁻¹] @@ -52,7 +52,7 @@ # Crop coefficient Kc [-] kc::Vector{T} | "-" # Brooks-Corey power coefficient [-] for each soil layer - c::Vector{SVector{N,T}} | "-" + c::Vector{SVector{N, T}} | "-" # Stemflow [mm Δt⁻¹] stemflow::Vector{T} # Throughfall [mm Δt⁻¹] @@ -64,7 +64,7 @@ # 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" + ustorelayerdepth::Vector{SVector{N, T}} | "mm" # Saturated store [mm] satwaterdepth::Vector{T} | "mm" # Pseudo-water table depth [mm] (top of the saturated zone) @@ -141,9 +141,9 @@ # 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}} | "-" + vwc::Vector{SVector{N, T}} | "-" # Volumetric water content [%] per soil layer (including theta_r and saturated zone) - vwc_perc::Vector{SVector{N,T}} | "%" + 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) @@ -207,7 +207,7 @@ # Total water storage (excluding floodplain volume, lakes and reservoirs) [mm] total_storage::Vector{T} | "mm" - function SBM{T,N,M}(args...) where {T,N,M} + function SBM{T, N, M}(args...) where {T, N, M} equal_size_vectors(args) return new(args...) end @@ -260,7 +260,6 @@ function initialize_canopy(nc, config, inds) 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 @@ -477,7 +476,7 @@ function initialize_sbm(nc, config, riverfrac, inds) act_thickl = zeros(Float, maxlayers, n) s_layers = zeros(Float, maxlayers + 1, n) - for i = 1:n + for i in 1:n if length(config_thicknesslayers) > 0 act_thickl_, nlayers_ = set_layerthickness(soilthickness[i], sumlayers, thicknesslayers) @@ -556,7 +555,7 @@ function initialize_sbm(nc, config, riverfrac, inds) """) end - sbm = SBM{Float,maxlayers,maxlayers + 1}( + sbm = SBM{Float, maxlayers, maxlayers + 1}(; dt = tosecond(dt), maxlayers = maxlayers, n = n, @@ -665,25 +664,24 @@ function initialize_sbm(nc, config, riverfrac, inds) ) 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 + 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 + e_r = if sbm.precipitation[i] > 0.0 + min(0.25, ewet / max(0.0001, canopyfraction * sbm.precipitation[i])) + else + 0.0 + end else cmax = sbm.cmax[i] canopygapfraction = sbm.canopygapfraction[i] @@ -756,7 +754,7 @@ function update_until_recharge(sbm::SBM, config) 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 + threaded_foreach(1:(sbm.n); basesize = 250) do i if modelsnow rainfallplusmelt = sbm.rainfallplusmelt[i] if modelglacier @@ -777,7 +775,6 @@ function update_until_recharge(sbm::SBM, config) # 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] @@ -826,7 +823,6 @@ function update_until_recharge(sbm::SBM, config) soilinfreduction, ) - usl, n_usl = set_layerthickness(sbm.zi[i], sbm.sumlayers[i], sbm.act_thickl[i]) z = cumsum(usl) usld = sbm.ustorelayerdepth[i] @@ -852,12 +848,14 @@ function update_until_recharge(sbm::SBM, config) ) usld = setindex(usld, ustorelayerdepth, 1) else - for m = 1:n_usl + for m in 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 : + ustorelayerdepth = if m == 1 + sbm.ustorelayerdepth[i][m] + infiltsoilpath + else sbm.ustorelayerdepth[i][m] + ast + end ustorelayerdepth, ast = unsatzone_flow_layer(ustorelayerdepth, kv_z, l_sat, sbm.c[i][m]) usld = setindex(usld, ustorelayerdepth, m) @@ -922,7 +920,7 @@ function update_until_recharge(sbm::SBM, config) # actual transpiration from ustore actevapustore = 0.0 - for k = 1:n_usl + for k in 1:n_usl ustorelayerdepth, actevapustore, restpottrans = acttransp_unsat_sbm( rootingdepth, usld[k], @@ -941,11 +939,11 @@ function update_until_recharge(sbm::SBM, config) # check soil moisture balance per layer du = 0.0 - for k = n_usl:-1:1 + for k in 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) + usld = setindex(usld, usld[k - 1] + du, k - 1) end end @@ -982,7 +980,7 @@ function update_until_recharge(sbm::SBM, config) end netcapflux = capflux - for k = n_usl:-1:1 + for k in n_usl:-1:1 toadd = min( netcapflux, max(usl[k] * (sbm.theta_s[i] - sbm.theta_r[i]) - usld[k], 0.0), @@ -1046,13 +1044,12 @@ function update_until_recharge(sbm::SBM, config) end function update_after_subsurfaceflow(sbm::SBM, zi, exfiltsatwater) - - threaded_foreach(1:sbm.n, basesize = 1000) do i + 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 + for k in 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 @@ -1060,7 +1057,7 @@ function update_after_subsurfaceflow(sbm::SBM, zi, exfiltsatwater) end usld = setindex(usld, usld[k] - exfiltustore, k) if k > 1 - usld = setindex(usld, usld[k-1] + exfiltustore, k - 1) + usld = setindex(usld, usld[k - 1] + exfiltustore, k - 1) end end @@ -1076,7 +1073,7 @@ function update_after_subsurfaceflow(sbm::SBM, zi, exfiltsatwater) # 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] + for k in 1:sbm.nlayers[i] if k <= n_usl vwc = setindex( vwc, @@ -1093,7 +1090,7 @@ function update_after_subsurfaceflow(sbm::SBM, zi, exfiltsatwater) end rootstore_unsat = 0 - for k = 1:n_usl + for k in 1:n_usl rootstore_unsat = rootstore_unsat + min(1.0, (max(0.0, sbm.rootingdepth[i] - sbm.sumlayers[i][k]) / usl[k])) * @@ -1166,7 +1163,7 @@ function update_total_water_storage( ) # Chunk the data for parallel computing - threaded_foreach(1:sbm.n, basesize = 1000) do i + threaded_foreach(1:(sbm.n); basesize = 1000) do i # Cumulate per vertical type # Maybe re-categorize in the future diff --git a/src/sbm_gwf_model.jl b/src/sbm_gwf_model.jl index d715399d2..1393ad97c 100644 --- a/src/sbm_gwf_model.jl +++ b/src/sbm_gwf_model.jl @@ -64,7 +64,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 @@ -391,8 +391,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:(sbm.maxlayers))), ) close(nc) @@ -477,9 +477,8 @@ function initialize_sbm_gwf_model(config::Config) 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} +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 inds_riv = network.index_river @@ -547,7 +546,7 @@ function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmGwfModel} ssf_toriver = zeros(vertical.n) 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 end diff --git a/src/sbm_model.jl b/src/sbm_model.jl index bc238bab9..2d7663e99 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`." @@ -62,7 +61,7 @@ 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 @@ -129,7 +128,7 @@ function initialize_sbm_model(config::Config) soilthickness = sbm.soilthickness .* 0.001 z_exp = sbm.z_exp .* 0.001 - ssf = LateralSSF{Float}( + ssf = LateralSSF{Float}(; kh_0 = kh_0, f = f, kh = fill(mv, n), @@ -162,7 +161,7 @@ function initialize_sbm_model(config::Config) 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), @@ -311,8 +310,8 @@ function initialize_sbm_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:(sbm.maxlayers))), ) close(nc) @@ -386,8 +385,7 @@ function initialize_sbm_model(config::Config) 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} @unpack lateral, vertical, network, clock, config = model ksat_profile = get(config.input.vertical, "ksat_profile", "exponential")::String @@ -405,7 +403,7 @@ function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SbmModel} end update(lateral.subsurface, network.land, network.frac_toriver, ksat_profile) model = update_after_subsurfaceflow(model) - model = update_total_water_storage(model) + return model = update_total_water_storage(model) end """ @@ -414,7 +412,9 @@ end 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} @unpack lateral, vertical, network, clock, config = model inds_riv = network.index_river @@ -451,8 +451,8 @@ Update SBM model after subsurface flow for a single timestep. This function is a 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} + model::Model{N, L, V, R, W, T}, +) where {N, L, V, R, W, T <: SbmModel} @unpack lateral, vertical, network, clock, config = model # update vertical sbm concept (runoff, ustorelayerdepth and satwaterdepth) @@ -463,7 +463,7 @@ function update_after_subsurfaceflow( ) ssf_toriver = lateral.subsurface.to_river ./ tosecond(basetimestep) - surface_routing(model, ssf_toriver = ssf_toriver) + surface_routing(model; ssf_toriver = ssf_toriver) return model end @@ -473,7 +473,9 @@ 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} @unpack lateral, vertical, network, clock, config = model # Update the total water storage based on vertical states @@ -490,8 +492,8 @@ function update_total_water_storage(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W, end function set_states( - model::Model{N,L,V,R,W,T}, -) where {N,L,V,R,W,T<:Union{SbmModel,SbmGwfModel}} + model::Model{N, L, V, R, W, T}, +) where {N, L, V, R, W, T <: Union{SbmModel, SbmGwfModel}} @unpack lateral, vertical, network, config = model reinit = get(config.model, "reinit", true)::Bool diff --git a/src/sediment.jl b/src/sediment.jl index 4d6edd153..df572c8ba 100644 --- a/src/sediment.jl +++ b/src/sediment.jl @@ -99,7 +99,6 @@ end end - function initialize_landsed(nc, config, river, riverfrac, xl, yl, inds) # Initialize parameters for the soil loss part n = length(inds) @@ -189,7 +188,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 +231,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, @@ -307,7 +306,7 @@ function update_until_ols(eros::LandSediment, config) 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 +391,16 @@ function update_until_ols(eros::LandSediment, config) eros.erossagg[i] = soilloss * eros.fsagg[i] eros.eroslagg[i] = soilloss * eros.flagg[i] end - end ### Sediment transport capacity in overland flow ### 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 +438,6 @@ function update_until_oltransport(ols::LandSediment, config::Config) ols.TCsagg[i] = TCsagg ols.TClagg[i] = TClagg end - end function tc_govers(ols::LandSediment, i::Int, sinslope::Float, ts::Float)::Float @@ -627,7 +623,6 @@ 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...) @@ -1010,7 +1005,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 @@ -1048,7 +1043,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 @@ -1570,7 +1565,5 @@ function update(rs::RiverSediment, network, config) rs.SSconc[v] = SS * toconc rs.Bedconc[v] = Bed * toconc - end - end diff --git a/src/sediment_model.jl b/src/sediment_model.jl index e2229a1bd..e9a054526 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), @@ -154,7 +153,7 @@ function initialize_sediment_model(config::Config) 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} @unpack lateral, vertical, network, clock, config = model update_until_ols(vertical, config) @@ -192,7 +191,9 @@ function update(model::Model{N,L,V,R,W,T}) where {N,L,V,R,W,T<:SedimentModel} return model 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 @unpack config = model reinit = get(config.model, "reinit", true)::Bool diff --git a/src/states.jl b/src/states.jl index 2c5eb4d70..122286b46 100644 --- a/src/states.jl +++ b/src/states.jl @@ -56,8 +56,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) 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/utils.jl b/src/utils.jl index c91442e97..f30696514 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -98,7 +98,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 +116,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 @@ -293,7 +293,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 @@ -347,10 +347,9 @@ a SVector `sl` with cumulative soil depth starting at soil surface (0), and a SV 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] + for i in 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) @@ -435,10 +434,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 """ @@ -497,8 +496,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) @@ -519,7 +518,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 @@ -556,7 +555,6 @@ function set_effective_flowwidth!( waterbody, inds_rev_riv, ) - toposort = topological_sort_by_dfs(graph_riv) n = length(we_x) for v in toposort @@ -618,7 +616,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 """ @@ -691,7 +689,6 @@ of vertical concept `SBM` (at index `i`) based on multiplication factor `khfrac` table depth `z` [mm] and hydraulic conductivity profile `ksat_profile`. """ function kh_layered_profile(sbm::SBM, khfrac, i, ksat_profile) - m = sbm.nlayers[i] t_factor = (tosecond(basetimestep) / sbm.dt) if (sbm.soilthickness[i] - sbm.zi[i]) > 0.0 @@ -809,7 +806,7 @@ function initialize_lateralssf_layered!(ssf::LateralSSF, sbm::SBM, ksat_profile) ssf.ssf[i] = ssf.kh[i] * (ssf.soilthickness[i] - ssf.zi[i]) * ssf.slope[i] * ssf.dw[i] kh_max = 0.0 - for j = 1:sbm.nlayers[i] + for j in 1:sbm.nlayers[i] if j <= sbm.nlayers_kv[i] kh_max += sbm.kv[i][j] * sbm.act_thickl[i][j] else diff --git a/src/vertical_process.jl b/src/vertical_process.jl index 9801b1e2e..4b50cb833 100644 --- a/src/vertical_process.jl +++ b/src/vertical_process.jl @@ -65,7 +65,6 @@ function rainfall_interception_gash( throughfall = throughfall + canopy_drainage return throughfall, interception, stemflow, canopystorage - end """ @@ -116,7 +115,6 @@ function rainfall_interception_modrut( interception = canopy_evap return netinterception, throughfall, stemflow, leftover, interception, canopystorage - end """ @@ -282,7 +280,7 @@ function unsatzone_flow_layer(usd, kv_z, l_sat, c) 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 + for _ in 1:its st = (kv_z / its) * min(pow(usd / l_sat, c), 1.0) ast = min(st, usd) usd -= ast @@ -310,7 +308,6 @@ function unsatzone_flow_sbm( theta_s, theta_r, ) - sd = soilwatercapacity - satwaterdepth if sd <= 0.00001 ast = 0.0 @@ -321,10 +318,8 @@ function unsatzone_flow_sbm( end return ustorelayerdepth, ast - end - """ snowpack_hbv(snow, snowwater, precipitation, temperature, tti, tt, ttm, cfmax, whc) @@ -447,5 +442,4 @@ function glacier_hbv(glacierfrac, glacierstore, snow, temperature, tt, cfmax, g_ glacierstore = glacierstore - glaciermelt return snow, snow2glacier, glacierstore, glaciermelt - end diff --git a/test/bmi.jl b/test/bmi.jl index db8c95462..6d1f73867 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 @@ -125,7 +122,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,7 +146,7 @@ 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 @@ -173,7 +169,7 @@ 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 @@ -190,11 +186,9 @@ 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) diff --git a/test/groundwater.jl b/test/groundwater.jl index 13d529ed1..33308f89f 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 @@ -60,7 +58,6 @@ function homogenous_aquifer(nrow, ncol) return (connectivity, conf_aqf, unconf_aqf) end - @testset "groundwater" begin ncol = 2 nrow = 3 @@ -352,7 +349,7 @@ end Q = zeros(3) dt = 0.25 # days - for _ = 1:50 + for _ in 1:50 Wflow.update(gwf, Q, dt, conductivity_profile) end @@ -374,7 +371,7 @@ end Q = zeros(3) dt = 0.25 # days - for _ = 1:50 + for _ in 1:50 Wflow.update(gwf, Q, dt, conductivity_profile) end @@ -402,7 +399,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), @@ -428,14 +425,21 @@ end nstep = Int(ceil(time / dt)) time = nstep * dt - for i = 1:nstep + 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 head_analytical = [ - transient_aquifer_1d(x, time, conductivity, specific_yield, aquifer_length, beta) for x in xc + transient_aquifer_1d( + x, + time, + conductivity, + specific_yield, + aquifer_length, + beta, + ) for x in xc ] difference = gwf.aquifer.head .- head_analytical # @test all(difference .< ?) #TODO @@ -462,7 +466,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), @@ -488,14 +492,21 @@ end nstep = Int(ceil(time / dt)) time = nstep * dt - for i = 1:nstep + 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 head_analytical = [ - transient_aquifer_1d(x, time, conductivity, specific_yield, aquifer_length, beta) for x in xc + transient_aquifer_1d( + x, + time, + conductivity, + specific_yield, + aquifer_length, + beta, + ) for x in xc ] difference = gwf.aquifer.head .- head_analytical # @test all(difference .< ?) #TODO @@ -537,7 +548,7 @@ end fill(0.0, connectivity.nconnection), # conductance, 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 @@ -550,24 +561,23 @@ end nstep = Int(ceil(time / dt)) time = nstep * dt - for i = 1:nstep + 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/horizontal_process.jl b/test/horizontal_process.jl index 3e9eb41ed..fe23c0b42 100644 --- a/test/horizontal_process.jl +++ b/test/horizontal_process.jl @@ -41,7 +41,7 @@ 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 @@ -114,7 +114,6 @@ end end @testset "local inertial long channel MacDonald (1997)" begin - g = 9.80665 L = 1000.0 dx = 5.0 @@ -135,11 +134,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 +155,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 +167,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 +179,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, @@ -241,5 +239,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 73d681ee6..679691815 100644 --- a/test/io.jl +++ b/test/io.jl @@ -11,7 +11,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 @@ -33,7 +33,7 @@ config = Wflow.Config(tomlpath) # modifiers can also be applied kvconf = Wflow.get_alias(config.input.vertical, "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 @@ -284,7 +284,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() @@ -296,7 +296,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]) @@ -307,7 +306,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 @@ -316,7 +315,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) diff --git a/test/reservoir_lake.jl b/test/reservoir_lake.jl index 51201e337..de4386ef4 100644 --- a/test/reservoir_lake.jl +++ b/test/reservoir_lake.jl @@ -1,4 +1,4 @@ -res = Wflow.SimpleReservoir{Float64}( +res = Wflow.SimpleReservoir{Float64}(; dt = 86400.0, demand = [52.523], maxrelease = [420.184], @@ -37,7 +37,7 @@ end @test Wflow.grid_location(res, :outflow) == "node" end -lake = Wflow.Lake{Float64}( +lake = Wflow.Lake{Float64}(; dt = 86400.0, lowerlake_ind = [0], area = [180510409.0], @@ -85,9 +85,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], @@ -139,7 +139,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], diff --git a/test/run.jl b/test/run.jl index 084b11b93..0a1850795 100644 --- a/test/run.jl +++ b/test/run.jl @@ -46,7 +46,6 @@ config.output.path = model = Wflow.initialize_sbm_model(config) 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 config.starttime = DateTime("2000-01-14T00:00:00") diff --git a/test/run_sbm.jl b/test/run_sbm.jl index 76db24547..b5ed5a5d6 100644 --- a/test/run_sbm.jl +++ b/test/run_sbm.jl @@ -109,7 +109,7 @@ end ssf = model.lateral.subsurface.ssf @test sum(ssf) ≈ 6.370399148012509f7 @test ssf[network.land.order[1]] ≈ 7.169036749244327f2 - @test ssf[network.land.order[end-100]] ≈ 2333.801056570759f0 + @test ssf[network.land.order[end - 100]] ≈ 2333.801056570759f0 @test ssf[network.land.order[end]] ≈ 288.19428729403944f0 end @@ -180,7 +180,7 @@ evap = copy(model.vertical.potential_evaporation) lai = copy(model.vertical.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) @@ -271,7 +271,7 @@ model = Wflow.run_timestep(model) @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") @@ -295,7 +295,7 @@ 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") @@ -336,7 +336,7 @@ end @test Wflow.grid_location(land, :qx) == "edge" 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") @@ -508,7 +508,7 @@ model = Wflow.run_timestep(model) @test h[501] ≈ 0.05665929962422606f0 @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 @@ -613,5 +613,5 @@ Wflow.close_files(model, delete_output = false) @test q[43] ≈ 10.013363662625276f0 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 53c9d22fc..81d0b6f88 100644 --- a/test/run_sbm_gwf.jl +++ b/test/run_sbm_gwf.jl @@ -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") @@ -100,7 +100,7 @@ model = Wflow.run_timestep(model) @test q[13] ≈ 0.0004590884299495001f0 @test q[5] ≈ 0.006328157455390906f0 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") @@ -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") @@ -203,4 +203,4 @@ end @test Wflow.grid_location(constanthead, :head) == "node" end -Wflow.close_files(model, delete_output = false) +Wflow.close_files(model; delete_output = false) diff --git a/test/runtests.jl b/test/runtests.jl index 440c640a3..3cf080855 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,11 +11,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 @@ -66,7 +66,6 @@ lake_hq_2_path = testdata(v"0.2.1", "lake_hq_2.csv", "lake_hq_2.csv") forcing_calendar_noleap_path = testdata(v"0.2.8", "forcing-calendar-noleap.nc", "forcing-calendar-noleap.nc") - include("testing_utils.jl") @info "testing Wflow with" nthreads() VERSION Float diff --git a/test/subdomains.jl b/test/subdomains.jl index bb5517640..441f01ac4 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 From 5ab02bfa5068fb89a1a933a8e24308580d0874c0 Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Thu, 18 Jul 2024 13:55:44 +0200 Subject: [PATCH 04/42] 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 --- .gitignore | 2 -- pixi.toml | 8 +------- utils/env_setup.bat | 1 - utils/env_setup.sh | 3 --- 4 files changed, 1 insertion(+), 13 deletions(-) delete mode 100644 utils/env_setup.bat delete mode 100644 utils/env_setup.sh diff --git a/.gitignore b/.gitignore index a1ede6c59..aff6d881c 100644 --- a/.gitignore +++ b/.gitignore @@ -24,5 +24,3 @@ # pixi environments .pixi -# juliaup -utils/juliaup diff --git a/pixi.toml b/pixi.toml index 134454d18..824ed2000 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 default 1.10.0" +install-julia = "juliaup add 1.10.0 && juliaup override unset && juliaup override set 1.10.0" # Julia update-registry-julia = "julia --eval='using Pkg; Registry.update()'" instantiate-julia = "julia --project --eval='using Pkg; Pkg.instantiate()'" @@ -46,11 +46,5 @@ instantiate-wflow-server = "julia --project=server --eval 'using Pkg; Pkg.instan juliaup = "*" python = ">=3.10" -[activation] -scripts = ["utils/env_setup.sh"] - -[target.win-64.activation] -scripts = ["utils/env_setup.bat"] - [system-requirements] linux = "3.10.0" diff --git a/utils/env_setup.bat b/utils/env_setup.bat deleted file mode 100644 index f60008e9e..000000000 --- a/utils/env_setup.bat +++ /dev/null @@ -1 +0,0 @@ -set JULIAUP_DEPOT_PATH=%~dp0 diff --git a/utils/env_setup.sh b/utils/env_setup.sh deleted file mode 100644 index 15e083050..000000000 --- a/utils/env_setup.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -JULIAUP_DEPOT_PATH=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -export JULIAUP_DEPOT_PATH From 5e87215d01c411d4abada5cffc6ba45a9d40903c Mon Sep 17 00:00:00 2001 From: hboisgon Date: Mon, 9 Sep 2024 17:45:53 +0800 Subject: [PATCH 05/42] first commit refactor soil erosion --- src/erosion.jl | 56 +++++++ src/erosion/erosion_process.jl | 204 +++++++++++++++++++++++++ src/erosion/overland_flow_erosion.jl | 118 +++++++++++++++ src/erosion/rainfall_erosion.jl | 218 +++++++++++++++++++++++++++ src/erosion/soil_erosion.jl | 156 +++++++++++++++++++ test/sediment_config_refactor.toml | 213 ++++++++++++++++++++++++++ 6 files changed, 965 insertions(+) create mode 100644 src/erosion.jl create mode 100644 src/erosion/erosion_process.jl create mode 100644 src/erosion/overland_flow_erosion.jl create mode 100644 src/erosion/rainfall_erosion.jl create mode 100644 src/erosion/soil_erosion.jl create mode 100644 test/sediment_config_refactor.toml diff --git a/src/erosion.jl b/src/erosion.jl new file mode 100644 index 000000000..a1dd759e2 --- /dev/null +++ b/src/erosion.jl @@ -0,0 +1,56 @@ +@get_units @with_kw struct SoilErosion{RE, OLE, TE} + rainfall_erosion::RE | "-" + overland_flow_erosion::OLE | "-" + total_erosion::TE | "-" +end + +function initialize_soil_erosion(nc, config, inds) + + # Rainfall erosion + rainfallerosionmodel = get(config.model, "rainfall_erosion", "answers")::String + if rainfallerosionmodel == "answers" + rainfall_erosion_model = initialize_rainfall_erosion_answers(nc, config, inds) + elseif rainfallerosionmodel == "eurosem" + rainfall_erosion_model = initialize_eurosem_rainfall_erosion_model(nc, config, inds) + else + error("Unknown rainfall erosion model: $rainfallerosionmodel") + end + + # Overland flow erosion + overlandflowerosionmodel = get(config.model, "overland_flow_erosion", "answers")::String + if overlandflowerosionmodel == "answers" + overland_flow_erosion_model = + initialize_answers_overland_flow_erosion_model(nc, config, inds) + else + error("Unknown overland flow erosion model: $overlandflowerosionmodel") + # overland_flow_erosion_model = NoOverlandFlowErosionModel() + end + + # Total soil erosion and particle differentiation + total_erosion_model = initialize_soil_erosion_model(nc, config, inds) + + soil_erosion = SoilErosion{ + typeof(rainfall_erosion_model), + typeof(overland_flow_erosion_model), + typeof(total_erosion_model), + }(; + rainfall_erosion = rainfall_erosion_model, + overland_flow_erosion = overland_flow_erosion_model, + total_erosion = total_erosion_model, + ) + return soil_erosion +end + +function update!(model::SoilErosion, area, dt) + #TODO add interception/canopygapfraction calculation here for eurosem + #need SBM refactor + # Rainfall erosion + update!(model.rainfall_erosion, area, dt) + # Overland flow erosion + update!(model.overland_flow_erosion, area, dt) + # Total soil erosion and particle differentiation + (; rainfall_erosion, overland_flow_erosion) = model.total_erosion.boundary_conditions + @. rainfall_erosion = get_rainfall_erosion(model.rainfall_erosion) + @. overland_flow_erosion = get_overland_flow_erosion(model.overland_flow_erosion) + update!(model.total_erosion) +end diff --git a/src/erosion/erosion_process.jl b/src/erosion/erosion_process.jl new file mode 100644 index 000000000..c55bde0e8 --- /dev/null +++ b/src/erosion/erosion_process.jl @@ -0,0 +1,204 @@ +""" + rainfall_erosion_eurosem( + precip, + interception, + waterlevel, + soil_detachability, + eurosem_exponent, + canopyheight, + canopygapfraction, + soilcover_fraction, + area, + dt, + ) + +Rainfall erosion model based on EUROSEM. + +# Arguments +- `precip` (precipitation [mm Δt⁻¹]) +- `interception` (interception [mm Δt⁻¹]) +- `waterlevel` (water level [m]) +- `soil_detachability` (soil detachability [-]) +- `eurosem_exponent` (EUROSEM exponent [-]) +- `canopyheight` (canopy height [m]) +- `canopygapfraction` (canopy gap fraction [-]) +- `soilcover_fraction` (soil cover fraction [-]) +- `area` (area [m2]) +- `dt` (timestep [seconds]) + +# Output +- `rainfall_erosion` (soil loss [t Δt⁻¹]) +""" +function rainfall_erosion_eurosem( + precip, + interception, + waterlevel, + soil_detachability, + eurosem_exponent, + canopyheight, + canopygapfraction, + soilcover_fraction, + area, + dt, +) + # calculate rainfall intensity [mm/h] + rintnsty = precip / (dt / 3600) + # Kinetic energy of direct throughfall [J/m2/mm] + # kedir = max(11.87 + 8.73 * log10(max(0.0001, rintnsty)),0.0) #basis used in USLE + kedir = max(8.95 + 8.44 * log10(max(0.0001, rintnsty)), 0.0) #variant used in most distributed mdoels + # Kinetic energy of leaf drainage [J/m2/mm] + pheff = 0.5 * canopyheight + keleaf = max((15.8 * pheff^0.5) - 5.87, 0.0) + + #Depths of rainfall (total, leaf drianage, direct) [mm] + rdtot = precip + rdleaf = rdtot * 0.1 * canopygapfraction #stemflow + rddir = max(rdtot - rdleaf - interception, 0.0) #throughfall + + #Total kinetic energy by rainfall [J/m2] + ketot = (rddir * kedir + rdleaf * keleaf) * 0.001 + # Rainfall / splash erosion [g/m2] + sedspl = soil_detachability * ketot * exp(-eurosem_exponent * waterlevel) + sedspl = sedspl * area * 1e-6 # ton/cell + + # Remove the impervious area + sedspl = sedspl * (1.0 - soilcover_fraction) + return rainfall_erosion +end + +""" + rainfall_erosion_answers( + precip, + usle_k, + usle_c, + area, + dt, + ) + +Rainfall erosion model based on ANSWERS. + +# Arguments +- `precip` (precipitation [mm Δt⁻¹]) +- `usle_k` (USLE soil erodibility [t ha-1 mm-1]) +- `usle_c` (USLE cover and management factor [-]) +- `soilcover_fraction` (soil cover fraction [-]) +- `area` (area [m2]) +- `dt` (timestep [seconds]) + +# Output +- `rainfall_erosion` (soil loss [t Δt⁻¹]) +""" +function rainfall_erosion_answers(precip, usle_k, usle_c, area, dt) + # calculate rainfall intensity [mm/min] + rintnsty = precip / (ts / 60) + # splash erosion [kg/min] + sedspl = 0.108 * usle_c * usle_k * area * rintnsty^2 + # [ton/timestep] + sedspl = sedspl * (dt / 60) * 1e-3 + return rainfall_erosion +end + +""" + overland_flow_erosion_answers( + overland_flow, + waterlevel, + usle_k, + usle_c, + answers_k, + slope, + soilcover_fraction, + area, + dt, + ) + +Overland flow erosion model based on ANSWERS. + +# Arguments +- `overland_flow` (overland flow [m3 s-1]) +- `waterlevel` (water level [m]) +- `usle_k` (USLE soil erodibility [t ha-1 mm-1]) +- `usle_c` (USLE cover and management factor [-]) +- `answers_k` (ANSWERS overland flow factor [-]) +- `slope` (slope [-]) +- `area` (area [m2]) +- `dt` (timestep [seconds]) + +# Output +- `overland_flow_erosion` (soil loss [t Δt⁻¹]) +""" +function overland_flow_erosion_answers( + overland_flow, + usle_k, + usle_c, + answers_k, + slope, + area, + dt, +) + # Overland flow rate [m2/min] + qr_land = overland_flow * 60 / ((area) / 2) + # Sine of the slope + sinslope = sin(atan(slope)) + + # Overland flow erosion [kg/min] + # For a wide range of slope, it is better to use the sine of slope rather than tangeant + erosion = answers_k * usle_c * usle_k * area * sinslope * qr_land + # [ton/timestep] + erosion = erosion * (dt / 60) * 1e-3 + return erosion +end + +""" + total_soil_erosion( + rainfall_erosion, + overland_flow_erosion, + clay_fraction, + silt_fraction, + sand_fraction, + sagg_fraction, + lagg_fraction, + ) + +Calculate total soil erosion and particle differentiation. + +# Arguments +- `rainfall_erosion` (soil loss from rainfall erosion [t Δt⁻¹]) +- `overland_flow_erosion` (soil loss from overland flow erosion [t Δt⁻¹]) +- `clay_fraction` (clay fraction [-]) +- `silt_fraction` (silt fraction [-]) +- `sand_fraction` (sand fraction [-]) +- `sagg_fraction` (small aggregates fraction [-]) +- `lagg_fraction` (large aggregates fraction [-]) + +# Output +- `soil_erosion` (total soil loss [t Δt⁻¹]) +- `clay_erosion` (clay loss [t Δt⁻¹]) +- `silt_erosion` (silt loss [t Δt⁻¹]) +- `sand_erosion` (sand loss [t Δt⁻¹]) +- `sagg_erosion` (small aggregates loss [t Δt⁻¹]) +- `lagg_erosion` (large aggregates loss [t Δt⁻¹]) +""" +function total_soil_erosion( + rainfall_erosion, + overland_flow_erosion, + clay_fraction, + silt_fraction, + sand_fraction, + sagg_fraction, + lagg_fraction, +) + # Total soil erosion + soil_erosion = rainfall_erosion + overland_flow_erosion + # Particle differentiation + clay_erosion = soil_erosion * clay_fraction + silt_erosion = soil_erosion * silt_fraction + sand_erosion = soil_erosion * sand_fraction + sagg_erosion = soil_erosion * sagg_fraction + lagg_erosion = soil_erosion * lagg_fraction + return soil_erosion, + clay_erosion, + silt_erosion, + sand_erosion, + sagg_erosion, + lagg_erosion +end \ No newline at end of file diff --git a/src/erosion/overland_flow_erosion.jl b/src/erosion/overland_flow_erosion.jl new file mode 100644 index 000000000..237da6474 --- /dev/null +++ b/src/erosion/overland_flow_erosion.jl @@ -0,0 +1,118 @@ +abstract type AbstractOverlandFlowErosionModel end + +struct NoOverlandFlowErosionModel <: AbstractOverlandFlowErosionModel end + +## Overland flow structs and functions +@get_units @with_kw struct OverlandFlowErosionModelVars{T} + # Total soil erosion from overland flow + overland_flow_erosion::Vector{T} | "t dt-1" +end + +function overland_flow_erosion_model_vars(n) + vars = OverlandFlowErosionModelVars(; overland_flow_erosion = fill(mv, n)) + return vars +end + +@get_units @with_kw struct OverlandFlowErosionBC{T} + # Overland flow + overland_flow::Vector{T} | "m dt-1" +end + +function overland_flow_erosion_bc(n) + bc = OverlandFlowErosionBC(; overland_flow = fill(mv, n)) + return bc +end + +# ANSWERS specific structs and functions for rainfall erosion +@get_units @with_kw struct OverlandFlowErosionAnswersParameters{T} + # Soil erodibility factor + usle_k::Vector{T} | "-" + # Crop management factor + usle_c::Vector{T} | "-" + # Answers overland flow factor + answers_k::Vector{T} | "-" + # slope + slope::Vector{T} | "-" +end + +@get_units @with_kw struct OverlandFlowErosionAnswersModel{T} <: + AbstractOverlandFlowErosionModel + boundary_conditions::OverlandFlowErosionBC{T} | "-" + parameters::OverlandFlowErosionAnswersParameters{T} | "-" + variables::OverlandFlowErosionModelVars{T} | "-" +end + +function initialize_answers_params_overland_flow(nc, config, inds) + usle_k = ncread( + nc, + config, + "vertical.soil_erosion.parameters.usle_k"; + sel = inds, + defaults = 0.1, + type = Float, + ) + usle_c = ncread( + nc, + config, + "vertical.soil_erosion.parameters.usle_c"; + sel = inds, + defaults = 0.01, + type = Float, + ) + answers_k = ncread( + nc, + config, + "vertical.soil_erosion.parameters.answers_k"; + sel = inds, + defaults = 0.9, + type = Float, + ) + slope = ncread(nc, config, "vertical.slope"; sel = inds, defaults = 0.01, type = Float) + answers_parameters = OverlandFlowErosionAnswersParameters(; + usle_k = usle_k, + usle_c = usle_c, + answers_k = answers_k, + slope = slope, + ) + return answers_parameters +end + +function initialize_answers_overland_flow_erosion_model(nc, config, inds) + n = length(inds) + vars = overland_flow_erosion_model_vars(n) + params = initialize_answers_params_overland_flow(nc, config, inds) + bc = overland_flow_erosion_bc(n) + model = OverlandFlowErosionAnswersModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::OverlandFlowErosionAnswersModel, area, dt) + (; overland_flow) = model.boundary_conditions + (; usle_k, usle_c, answers_k, slope) = model.parameters + (; overland_flow_erosion) = model.variables + + n = length(overland_flow) + threaded_foreach(1:n; basesize = 1000) do i + overland_flow_erosion[i] = overland_flow_erosion_answers( + overland_flow[i], + usle_k[i], + usle_c[i], + answers_k[i], + slope[i], + area[i], + dt, + ) + end +end + +function update!(model::NoOverlandFlowErosionModel) + return nothing +end + +get_overland_flow_erosion(model::NoOverlandFlowErosionModel) = 0.0 +get_overland_flow_erosion(model::OverlandFlowErosionAnswersModel) = + model.variables.overland_flow_erosion \ No newline at end of file diff --git a/src/erosion/rainfall_erosion.jl b/src/erosion/rainfall_erosion.jl new file mode 100644 index 000000000..674f79fe9 --- /dev/null +++ b/src/erosion/rainfall_erosion.jl @@ -0,0 +1,218 @@ +abstract type AbstractRainfallErosionModel end + +struct NoRainfallErosionModel <: RainfallErosionModel end + +## General rainfall erosion functions and structs +@get_units @with_kw struct RainfallErosionModelVars{T} + # Total soil erosion from rainfall (splash) + rainfall_erosion::Vector{T} | "t dt-1" +end + +function rainfall_erosion_model_vars(n) + vars = RainfallErosionModelVars(; rainfall_erosion = fill(mv, n)) + return vars +end + +@get_units @with_kw struct RainfallErosionBC{T} + # Precipitation + precip::Vector{T} | "mm dt-1" +end + +function rainfall_erosion_bc(n) + bc = RainfallErosionBC(; precip = fill(mv, n)) + return bc +end + +## EUROSEM specific structs and functions for rainfall erosion +@get_units @with_kw struct RainfallErosionEurosemBC{T} + # Precipitation + precip::Vector{T} | "mm dt-1" + # Interception + interception::Vector{T} | "mm dt-1" + # Water level + waterlevel::Vector{T} | "m" +end + +function rainfall_erosion_eurosem_bc(n) + bc = RainfallErosionEurosemBC(; + precip = fill(mv, n), + interception = fill(mv, n), + waterlevel = fill(mv, n), + ) + return bc +end + +@get_units @with_kw struct RainfallErosionEurosemParameters{T} + # Soil detachability factor + soil_detachability::Vector{T} | "g J-1" + # Exponent EUROSEM + eurosem_exponent::Vector{T} | "-" + # Canopy height + canopyheight::Vector{T} | "m" + # Canopy gap fraction + canopygapfraction::Vector{T} | "-" + # Fraction of the soil that is covered (eg paved, snow, etc) + soilcover_fraction::Vector{T} | "-" +end + +@get_units @with_kw struct RainfallErosionEurosemModel{T} <: AbstractRainfallErosionModel + boundary_conditions::RainfallErosionEurosemBC{T} | "-" + parameters::RainfallErosionEurosemParameters{T} | "-" + variables::RainfallErosionModelVars{T} | "-" +end + +function initialize_eurosem_params(nc, config, inds) + n = length(inds) + soil_detachability = ncread( + nc, + config, + "vertical.soil_erosion.parameters.soil_detachability"; + sel = inds, + defaults = 0.6, + type = Float, + ) + eurosem_exponent = ncread( + nc, + config, + "vertical.soil_erosion.parameters.eurosem_exponent"; + sel = inds, + defaults = 2.0, + type = Float, + ) + canopyheight = ncread( + nc, + config, + "vertical.soil_erosion.parameters.canopyheight"; + sel = inds, + defaults = 0.5, + type = Float, + ) + canopygapfraction = ncread( + nc, + config, + "vertical.soil_erosion.parameters.canopygapfraction"; + sel = inds, + defaults = 0.1, + type = Float, + ) + soilcover_fraction = + ncread(nc, config, "vertical.pathfrac"; sel = inds, defaults = 0.01, type = Float) + eurosem_parameters = RainfallErosionEurosemParameters(; + soil_detachability = soil_detachability, + eurosem_exponent = eurosem_exponent, + canopyheight = canopyheight, + canopygapfraction = canopygapfraction, + soilcover_fraction = soilcover_fraction, + ) + return eurosem_parameters +end + +function initialize_eurosem_rainfall_erosion_model(nc, config, inds) + n = length(inds) + vars = rainfall_erosion_model_vars(n) + params = initialize_eurosem_params(nc, config, inds) + bc = rainfall_erosion_eurosem_bc(n) + model = RainfallErosionEurosemModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::RainfallErosionEurosemModel, area, dt) + (; precip, interception, waterlevel) = model.boundary_conditions + (; + soil_detachability, + eurosem_exponent, + canopyheight, + canopygapfraction, + soilcover_fraction, + ) = model.parameters + (; rainfall_erosion) = model.variables + + n = length(precip) + threaded_foreach(1:n; basesize = 1000) do i + rainfall_erosion[i] = rainfall_erosion_eurosem( + precip[i], + interception[i], + waterlevel[i], + soil_detachability[i], + eurosem_exponent[i], + canopyheight[i], + canopygapfraction[i], + soilcover_fraction[i], + area[i], + dt, + ) + end +end + +# ANSWERS specific structs and functions for rainfall erosion +@get_units @with_kw struct RainfallErosionAnswersParameters{T} + # Soil erodibility factor + usle_k::Vector{T} | "-" + # Crop management factor + usle_c::Vector{T} | "-" +end + +@get_units @with_kw struct RainfallErosionAnswersModel{T} <: AbstractRainfallErosionModel + boundary_conditions::RainfallErosionBC{T} | "-" + parameters::RainfallErosionAnswersParameters{T} | "-" + variables::RainfallErosionModelVars{T} | "-" +end + +function initialize_answers_params_rainfall(nc, config, inds) + usle_k = ncread( + nc, + config, + "vertical.soil_erosion.parameters.usle_k"; + sel = inds, + defaults = 0.1, + type = Float, + ) + usle_c = ncread( + nc, + config, + "vertical.soil_erosion.parameters.usle_c"; + sel = inds, + defaults = 0.01, + type = Float, + ) + answers_parameters = + RainfallErosionAnswersParameters(; usle_k = usle_k, usle_c = usle_c) + return answers_parameters +end + +function initialize_answers_rainfall_erosion_model(nc, config, inds) + n = length(inds) + vars = rainfall_erosion_model_vars(n) + params = initialize_answers_params_rainfall(nc, config, inds) + bc = rainfall_erosion_bc(n) + model = RainfallErosionAnswersModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::RainfallErosionAnswersModel, area, dt) + (; precip) = model.boundary_conditions + (; usle_k, usle_c) = model.parameters + (; rainfall_erosion) = model.variables + + n = length(precip) + threaded_foreach(1:n; basesize = 1000) do i + rainfall_erosion[i] = + rainfall_erosion_answers(precip[i], usle_k[i], usle_c[i], area[i], dt) + end +end + +function update!(model::NoRainfallErosionModel) + return nothing +end + +get_rainfall_erosion(model::NoRainfallErosionModel) = 0.0 +get_rainfall_erosion(model::RainfallErosionEurosemModel) = model.variables.rainfall_erosion +get_rainfall_erosion(model::RainfallErosionAnswersModel) = model.variables.rainfall_erosion \ No newline at end of file diff --git a/src/erosion/soil_erosion.jl b/src/erosion/soil_erosion.jl new file mode 100644 index 000000000..9e33aafa9 --- /dev/null +++ b/src/erosion/soil_erosion.jl @@ -0,0 +1,156 @@ +abstract type AbstractSoilErosionModel end + +## Total soil erosion and differentiation structs and functions +@get_units @with_kw struct SoilErosionModelVars{T} + # Total soil erosion + soil_erosion::Vector{T} | "t dt-1" + # Total clay erosion + clay_erosion::Vector{T} | "t dt-1" + # Total silt erosion + silt_erosion::Vector{T} | "t dt-1" + # Total sand erosion + sand_erosion::Vector{T} | "t dt-1" + # Total small aggregates erosion + sagg_erosion::Vector{T} | "t dt-1" + # Total large aggregates erosion + lagg_erosion::Vector{T} | "t dt-1" +end + +function soil_erosion_model_vars(n) + vars = SoilErosionModelVars(; + soil_erosion = fill(mv, n), + clay_erosion = fill(mv, n), + silt_erosion = fill(mv, n), + sand_erosion = fill(mv, n), + sagg_erosion = fill(mv, n), + lagg_erosion = fill(mv, n), + ) + return vars +end + +@get_units @with_kw struct SoilErosionBC{T} + # Rainfall erosion + rainfall_erosion::Vector{T} | "t dt-1" + # Overland flow erosion + overland_flow_erosion::Vector{T} | "m dt-1" +end + +function soil_erosion_bc(n) + bc = + SoilErosionBC(; rainfall_erosion = fill(mv, n), overland_flow_erosion = fill(mv, n)) + return bc +end + +# Parameters for particle differentiation +@get_units @with_kw struct SoilErosionParameters{T} + # Soil content clay + clay_fraction::Vector{T} | "-" + # Soil content silt + silt_fraction::Vector{T} | "-" + # Soil content sand + sand_fraction::Vector{T} | "-" + # Soil content small aggregates + sagg_fraction::Vector{T} | "-" + # Soil content large aggregates + lagg_fraction::Vector{T} | "-" +end + +@get_units @with_kw struct SoilErosionModel{T} <: AbstractSoilErosionModel + boundary_conditions::SoilErosionBC{T} | "-" + parameters::SoilErosionParameters{T} | "-" + variables::SoilErosionModelVars{T} | "-" +end + +function initialize_soil_erosion_params(nc, config, inds) + clay_fraction = ncread( + nc, + config, + "vertical.soil_erosion.parameters.clay_fraction"; + sel = inds, + defaults = 0.4, + type = Float, + ) + silt_fraction = ncread( + nc, + config, + "vertical.soil_erosion.parameters.silt_fraction"; + sel = inds, + defaults = 0.3, + type = Float, + ) + sand_fraction = ncread( + nc, + config, + "vertical.soil_erosion.parameters.sand_fraction"; + sel = inds, + defaults = 0.3, + type = Float, + ) + sagg_fraction = ncread( + nc, + config, + "vertical.soil_erosion.parameters.sagg_fraction"; + sel = inds, + defaults = 0.0, + type = Float, + ) + lagg_fraction = ncread( + nc, + config, + "vertical.soil_erosion.parameters.lagg_fraction"; + sel = inds, + defaults = 0.0, + type = Float, + ) + # Check that soil fractions sum to 1 + soil_fractions = + clay_fraction + silt_fraction + sand_fraction + sagg_fraction + lagg_fraction + if any(abs.(soil_fractions .- 1.0) .> 1e-6) + error("Particle fractions in the soil must sum to 1") + end + soil_parameters = SoilErosionParameters(; + clay_fraction = clay_fraction, + silt_fraction = silt_fraction, + sand_fraction = sand_fraction, + sagg_fraction = sagg_fraction, + lagg_fraction = lagg_fraction, + ) + + return soil_parameters +end + +function initialize_soil_erosion_model(nc, config, inds) + n = length(inds) + vars = soil_erosion_model_vars(n) + params = initialize_soil_erosion_params(nc, config, inds) + bc = soil_erosion_bc(n) + model = + SoilErosionModel(; boundary_conditions = bc, parameters = params, variables = vars) + return model +end + +function update!(model::SoilErosionModel) + (; rainfall_erosion, overland_flow_erosion) = model.boundary_conditions + (; clay_fraction, silt_fraction, sand_fraction, sagg_fraction, lagg_fraction) = + model.parameters + (; soil_erosion, clay_erosion, silt_erosion, sand_erosion, sagg_erosion, lagg_erosion) = + model.variables + + n = length(rainfall_erosion) + threaded_foreach(1:n; basesize = 1000) do i + soil_erosion[i], + clay_erosion[i], + silt_erosion[i], + sand_erosion[i], + sagg_erosion[i], + lagg_erosion[i] = total_soil_erosion( + rainfall_erosion[i], + overland_flow_erosion[i], + clay_fraction[i], + silt_fraction[i], + sand_fraction[i], + sagg_fraction[i], + lagg_fraction[i], + ) + end +end \ No newline at end of file diff --git a/test/sediment_config_refactor.toml b/test/sediment_config_refactor.toml new file mode 100644 index 000000000..1844b208a --- /dev/null +++ b/test/sediment_config_refactor.toml @@ -0,0 +1,213 @@ +# 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-01-03T00:00:00 +starttime = 1999-12-31T00:00:00 +time_units = "days since 1900-01-01 00:00:00" +timestepsecs = 86400 +dir_input = "data/input" +dir_output = "data/output" + +[state] +path_input = "instates-moselle-sed.nc" +path_output = "outstates-moselle-sed.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.lateral.river] +clayload = "clayload" +claystore = "claystore" +gravload = "gravload" +gravstore = "gravstore" +laggload = "laggload" +laggstore = "laggstore" +outclay = "outclay" +outgrav = "outgrav" +outlagg = "outlagg" +outsagg = "outsagg" +outsand = "outsand" +outsilt = "outsilt" +saggload = "saggload" +saggstore = "saggstore" +sandload = "sandload" +sandstore = "sandstore" +siltload = "siltload" +siltstore = "siltstore" + +[input] +path_forcing = "forcing-moselle-sed.nc" +path_static = "staticmaps-moselle-sed.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.h_land", + "vertical.interception", + "vertical.precipitation", + "vertical.q_land", + "lateral.river.h_riv", + "lateral.river.q_riv", +] + +cyclic = ["vertical.leaf_area_index"] + +[input.vertical] +pathfrac = "PathFrac" +slope = "Slope" +## boundary_conditions to be moved +precipitation = "P" +q_land = "runL" +interception = "int" +## interception to be merged with sbm +#kext = "Kext" +#leaf_area_index = "LAI" # cyclic +#specific_leaf = "Sl" +#storage_wood = "Swood" +## Land transport part +h_land = "levKinL" +rivcell = "wflow_river" +# Reservoir +resareas = "wflow_reservoirareas" +# Lake +lakeareas = "wflow_lakeareas" + +[input.vertical.soil_erosion.parameters] +soil_detachability = "ErosK" +eurosem_exponent = "eros_spl_EUROSEM" +canopyheight = "CanopyHeight" +canopygapfraction = "CanopyGapFraction" +usle_k = "USLE_K" +usle_c = "USLE_C" +answers_k = "eros_ov" +clay_fraction = "PercentClay" +silt_fraction = "PercentSilt" +sand_fraction = "PercentSand" +sagg_fraction = "PercentSagg" +lagg_fraction = "PercentLagg" + +[input.lateral.land] +slope = "Slope" + +[input.lateral.river] +h_riv = "h" +q_riv = "q" +cbagnold = "c_Bagnold" +d50 = "D50_River" +d50engelund = "D50_River" +ebagnold = "exp_Bagnold" +fclayriv = "ClayF_River" +fgravriv = "GravelF_River" +fsandriv = "SandF_River" +fsiltriv = "SiltF_River" +length = "wflow_riverlength" +slope = "RiverSlope" +width = "wflow_riverwidth" +# Reservoir +resarea = "ResSimpleArea" +restrapeff = "ResTrapEff" +resareas = "wflow_reservoirareas" +reslocs = "wflow_reservoirlocs" +# Lake +lakearea = "LakeArea" +lakeareas = "wflow_lakeareas" +lakelocs = "wflow_lakelocs" + +[model] +dolake = false +doreservoir = true +landtransportmethod = "yalinpart" # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] +rainerosmethod = "answers" # Rainfall erosion equation: ["answers", "eurosem"] +reinit = true +rivtransportmethod = "bagnold" # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] +runrivermodel = true +type = "sediment" + +[output] +path = "output-moselle-sed.nc" + +[output.vertical] +TCclay = "TCclay" +TCsed = "TCsed" +erosclay = "erosclay" +pathfrac = "pathfrac" +precipitation = "prec" +sedov = "sedov" +sedspl = "sedspl" +soilloss = "soilloss" + +[output.lateral.land] +inlandclay = "inlandclay" +inlandsed = "inlandsed" +olclay = "olclay" +olsed = "olsed" + +[output.lateral.river] +Bedconc = "Bedconc" +SSconc = "SSconc" +Sedconc = "Sedconc" +clayload = "clayload" +h_riv = "h_riv" +inlandclay = "inlandclayriv" +outclay = "outclay" +width = "widthriv" + +[csv] +path = "output-moselle-sediment.csv" + +[[csv.column]] +coordinate.x = 6.931 +coordinate.y = 48.085 +header = "SL" +parameter = "vertical.soilloss" + +[[csv.column]] +coordinate.x = 6.931 +coordinate.y = 48.085 +header = "SSPL" +parameter = "vertical.sedspl" + +[[csv.column]] +coordinate.x = 6.931 +coordinate.y = 48.085 +header = "SOV" +parameter = "vertical.sedov" + +[[csv.column]] +coordinate.x = 6.931 +coordinate.y = 48.085 +header = "P" +parameter = "vertical.precipitation" + +[[csv.column]] +coordinate.x = 6.931 +coordinate.y = 48.085 +header = "ql" +parameter = "vertical.q_land" + +[[csv.column]] +coordinate.x = 6.931 +coordinate.y = 48.085 +header = "TCsed" +parameter = "vertical.TCsed" + +[[csv.column]] +coordinate.x = 6.931 +coordinate.y = 48.085 +header = "TCclay" +parameter = "vertical.TCclay" + +[[csv.column]] +coordinate.x = 6.931 +coordinate.y = 48.085 +header = "inlandsed" +parameter = "lateral.land.inlandsed" From a98b2a4a914dd9fd5dc57896ee49eb019f811497 Mon Sep 17 00:00:00 2001 From: hboisgon Date: Tue, 17 Sep 2024 13:39:50 +0800 Subject: [PATCH 06/42] small updates and bugfixes until test comparison --- src/Wflow.jl | 6 + src/erosion.jl | 46 +++++--- src/erosion/erosion_process.jl | 34 +++--- src/erosion/overland_flow_erosion.jl | 57 ++++------ src/erosion/rainfall_erosion.jl | 97 ++++++++-------- src/erosion/soil_erosion.jl | 36 +++--- src/forcing.jl | 42 +++++++ src/sediment_model.jl | 80 +++++++------- test/run_sediment.jl | 94 ++++++++-------- test/runtests.jl | 26 ++--- test/sediment_config.toml | 159 +++++++++++++++------------ 11 files changed, 364 insertions(+), 313 deletions(-) create mode 100644 src/forcing.jl diff --git a/src/Wflow.jl b/src/Wflow.jl index b09d89b9c..3c27a7e8b 100644 --- a/src/Wflow.jl +++ b/src/Wflow.jl @@ -107,6 +107,7 @@ 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("forcing.jl") include("horizontal_process.jl") include("flow.jl") include("water_demand.jl") @@ -115,6 +116,11 @@ include("sediment.jl") include("reservoir_lake.jl") include("sbm_model.jl") include("sediment_model.jl") +include("erosion.jl") +include("erosion/erosion_process.jl") +include("erosion/rainfall_erosion.jl") +include("erosion/overland_flow_erosion.jl") +include("erosion/soil_erosion.jl") include("vertical_process.jl") include("groundwater/connectivity.jl") include("groundwater/aquifer.jl") diff --git a/src/erosion.jl b/src/erosion.jl index a1dd759e2..8b328bd9f 100644 --- a/src/erosion.jl +++ b/src/erosion.jl @@ -1,15 +1,18 @@ -@get_units @with_kw struct SoilErosion{RE, OLE, TE} +@get_units @with_kw struct SoilLoss{RE, OLE, SE, T} + hydrometeo_forcing::HydrometeoForcing | "-" rainfall_erosion::RE | "-" overland_flow_erosion::OLE | "-" - total_erosion::TE | "-" + soil_erosion::SE | "-" + area::Vector{T} | "m²" end -function initialize_soil_erosion(nc, config, inds) - +function initialize_soil_loss(nc, config, inds, area, slope) + n = length(inds) + hydrometeo_forcing = initialize_hydrometeo_forcing(n) # Rainfall erosion rainfallerosionmodel = get(config.model, "rainfall_erosion", "answers")::String if rainfallerosionmodel == "answers" - rainfall_erosion_model = initialize_rainfall_erosion_answers(nc, config, inds) + rainfall_erosion_model = initialize_answers_rainfall_erosion_model(nc, config, inds) elseif rainfallerosionmodel == "eurosem" rainfall_erosion_model = initialize_eurosem_rainfall_erosion_model(nc, config, inds) else @@ -20,37 +23,44 @@ function initialize_soil_erosion(nc, config, inds) overlandflowerosionmodel = get(config.model, "overland_flow_erosion", "answers")::String if overlandflowerosionmodel == "answers" overland_flow_erosion_model = - initialize_answers_overland_flow_erosion_model(nc, config, inds) + initialize_answers_overland_flow_erosion_model(nc, config, inds, slope) else error("Unknown overland flow erosion model: $overlandflowerosionmodel") # overland_flow_erosion_model = NoOverlandFlowErosionModel() end # Total soil erosion and particle differentiation - total_erosion_model = initialize_soil_erosion_model(nc, config, inds) + soil_erosion_model = initialize_soil_erosion_model(nc, config, inds) - soil_erosion = SoilErosion{ + soil_loss = SoilLoss{ typeof(rainfall_erosion_model), typeof(overland_flow_erosion_model), - typeof(total_erosion_model), + typeof(soil_erosion_model), + Float, }(; + hydrometeo_forcing = hydrometeo_forcing, rainfall_erosion = rainfall_erosion_model, overland_flow_erosion = overland_flow_erosion_model, - total_erosion = total_erosion_model, + soil_erosion = soil_erosion_model, + area = area, ) - return soil_erosion + return soil_loss end -function update!(model::SoilErosion, area, dt) +function update!(model::SoilLoss, dt) + # Convert dt to integer + ts = tosecond(dt) #TODO add interception/canopygapfraction calculation here for eurosem #need SBM refactor # Rainfall erosion - update!(model.rainfall_erosion, area, dt) + update!(model.rainfall_erosion, model.hydrometeo_forcing, model.area, ts) # Overland flow erosion - update!(model.overland_flow_erosion, area, dt) + update!(model.overland_flow_erosion, model.hydrometeo_forcing, model.area, ts) # Total soil erosion and particle differentiation - (; rainfall_erosion, overland_flow_erosion) = model.total_erosion.boundary_conditions - @. rainfall_erosion = get_rainfall_erosion(model.rainfall_erosion) - @. overland_flow_erosion = get_overland_flow_erosion(model.overland_flow_erosion) - update!(model.total_erosion) + re = get_rainfall_erosion(model.rainfall_erosion) + ole = get_overland_flow_erosion(model.overland_flow_erosion) + (; rainfall_erosion, overland_flow_erosion) = model.soil_erosion.boundary_conditions + @. rainfall_erosion = re + @. overland_flow_erosion = ole + update!(model.soil_erosion) end diff --git a/src/erosion/erosion_process.jl b/src/erosion/erosion_process.jl index c55bde0e8..00a3d5415 100644 --- a/src/erosion/erosion_process.jl +++ b/src/erosion/erosion_process.jl @@ -9,7 +9,7 @@ canopygapfraction, soilcover_fraction, area, - dt, + ts, ) Rainfall erosion model based on EUROSEM. @@ -24,7 +24,7 @@ Rainfall erosion model based on EUROSEM. - `canopygapfraction` (canopy gap fraction [-]) - `soilcover_fraction` (soil cover fraction [-]) - `area` (area [m2]) -- `dt` (timestep [seconds]) +- `ts` (timestep [seconds]) # Output - `rainfall_erosion` (soil loss [t Δt⁻¹]) @@ -39,10 +39,10 @@ function rainfall_erosion_eurosem( canopygapfraction, soilcover_fraction, area, - dt, + ts, ) # calculate rainfall intensity [mm/h] - rintnsty = precip / (dt / 3600) + rintnsty = precip / (ts / 3600) # Kinetic energy of direct throughfall [J/m2/mm] # kedir = max(11.87 + 8.73 * log10(max(0.0001, rintnsty)),0.0) #basis used in USLE kedir = max(8.95 + 8.44 * log10(max(0.0001, rintnsty)), 0.0) #variant used in most distributed mdoels @@ -58,11 +58,11 @@ function rainfall_erosion_eurosem( #Total kinetic energy by rainfall [J/m2] ketot = (rddir * kedir + rdleaf * keleaf) * 0.001 # Rainfall / splash erosion [g/m2] - sedspl = soil_detachability * ketot * exp(-eurosem_exponent * waterlevel) - sedspl = sedspl * area * 1e-6 # ton/cell + rainfall_erosion = soil_detachability * ketot * exp(-eurosem_exponent * waterlevel) + rainfall_erosion = rainfall_erosion * area * 1e-6 # ton/cell # Remove the impervious area - sedspl = sedspl * (1.0 - soilcover_fraction) + rainfall_erosion = rainfall_erosion * (1.0 - soilcover_fraction) return rainfall_erosion end @@ -72,7 +72,7 @@ end usle_k, usle_c, area, - dt, + ts, ) Rainfall erosion model based on ANSWERS. @@ -83,18 +83,18 @@ Rainfall erosion model based on ANSWERS. - `usle_c` (USLE cover and management factor [-]) - `soilcover_fraction` (soil cover fraction [-]) - `area` (area [m2]) -- `dt` (timestep [seconds]) +- `ts` (timestep [seconds]) # Output - `rainfall_erosion` (soil loss [t Δt⁻¹]) """ -function rainfall_erosion_answers(precip, usle_k, usle_c, area, dt) +function rainfall_erosion_answers(precip, usle_k, usle_c, area, ts) # calculate rainfall intensity [mm/min] rintnsty = precip / (ts / 60) # splash erosion [kg/min] - sedspl = 0.108 * usle_c * usle_k * area * rintnsty^2 + rainfall_erosion = 0.108 * usle_c * usle_k * area * rintnsty^2 # [ton/timestep] - sedspl = sedspl * (dt / 60) * 1e-3 + rainfall_erosion = rainfall_erosion * (ts / 60) * 1e-3 return rainfall_erosion end @@ -108,7 +108,7 @@ end slope, soilcover_fraction, area, - dt, + ts, ) Overland flow erosion model based on ANSWERS. @@ -121,7 +121,7 @@ Overland flow erosion model based on ANSWERS. - `answers_k` (ANSWERS overland flow factor [-]) - `slope` (slope [-]) - `area` (area [m2]) -- `dt` (timestep [seconds]) +- `ts` (timestep [seconds]) # Output - `overland_flow_erosion` (soil loss [t Δt⁻¹]) @@ -133,10 +133,10 @@ function overland_flow_erosion_answers( answers_k, slope, area, - dt, + ts, ) # Overland flow rate [m2/min] - qr_land = overland_flow * 60 / ((area) / 2) + qr_land = overland_flow * 60 / (area .^ 0.5) # Sine of the slope sinslope = sin(atan(slope)) @@ -144,7 +144,7 @@ function overland_flow_erosion_answers( # For a wide range of slope, it is better to use the sine of slope rather than tangeant erosion = answers_k * usle_c * usle_k * area * sinslope * qr_land # [ton/timestep] - erosion = erosion * (dt / 60) * 1e-3 + erosion = erosion * (ts / 60) * 1e-3 return erosion end diff --git a/src/erosion/overland_flow_erosion.jl b/src/erosion/overland_flow_erosion.jl index 237da6474..43a4da684 100644 --- a/src/erosion/overland_flow_erosion.jl +++ b/src/erosion/overland_flow_erosion.jl @@ -5,24 +5,14 @@ struct NoOverlandFlowErosionModel <: AbstractOverlandFlowErosionModel end ## Overland flow structs and functions @get_units @with_kw struct OverlandFlowErosionModelVars{T} # Total soil erosion from overland flow - overland_flow_erosion::Vector{T} | "t dt-1" + amount::Vector{T} | "t dt-1" end function overland_flow_erosion_model_vars(n) - vars = OverlandFlowErosionModelVars(; overland_flow_erosion = fill(mv, n)) + vars = OverlandFlowErosionModelVars(; amount = fill(mv, n)) return vars end -@get_units @with_kw struct OverlandFlowErosionBC{T} - # Overland flow - overland_flow::Vector{T} | "m dt-1" -end - -function overland_flow_erosion_bc(n) - bc = OverlandFlowErosionBC(; overland_flow = fill(mv, n)) - return bc -end - # ANSWERS specific structs and functions for rainfall erosion @get_units @with_kw struct OverlandFlowErosionAnswersParameters{T} # Soil erodibility factor @@ -37,16 +27,15 @@ end @get_units @with_kw struct OverlandFlowErosionAnswersModel{T} <: AbstractOverlandFlowErosionModel - boundary_conditions::OverlandFlowErosionBC{T} | "-" parameters::OverlandFlowErosionAnswersParameters{T} | "-" variables::OverlandFlowErosionModelVars{T} | "-" end -function initialize_answers_params_overland_flow(nc, config, inds) +function initialize_answers_params_overland_flow(nc, config, inds, slope) usle_k = ncread( nc, config, - "vertical.soil_erosion.parameters.usle_k"; + "vertical.overland_flow_erosion.parameters.usle_k"; sel = inds, defaults = 0.1, type = Float, @@ -54,7 +43,7 @@ function initialize_answers_params_overland_flow(nc, config, inds) usle_c = ncread( nc, config, - "vertical.soil_erosion.parameters.usle_c"; + "vertical.overland_flow_erosion.parameters.usle_c"; sel = inds, defaults = 0.01, type = Float, @@ -62,12 +51,11 @@ function initialize_answers_params_overland_flow(nc, config, inds) answers_k = ncread( nc, config, - "vertical.soil_erosion.parameters.answers_k"; + "vertical.overland_flow_erosion.parameters.answers_k"; sel = inds, defaults = 0.9, type = Float, ) - slope = ncread(nc, config, "vertical.slope"; sel = inds, defaults = 0.01, type = Float) answers_parameters = OverlandFlowErosionAnswersParameters(; usle_k = usle_k, usle_c = usle_c, @@ -77,34 +65,34 @@ function initialize_answers_params_overland_flow(nc, config, inds) return answers_parameters end -function initialize_answers_overland_flow_erosion_model(nc, config, inds) +function initialize_answers_overland_flow_erosion_model(nc, config, inds, slope) n = length(inds) vars = overland_flow_erosion_model_vars(n) - params = initialize_answers_params_overland_flow(nc, config, inds) - bc = overland_flow_erosion_bc(n) - model = OverlandFlowErosionAnswersModel(; - boundary_conditions = bc, - parameters = params, - variables = vars, - ) + params = initialize_answers_params_overland_flow(nc, config, inds, slope) + model = OverlandFlowErosionAnswersModel(; parameters = params, variables = vars) return model end -function update!(model::OverlandFlowErosionAnswersModel, area, dt) - (; overland_flow) = model.boundary_conditions +function update!( + model::OverlandFlowErosionAnswersModel, + hydrometeo_forcing::HydrometeoForcing, + area, + ts, +) + (; q_land) = hydrometeo_forcing (; usle_k, usle_c, answers_k, slope) = model.parameters - (; overland_flow_erosion) = model.variables + (; amount) = model.variables - n = length(overland_flow) + n = length(q_land) threaded_foreach(1:n; basesize = 1000) do i - overland_flow_erosion[i] = overland_flow_erosion_answers( - overland_flow[i], + amount[i] = overland_flow_erosion_answers( + q_land[i], usle_k[i], usle_c[i], answers_k[i], slope[i], area[i], - dt, + ts, ) end end @@ -114,5 +102,4 @@ function update!(model::NoOverlandFlowErosionModel) end get_overland_flow_erosion(model::NoOverlandFlowErosionModel) = 0.0 -get_overland_flow_erosion(model::OverlandFlowErosionAnswersModel) = - model.variables.overland_flow_erosion \ No newline at end of file +get_overland_flow_erosion(model::OverlandFlowErosionAnswersModel) = model.variables.amount \ No newline at end of file diff --git a/src/erosion/rainfall_erosion.jl b/src/erosion/rainfall_erosion.jl index 674f79fe9..b7643aa53 100644 --- a/src/erosion/rainfall_erosion.jl +++ b/src/erosion/rainfall_erosion.jl @@ -1,44 +1,26 @@ abstract type AbstractRainfallErosionModel end -struct NoRainfallErosionModel <: RainfallErosionModel end +struct NoRainfallErosionModel <: AbstractRainfallErosionModel end ## General rainfall erosion functions and structs @get_units @with_kw struct RainfallErosionModelVars{T} # Total soil erosion from rainfall (splash) - rainfall_erosion::Vector{T} | "t dt-1" + amount::Vector{T} | "t dt-1" end function rainfall_erosion_model_vars(n) - vars = RainfallErosionModelVars(; rainfall_erosion = fill(mv, n)) + vars = RainfallErosionModelVars(; amount = fill(mv, n)) return vars end -@get_units @with_kw struct RainfallErosionBC{T} - # Precipitation - precip::Vector{T} | "mm dt-1" -end - -function rainfall_erosion_bc(n) - bc = RainfallErosionBC(; precip = fill(mv, n)) - return bc -end - ## EUROSEM specific structs and functions for rainfall erosion @get_units @with_kw struct RainfallErosionEurosemBC{T} - # Precipitation - precip::Vector{T} | "mm dt-1" # Interception interception::Vector{T} | "mm dt-1" - # Water level - waterlevel::Vector{T} | "m" end function rainfall_erosion_eurosem_bc(n) - bc = RainfallErosionEurosemBC(; - precip = fill(mv, n), - interception = fill(mv, n), - waterlevel = fill(mv, n), - ) + bc = RainfallErosionEurosemBC(; interception = fill(mv, n)) return bc end @@ -62,11 +44,10 @@ end end function initialize_eurosem_params(nc, config, inds) - n = length(inds) soil_detachability = ncread( nc, config, - "vertical.soil_erosion.parameters.soil_detachability"; + "vertical.rainfall_erosion.parameters.soil_detachability"; sel = inds, defaults = 0.6, type = Float, @@ -74,7 +55,7 @@ function initialize_eurosem_params(nc, config, inds) eurosem_exponent = ncread( nc, config, - "vertical.soil_erosion.parameters.eurosem_exponent"; + "vertical.rainfall_erosion.parameters.eurosem_exponent"; sel = inds, defaults = 2.0, type = Float, @@ -82,7 +63,7 @@ function initialize_eurosem_params(nc, config, inds) canopyheight = ncread( nc, config, - "vertical.soil_erosion.parameters.canopyheight"; + "vertical.rainfall_erosion.parameters.canopyheight"; sel = inds, defaults = 0.5, type = Float, @@ -90,13 +71,19 @@ function initialize_eurosem_params(nc, config, inds) canopygapfraction = ncread( nc, config, - "vertical.soil_erosion.parameters.canopygapfraction"; + "vertical.rainfall_erosion.parameters.canopygapfraction"; sel = inds, defaults = 0.1, type = Float, ) - soilcover_fraction = - ncread(nc, config, "vertical.pathfrac"; sel = inds, defaults = 0.01, type = Float) + soilcover_fraction = ncread( + nc, + config, + "vertical.rainfall_erosion.parameters.pathfrac"; + sel = inds, + defaults = 0.01, + type = Float, + ) eurosem_parameters = RainfallErosionEurosemParameters(; soil_detachability = soil_detachability, eurosem_exponent = eurosem_exponent, @@ -120,8 +107,14 @@ function initialize_eurosem_rainfall_erosion_model(nc, config, inds) return model end -function update!(model::RainfallErosionEurosemModel, area, dt) - (; precip, interception, waterlevel) = model.boundary_conditions +function update!( + model::RainfallErosionEurosemModel, + hydrometeo_forcing::HydrometeoForcing, + area, + ts, +) + (; precipitation, waterlevel_land) = hydrometeo_forcing + (; interception) = model.boundary_conditions (; soil_detachability, eurosem_exponent, @@ -129,21 +122,21 @@ function update!(model::RainfallErosionEurosemModel, area, dt) canopygapfraction, soilcover_fraction, ) = model.parameters - (; rainfall_erosion) = model.variables + (; amount) = model.variables n = length(precip) threaded_foreach(1:n; basesize = 1000) do i - rainfall_erosion[i] = rainfall_erosion_eurosem( - precip[i], + amount[i] = rainfall_erosion_eurosem( + precipitation[i], interception[i], - waterlevel[i], + waterlevel_land[i], soil_detachability[i], eurosem_exponent[i], canopyheight[i], canopygapfraction[i], soilcover_fraction[i], area[i], - dt, + ts, ) end end @@ -157,7 +150,6 @@ end end @get_units @with_kw struct RainfallErosionAnswersModel{T} <: AbstractRainfallErosionModel - boundary_conditions::RainfallErosionBC{T} | "-" parameters::RainfallErosionAnswersParameters{T} | "-" variables::RainfallErosionModelVars{T} | "-" end @@ -166,7 +158,7 @@ function initialize_answers_params_rainfall(nc, config, inds) usle_k = ncread( nc, config, - "vertical.soil_erosion.parameters.usle_k"; + "vertical.rainfall_erosion.parameters.usle_k"; sel = inds, defaults = 0.1, type = Float, @@ -174,7 +166,7 @@ function initialize_answers_params_rainfall(nc, config, inds) usle_c = ncread( nc, config, - "vertical.soil_erosion.parameters.usle_c"; + "vertical.rainfall_erosion.parameters.usle_c"; sel = inds, defaults = 0.01, type = Float, @@ -188,24 +180,24 @@ function initialize_answers_rainfall_erosion_model(nc, config, inds) n = length(inds) vars = rainfall_erosion_model_vars(n) params = initialize_answers_params_rainfall(nc, config, inds) - bc = rainfall_erosion_bc(n) - model = RainfallErosionAnswersModel(; - boundary_conditions = bc, - parameters = params, - variables = vars, - ) + model = RainfallErosionAnswersModel(; parameters = params, variables = vars) return model end -function update!(model::RainfallErosionAnswersModel, area, dt) - (; precip) = model.boundary_conditions +function update!( + model::RainfallErosionAnswersModel, + hydrometeo_forcing::HydrometeoForcing, + area, + ts, +) + (; precipitation) = hydrometeo_forcing (; usle_k, usle_c) = model.parameters - (; rainfall_erosion) = model.variables + (; amount) = model.variables - n = length(precip) + n = length(precipitation) threaded_foreach(1:n; basesize = 1000) do i - rainfall_erosion[i] = - rainfall_erosion_answers(precip[i], usle_k[i], usle_c[i], area[i], dt) + amount[i] = + rainfall_erosion_answers(precipitation[i], usle_k[i], usle_c[i], area[i], ts) end end @@ -214,5 +206,4 @@ function update!(model::NoRainfallErosionModel) end get_rainfall_erosion(model::NoRainfallErosionModel) = 0.0 -get_rainfall_erosion(model::RainfallErosionEurosemModel) = model.variables.rainfall_erosion -get_rainfall_erosion(model::RainfallErosionAnswersModel) = model.variables.rainfall_erosion \ No newline at end of file +get_rainfall_erosion(model::AbstractRainfallErosionModel) = model.variables.amount \ No newline at end of file diff --git a/src/erosion/soil_erosion.jl b/src/erosion/soil_erosion.jl index 9e33aafa9..affd4caf0 100644 --- a/src/erosion/soil_erosion.jl +++ b/src/erosion/soil_erosion.jl @@ -3,27 +3,27 @@ abstract type AbstractSoilErosionModel end ## Total soil erosion and differentiation structs and functions @get_units @with_kw struct SoilErosionModelVars{T} # Total soil erosion - soil_erosion::Vector{T} | "t dt-1" + amount::Vector{T} | "t dt-1" # Total clay erosion - clay_erosion::Vector{T} | "t dt-1" + clay::Vector{T} | "t dt-1" # Total silt erosion - silt_erosion::Vector{T} | "t dt-1" + silt::Vector{T} | "t dt-1" # Total sand erosion - sand_erosion::Vector{T} | "t dt-1" + sand::Vector{T} | "t dt-1" # Total small aggregates erosion - sagg_erosion::Vector{T} | "t dt-1" + sagg::Vector{T} | "t dt-1" # Total large aggregates erosion - lagg_erosion::Vector{T} | "t dt-1" + lagg::Vector{T} | "t dt-1" end function soil_erosion_model_vars(n) vars = SoilErosionModelVars(; - soil_erosion = fill(mv, n), - clay_erosion = fill(mv, n), - silt_erosion = fill(mv, n), - sand_erosion = fill(mv, n), - sagg_erosion = fill(mv, n), - lagg_erosion = fill(mv, n), + amount = fill(mv, n), + clay = fill(mv, n), + silt = fill(mv, n), + sand = fill(mv, n), + sagg = fill(mv, n), + lagg = fill(mv, n), ) return vars end @@ -105,7 +105,7 @@ function initialize_soil_erosion_params(nc, config, inds) # Check that soil fractions sum to 1 soil_fractions = clay_fraction + silt_fraction + sand_fraction + sagg_fraction + lagg_fraction - if any(abs.(soil_fractions .- 1.0) .> 1e-6) + if any(abs.(soil_fractions .- 1.0) .> 1e-3) error("Particle fractions in the soil must sum to 1") end soil_parameters = SoilErosionParameters(; @@ -133,17 +133,11 @@ function update!(model::SoilErosionModel) (; rainfall_erosion, overland_flow_erosion) = model.boundary_conditions (; clay_fraction, silt_fraction, sand_fraction, sagg_fraction, lagg_fraction) = model.parameters - (; soil_erosion, clay_erosion, silt_erosion, sand_erosion, sagg_erosion, lagg_erosion) = - model.variables + (; amount, clay, silt, sand, sagg, lagg) = model.variables n = length(rainfall_erosion) threaded_foreach(1:n; basesize = 1000) do i - soil_erosion[i], - clay_erosion[i], - silt_erosion[i], - sand_erosion[i], - sagg_erosion[i], - lagg_erosion[i] = total_soil_erosion( + amount[i], clay[i], silt[i], sand[i], sagg[i], lagg[i] = total_soil_erosion( rainfall_erosion[i], overland_flow_erosion[i], clay_fraction[i], diff --git a/src/forcing.jl b/src/forcing.jl new file mode 100644 index 000000000..c48ea7142 --- /dev/null +++ b/src/forcing.jl @@ -0,0 +1,42 @@ + +@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 + +function initialize_atmospheric_forcing(n) + atmospheric_forcing = AtmosphericForcing(; + precipitation = fill(mv, n), + potential_evaporation = fill(mv, n), + temperature = fill(mv, n), + ) + return atmospheric_forcing +end + +@get_units @with_kw struct HydrometeoForcing{T} + # Precipitation [mm Δt⁻¹] + precipitation::Vector{T} + # Overland flow depth [m] + waterlevel_land::Vector{T} | "m" + # Overland flow discharge [m3 s-1] + q_land::Vector{T} | "m3 s-1" + # River depth [m] + waterlevel_river::Vector{T} | "m" + # River discharge [m3 s-1] + q_river::Vector{T} | "m3 s-1" +end + +function initialize_hydrometeo_forcing(n) + hydrometeo_forcing = HydrometeoForcing(; + precipitation = fill(mv, n), + waterlevel_land = fill(mv, n), + q_land = fill(mv, n), + waterlevel_river = fill(mv, n), + q_river = fill(mv, n), + ) + return hydrometeo_forcing +end \ No newline at end of file diff --git a/src/sediment_model.jl b/src/sediment_model.jl index e9a054526..c1f6203b9 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -13,18 +13,15 @@ function initialize_sediment_model(config::Config) reader = prepare_reader(config) clock = Clock(config, reader) - dt = clock.dt do_river = get(config.model, "runrivermodel", false)::Bool nc = NCDataset(static_path) - dims = dimnames(nc[param(config, "input.subcatchment")]) 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) - modelsize_2d = size(subcatch_2d) river_2d = ncread(nc, config, "river_location"; optional = false, type = Bool, fill = false) @@ -37,7 +34,6 @@ function initialize_sediment_model(config::Config) riverlength = riverlength_2d[inds] inds_riv, rev_inds_riv = active_indices(river_2d, 0) - nriv = length(inds_riv) # Needed to update the forcing reservoir = () @@ -52,8 +48,12 @@ function initialize_sediment_model(config::Config) sizeinmetres = get(config.model, "sizeinmetres", false)::Bool xl, yl = cell_lengths(y, cellength, sizeinmetres) riverfrac = river_fraction(river, riverlength, riverwidth, xl, yl) + area = xl .* yl + landslope = + ncread(nc, config, "vertical.slope"; optional = false, sel = inds, type = Float) + clamp!(landslope, 0.00001, Inf) - eros = initialize_landsed(nc, config, river, riverfrac, xl, yl, inds) + soilloss = initialize_soil_loss(nc, config, inds, area, landslope) ldd_2d = ncread(nc, config, "ldd"; optional = false, allow_missing = true) ldd = ldd_2d[inds] @@ -111,7 +111,7 @@ function initialize_sediment_model(config::Config) rs = initialize_riversed(nc, config, riverwidth, riverlength, inds_riv) - modelmap = (vertical = eros, lateral = (land = ols, river = rs)) + modelmap = (vertical = soilloss, lateral = (land = ols, river = rs)) indices_reverse = ( land = rev_inds, river = rev_inds_riv, @@ -140,7 +140,7 @@ function initialize_sediment_model(config::Config) config, (; land, river, reservoir, lake, index_river, frac_toriver), (land = ols, river = rs), - eros, + soilloss, clock, reader, writer, @@ -154,39 +154,41 @@ 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 - - update_until_ols(vertical, config) - update_until_oltransport(vertical, config) - - lateral.land.soilloss .= vertical.soilloss - lateral.land.erosclay .= vertical.erosclay - lateral.land.erossilt .= vertical.erossilt - lateral.land.erossand .= vertical.erossand - lateral.land.erossagg .= vertical.erossagg - lateral.land.eroslagg .= vertical.eroslagg - - lateral.land.TCsed .= vertical.TCsed - lateral.land.TCclay .= vertical.TCclay - lateral.land.TCsilt .= vertical.TCsilt - lateral.land.TCsand .= vertical.TCsand - lateral.land.TCsagg .= vertical.TCsagg - lateral.land.TClagg .= vertical.TClagg - - update(lateral.land, network.land, config) - - do_river = get(config.model, "runrivermodel", false)::Bool - - if do_river - inds_riv = network.index_river - lateral.river.inlandclay .= lateral.land.inlandclay[inds_riv] - lateral.river.inlandsilt .= lateral.land.inlandsilt[inds_riv] - lateral.river.inlandsand .= lateral.land.inlandsand[inds_riv] - lateral.river.inlandsagg .= lateral.land.inlandsagg[inds_riv] - lateral.river.inlandlagg .= lateral.land.inlandlagg[inds_riv] + (; lateral, vertical, network, config, clock) = model + dt = clock.dt - update(lateral.river, network.river, config) - end + # Soil erosion + update!(vertical, dt) + # update_until_oltransport(vertical, config) + + # lateral.land.soilloss .= vertical.soilloss + # lateral.land.erosclay .= vertical.erosclay + # lateral.land.erossilt .= vertical.erossilt + # lateral.land.erossand .= vertical.erossand + # lateral.land.erossagg .= vertical.erossagg + # lateral.land.eroslagg .= vertical.eroslagg + + # lateral.land.TCsed .= vertical.TCsed + # lateral.land.TCclay .= vertical.TCclay + # lateral.land.TCsilt .= vertical.TCsilt + # lateral.land.TCsand .= vertical.TCsand + # lateral.land.TCsagg .= vertical.TCsagg + # lateral.land.TClagg .= vertical.TClagg + + # update(lateral.land, network.land, config) + + # do_river = get(config.model, "runrivermodel", false)::Bool + + # if do_river + # inds_riv = network.index_river + # lateral.river.inlandclay .= lateral.land.inlandclay[inds_riv] + # lateral.river.inlandsilt .= lateral.land.inlandsilt[inds_riv] + # lateral.river.inlandsand .= lateral.land.inlandsand[inds_riv] + # lateral.river.inlandsagg .= lateral.land.inlandsagg[inds_riv] + # lateral.river.inlandlagg .= lateral.land.inlandlagg[inds_riv] + + # update(lateral.river, network.river, config) + # end return model end diff --git a/test/run_sediment.jl b/test/run_sediment.jl index 1d44f79c5..2f5ab3285 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -11,60 +11,62 @@ model = Wflow.run_timestep(model) @testset "first timestep sediment model (vertical)" begin eros = model.vertical - @test eros.erosov[1] ≈ 0.9f0 + @test eros.hydrometeo_forcing.q_land[1] ≈ 0.0f0 + @test eros.overland_flow_erosion.parameters.usle_k[1] ≈ 0.026510488241910934 + @test eros.overland_flow_erosion.variables.amount[1] ≈ 0.9f0 @test model.clock.iteration == 1 - @test mean(eros.leaf_area_index) ≈ 1.7120018886212223f0 - @test eros.dmsand[1] == 200.0f0 - @test eros.dmlagg[1] == 500.0f0 - @test mean(eros.interception) ≈ 0.4767846753916875f0 - @test mean(eros.soilloss) ≈ 0.008596682196335555f0 + #@test mean(eros.leaf_area_index) ≈ 1.7120018886212223f0 + #@test eros.dmsand[1] == 200.0f0 + #@test eros.dmlagg[1] == 500.0f0 + #@test mean(eros.interception) ≈ 0.4767846753916875f0 + @test mean(eros.soil_erosion.variables.amount) ≈ 0.008596682196335555f0 end -# run the second timestep -model = Wflow.run_timestep(model) +# # run the second timestep +# model = Wflow.run_timestep(model) -@testset "second timestep sediment model (vertical)" begin - eros = model.vertical +# @testset "second timestep sediment model (vertical)" begin +# eros = model.vertical - @test mean(eros.soilloss) ≈ 0.07601393657280235f0 - @test mean(eros.erosclay) ≈ 0.0022388720384961766f0 - @test mean(eros.erossand) ≈ 0.02572046244407882f0 - @test mean(eros.eroslagg) ≈ 0.022126541806118796f0 - @test mean(eros.TCsed) == 0.0 - @test mean(eros.TCsilt) ≈ 1.0988158364353527f6 - @test mean(eros.TCsand) ≈ 1.0987090622888755f6 - @test mean(eros.TCclay) ≈ 1.0992655197016734f6 - @test mean(eros.TCsilt) ≈ 1.0988158364353527f6 -end +# @test mean(eros.soil_erosion.variables.amount) ≈ 0.07601393657280235f0 +# @test mean(eros.soil_erosion.variables.clay) ≈ 0.0022388720384961766f0 +# @test mean(eros.soil_erosion.variables.sand) ≈ 0.02572046244407882f0 +# @test mean(eros.soil_erosion.variables.lagg) ≈ 0.022126541806118796f0 +# #@test mean(eros.TCsed) == 0.0 +# #@test mean(eros.TCsilt) ≈ 1.0988158364353527f6 +# #@test mean(eros.TCsand) ≈ 1.0987090622888755f6 +# #@test mean(eros.TCclay) ≈ 1.0992655197016734f6 +# #@test mean(eros.TCsilt) ≈ 1.0988158364353527f6 +# end -@testset "second timestep sediment model (lateral)" begin - lat = model.lateral +# @testset "second timestep sediment model (lateral)" begin +# lat = model.lateral - @test mean(lat.land.inlandsed) ≈ 0.07463801685030906f0 - @test mean(lat.land.inlandclay) ≈ 0.0022367786781657497f0 - @test mean(lat.land.inlandsand) ≈ 0.02519222037812127f0 - @test mean(lat.land.olclay) ≈ 0.006443036462118322f0 +# @test mean(lat.land.inlandsed) ≈ 0.07463801685030906f0 +# @test mean(lat.land.inlandclay) ≈ 0.0022367786781657497f0 +# @test mean(lat.land.inlandsand) ≈ 0.02519222037812127f0 +# @test mean(lat.land.olclay) ≈ 0.006443036462118322f0 - @test mean(lat.river.SSconc) ≈ 0.8259993252994058f0 - @test mean(lat.river.inlandclay) ≈ 0.01980468760667709f0 - @test lat.river.h_riv[network.river.order[end]] ≈ 0.006103649735450745f0 - @test lat.river.outclay[5649] ≈ 2.359031898208781f-9 -end +# @test mean(lat.river.SSconc) ≈ 0.8259993252994058f0 +# @test mean(lat.river.inlandclay) ≈ 0.01980468760667709f0 +# @test lat.river.h_riv[network.river.order[end]] ≈ 0.006103649735450745f0 +# @test lat.river.outclay[5649] ≈ 2.359031898208781f-9 +# end -@testset "Exchange and grid location sediment" begin - @test Wflow.exchange(model.vertical, :n) == 0 - @test Wflow.exchange(model.vertical, :erosk) == 1 - @test Wflow.exchange(model.vertical, :leaf_area_index) == 1 - @test Wflow.grid_location(model.vertical, :n) == "none" - @test Wflow.grid_location(model.vertical, :erosk) == "node" - @test Wflow.grid_location(model.vertical, :leaf_area_index) == "node" - land = model.lateral.land - @test Wflow.exchange(land, :n) == 0 - @test Wflow.exchange(land, :soilloss) == 1 - @test Wflow.exchange(land, :inlandsed) == 1 - @test Wflow.grid_location(land, :n) == "none" - @test Wflow.grid_location(land, :soilloss) == "node" - @test Wflow.grid_location(land, :inlandsed) == "node" -end +# @testset "Exchange and grid location sediment" begin +# @test Wflow.exchange(model.vertical, :n) == 0 +# @test Wflow.exchange(model.vertical, :erosk) == 1 +# @test Wflow.exchange(model.vertical, :leaf_area_index) == 1 +# @test Wflow.grid_location(model.vertical, :n) == "none" +# @test Wflow.grid_location(model.vertical, :erosk) == "node" +# @test Wflow.grid_location(model.vertical, :leaf_area_index) == "node" +# land = model.lateral.land +# @test Wflow.exchange(land, :n) == 0 +# @test Wflow.exchange(land, :soilloss) == 1 +# @test Wflow.exchange(land, :inlandsed) == 1 +# @test Wflow.grid_location(land, :n) == "none" +# @test Wflow.grid_location(land, :soilloss) == "node" +# @test Wflow.grid_location(land, :inlandsed) == "node" +# end Wflow.close_files(model) diff --git a/test/runtests.jl b/test/runtests.jl index cd8782d7e..d42ca804f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -79,20 +79,20 @@ include("testing_utils.jl") with_logger(NullLogger()) do ## run all tests @testset "Wflow.jl" begin - include("horizontal_process.jl") - include("io.jl") - include("vertical_process.jl") - include("reservoir_lake.jl") - include("run_sbm.jl") - include("run_sbm_piave.jl") - include("run_sbm_gwf_piave.jl") - include("run_sbm_gwf.jl") - include("run.jl") - include("groundwater.jl") - include("utils.jl") - include("bmi.jl") + #include("horizontal_process.jl") + #include("io.jl") + #include("vertical_process.jl") + #include("reservoir_lake.jl") + #include("run_sbm.jl") + #include("run_sbm_piave.jl") + #include("run_sbm_gwf_piave.jl") + #include("run_sbm_gwf.jl") + #include("run.jl") + #include("groundwater.jl") + #include("utils.jl") + #include("bmi.jl") include("run_sediment.jl") - include("subdomains.jl") + #include("subdomains.jl") Aqua.test_all(Wflow; ambiguities = false) end diff --git a/test/sediment_config.toml b/test/sediment_config.toml index 23c9fd4b5..946f0786f 100644 --- a/test/sediment_config.toml +++ b/test/sediment_config.toml @@ -51,42 +51,57 @@ 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.h_land", - "vertical.interception", - "vertical.precipitation", - "vertical.q_land", + "vertical.hydrometeo_forcing.waterlevel_land", + #"vertical.interception", + "vertical.hydrometeo_forcing.precipitation", + "vertical.hydrometeo_forcing.q_land", "lateral.river.h_riv", "lateral.river.q_riv", ] -cyclic = ["vertical.leaf_area_index"] +#cyclic = ["vertical.leaf_area_index"] [input.vertical] -altitude = "wflow_dem" -canopyheight = "CanopyHeight" -erosk = "ErosK" -erosov = "eros_ov" -erosspl = "eros_spl_EUROSEM" -h_land = "levKinL" -interception = "int" -kext = "Kext" -leaf_area_index = "LAI" # cyclic +## interception parameters to be moved +#kext = "Kext" +#leaf_area_index = "LAI" # cyclic +#specific_leaf = "Sl" +#storage_wood = "Swood" +## other parameters pathfrac = "PathFrac" -pclay = "PercentClay" -precipitation = "P" -psilt = "PercentSilt" -q_land = "runL" rivcell = "wflow_river" slope = "Slope" -specific_leaf = "Sl" -storage_wood = "Swood" -usleC = "USLE_C" -usleK = "USLE_K" # Reservoir resareas = "wflow_reservoirareas" # Lake lakeareas = "wflow_lakeareas" +[input.vertical.hydrometeo_forcing] +precipitation = "P" +waterlevel_land = "levKinL" +#interception = "int" +q_land = "runL" + +[input.vertical.rainfall_erosion.parameters] +soil_detachability = "soil_detachability" +eurosem_exponent = "eros_spl_EUROSEM" +canopyheight = "CanopyHeight" +canopygapfraction = "CanopyGapFraction" +usle_k = "usle_k" +usle_c = "USLE_C" + +[input.vertical.overland_flow_erosion.parameters] +usle_k = "usle_k" +usle_c = "USLE_C" +answers_k = "eros_ov" + +[input.vertical.soil_erosion.parameters] +clay_fraction = "fclay_soil" +silt_fraction = "fsilt_soil" +sand_fraction = "fsand_soil" +sagg_fraction = "fsagg_soil" +lagg_fraction = "flagg_soil" + [input.lateral.land] slope = "Slope" @@ -121,37 +136,39 @@ landtransportmethod = "yalinpart" # Overland flow transport capacity method: ["y rainerosmethod = "answers" # Rainfall erosion equation: ["answers", "eurosem"] reinit = true rivtransportmethod = "bagnold" # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] -runrivermodel = true +runrivermodel = false type = "sediment" [output] path = "output-moselle-sed.nc" -[output.vertical] -TCclay = "TCclay" -TCsed = "TCsed" -erosclay = "erosclay" -pathfrac = "pathfrac" -precipitation = "prec" -sedov = "sedov" -sedspl = "sedspl" -soilloss = "soilloss" - -[output.lateral.land] -inlandclay = "inlandclay" -inlandsed = "inlandsed" -olclay = "olclay" -olsed = "olsed" - -[output.lateral.river] -Bedconc = "Bedconc" -SSconc = "SSconc" -Sedconc = "Sedconc" -clayload = "clayload" -h_riv = "h_riv" -inlandclay = "inlandclayriv" -outclay = "outclay" -width = "widthriv" +[output.vertical.rainfall_erosion.variables] +amount = "rainfall_erosion" + +[output.vertical.overland_flow_erosion.variables] +amount = "overland_flow_erosion" + +[output.vertical.soil_erosion.variables] +amount = "soilloss" +clay = "erosclay" + +# [output.lateral.land] +# #TCclay = "TCclay" +# #TCsed = "TCsed" +# inlandclay = "inlandclay" +# inlandsed = "inlandsed" +# olclay = "olclay" +# olsed = "olsed" + +# [output.lateral.river] +# Bedconc = "Bedconc" +# SSconc = "SSconc" +# Sedconc = "Sedconc" +# clayload = "clayload" +# h_riv = "h_riv" +# inlandclay = "inlandclayriv" +# outclay = "outclay" +# width = "widthriv" [csv] path = "output-moselle-sediment.csv" @@ -160,46 +177,46 @@ path = "output-moselle-sediment.csv" coordinate.x = 6.931 coordinate.y = 48.085 header = "SL" -parameter = "vertical.soilloss" +parameter = "vertical.soil_erosion.variables.amount" [[csv.column]] coordinate.x = 6.931 coordinate.y = 48.085 header = "SSPL" -parameter = "vertical.sedspl" +parameter = "vertical.rainfall_erosion.variables.amount" [[csv.column]] coordinate.x = 6.931 coordinate.y = 48.085 header = "SOV" -parameter = "vertical.sedov" +parameter = "vertical.overland_flow_erosion.variables.amount" [[csv.column]] coordinate.x = 6.931 coordinate.y = 48.085 header = "P" -parameter = "vertical.precipitation" +parameter = "vertical.hydrometeo_forcing.precipitation" [[csv.column]] coordinate.x = 6.931 coordinate.y = 48.085 header = "ql" -parameter = "vertical.q_land" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "TCsed" -parameter = "vertical.TCsed" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "TCclay" -parameter = "vertical.TCclay" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "inlandsed" -parameter = "lateral.land.inlandsed" +parameter = "vertical.hydrometeo_forcing.q_land" + +# [[csv.column]] +# coordinate.x = 6.931 +# coordinate.y = 48.085 +# header = "TCsed" +# parameter = "vertical.TCsed" + +# [[csv.column]] +# coordinate.x = 6.931 +# coordinate.y = 48.085 +# header = "TCclay" +# parameter = "vertical.TCclay" + +# [[csv.column]] +# coordinate.x = 6.931 +# coordinate.y = 48.085 +# header = "inlandsed" +# parameter = "lateral.land.inlandsed" From 02883418a571effc3d6d5366bc8433d3fc9e9e44 Mon Sep 17 00:00:00 2001 From: hboisgon Date: Tue, 17 Sep 2024 14:41:53 +0800 Subject: [PATCH 07/42] Working tests for soil erosion --- test/run_sediment.jl | 42 +++++++++++++++++++++++++----------------- test/runtests.jl | 2 +- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/test/run_sediment.jl b/test/run_sediment.jl index 2f5ab3285..16c60593e 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -11,33 +11,41 @@ model = Wflow.run_timestep(model) @testset "first timestep sediment model (vertical)" begin eros = model.vertical + @test eros.hydrometeo_forcing.precipitation[1] ≈ 4.086122035980225f0 @test eros.hydrometeo_forcing.q_land[1] ≈ 0.0f0 - @test eros.overland_flow_erosion.parameters.usle_k[1] ≈ 0.026510488241910934 - @test eros.overland_flow_erosion.variables.amount[1] ≈ 0.9f0 + @test eros.overland_flow_erosion.parameters.usle_k[1] ≈ 0.026510488241910934f0 + @test eros.overland_flow_erosion.parameters.usle_c[1] ≈ 0.014194443821907043f0 + @test eros.overland_flow_erosion.parameters.answers_k[1] ≈ 0.9f0 + @test eros.overland_flow_erosion.variables.amount[1] ≈ 0.0f0 + @test eros.rainfall_erosion.variables.amount[1] ≈ 0.00027245577922893746f0 @test model.clock.iteration == 1 #@test mean(eros.leaf_area_index) ≈ 1.7120018886212223f0 #@test eros.dmsand[1] == 200.0f0 #@test eros.dmlagg[1] == 500.0f0 #@test mean(eros.interception) ≈ 0.4767846753916875f0 - @test mean(eros.soil_erosion.variables.amount) ≈ 0.008596682196335555f0 + @test mean(eros.overland_flow_erosion.variables.amount) ≈ 0.00861079076689589f0 + @test mean(eros.rainfall_erosion.variables.amount) ≈ 0.00016326203201620437f0 + @test mean(eros.soil_erosion.variables.amount) ≈ 0.008774052798912092f0 end -# # run the second timestep -# model = Wflow.run_timestep(model) +# run the second timestep +model = Wflow.run_timestep(model) -# @testset "second timestep sediment model (vertical)" begin -# eros = model.vertical +@testset "second timestep sediment model (vertical)" begin + eros = model.vertical -# @test mean(eros.soil_erosion.variables.amount) ≈ 0.07601393657280235f0 -# @test mean(eros.soil_erosion.variables.clay) ≈ 0.0022388720384961766f0 -# @test mean(eros.soil_erosion.variables.sand) ≈ 0.02572046244407882f0 -# @test mean(eros.soil_erosion.variables.lagg) ≈ 0.022126541806118796f0 -# #@test mean(eros.TCsed) == 0.0 -# #@test mean(eros.TCsilt) ≈ 1.0988158364353527f6 -# #@test mean(eros.TCsand) ≈ 1.0987090622888755f6 -# #@test mean(eros.TCclay) ≈ 1.0992655197016734f6 -# #@test mean(eros.TCsilt) ≈ 1.0988158364353527f6 -# end + @test mean(eros.soil_erosion.variables.amount) ≈ 0.07765800489746684f0 + @test mean(eros.soil_erosion.variables.clay) ≈ 0.002287480354866626f0 + @test mean(eros.soil_erosion.variables.silt) ≈ 0.0036164773521184896f0 + @test mean(eros.soil_erosion.variables.sand) ≈ 0.026301393837924607f0 + @test mean(eros.soil_erosion.variables.lagg) ≈ 0.022577957752547836f0 + @test mean(eros.soil_erosion.variables.sagg) ≈ 0.022874695590802723f0 + #@test mean(eros.TCsed) == 0.0 + #@test mean(eros.TCsilt) ≈ 1.0988158364353527f6 + #@test mean(eros.TCsand) ≈ 1.0987090622888755f6 + #@test mean(eros.TCclay) ≈ 1.0992655197016734f6 + #@test mean(eros.TCsilt) ≈ 1.0988158364353527f6 +end # @testset "second timestep sediment model (lateral)" begin # lat = model.lateral diff --git a/test/runtests.jl b/test/runtests.jl index d42ca804f..e55b1658b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -94,6 +94,6 @@ with_logger(NullLogger()) do include("run_sediment.jl") #include("subdomains.jl") - Aqua.test_all(Wflow; ambiguities = false) + #Aqua.test_all(Wflow; ambiguities = false) end end From 38f90e46a4826f2c54885f9568cadb4fb0f9a0cc Mon Sep 17 00:00:00 2001 From: hboisgon Date: Tue, 17 Sep 2024 14:50:35 +0800 Subject: [PATCH 08/42] re-enable all tests and remove non-used config --- test/runtests.jl | 28 ++-- test/sediment_config_refactor.toml | 213 ----------------------------- 2 files changed, 14 insertions(+), 227 deletions(-) delete mode 100644 test/sediment_config_refactor.toml diff --git a/test/runtests.jl b/test/runtests.jl index e55b1658b..cd8782d7e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -79,21 +79,21 @@ include("testing_utils.jl") with_logger(NullLogger()) do ## run all tests @testset "Wflow.jl" begin - #include("horizontal_process.jl") - #include("io.jl") - #include("vertical_process.jl") - #include("reservoir_lake.jl") - #include("run_sbm.jl") - #include("run_sbm_piave.jl") - #include("run_sbm_gwf_piave.jl") - #include("run_sbm_gwf.jl") - #include("run.jl") - #include("groundwater.jl") - #include("utils.jl") - #include("bmi.jl") + include("horizontal_process.jl") + include("io.jl") + include("vertical_process.jl") + include("reservoir_lake.jl") + include("run_sbm.jl") + include("run_sbm_piave.jl") + include("run_sbm_gwf_piave.jl") + include("run_sbm_gwf.jl") + include("run.jl") + include("groundwater.jl") + include("utils.jl") + include("bmi.jl") include("run_sediment.jl") - #include("subdomains.jl") + include("subdomains.jl") - #Aqua.test_all(Wflow; ambiguities = false) + Aqua.test_all(Wflow; ambiguities = false) end end diff --git a/test/sediment_config_refactor.toml b/test/sediment_config_refactor.toml deleted file mode 100644 index 1844b208a..000000000 --- a/test/sediment_config_refactor.toml +++ /dev/null @@ -1,213 +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-01-03T00:00:00 -starttime = 1999-12-31T00:00:00 -time_units = "days since 1900-01-01 00:00:00" -timestepsecs = 86400 -dir_input = "data/input" -dir_output = "data/output" - -[state] -path_input = "instates-moselle-sed.nc" -path_output = "outstates-moselle-sed.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.lateral.river] -clayload = "clayload" -claystore = "claystore" -gravload = "gravload" -gravstore = "gravstore" -laggload = "laggload" -laggstore = "laggstore" -outclay = "outclay" -outgrav = "outgrav" -outlagg = "outlagg" -outsagg = "outsagg" -outsand = "outsand" -outsilt = "outsilt" -saggload = "saggload" -saggstore = "saggstore" -sandload = "sandload" -sandstore = "sandstore" -siltload = "siltload" -siltstore = "siltstore" - -[input] -path_forcing = "forcing-moselle-sed.nc" -path_static = "staticmaps-moselle-sed.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.h_land", - "vertical.interception", - "vertical.precipitation", - "vertical.q_land", - "lateral.river.h_riv", - "lateral.river.q_riv", -] - -cyclic = ["vertical.leaf_area_index"] - -[input.vertical] -pathfrac = "PathFrac" -slope = "Slope" -## boundary_conditions to be moved -precipitation = "P" -q_land = "runL" -interception = "int" -## interception to be merged with sbm -#kext = "Kext" -#leaf_area_index = "LAI" # cyclic -#specific_leaf = "Sl" -#storage_wood = "Swood" -## Land transport part -h_land = "levKinL" -rivcell = "wflow_river" -# Reservoir -resareas = "wflow_reservoirareas" -# Lake -lakeareas = "wflow_lakeareas" - -[input.vertical.soil_erosion.parameters] -soil_detachability = "ErosK" -eurosem_exponent = "eros_spl_EUROSEM" -canopyheight = "CanopyHeight" -canopygapfraction = "CanopyGapFraction" -usle_k = "USLE_K" -usle_c = "USLE_C" -answers_k = "eros_ov" -clay_fraction = "PercentClay" -silt_fraction = "PercentSilt" -sand_fraction = "PercentSand" -sagg_fraction = "PercentSagg" -lagg_fraction = "PercentLagg" - -[input.lateral.land] -slope = "Slope" - -[input.lateral.river] -h_riv = "h" -q_riv = "q" -cbagnold = "c_Bagnold" -d50 = "D50_River" -d50engelund = "D50_River" -ebagnold = "exp_Bagnold" -fclayriv = "ClayF_River" -fgravriv = "GravelF_River" -fsandriv = "SandF_River" -fsiltriv = "SiltF_River" -length = "wflow_riverlength" -slope = "RiverSlope" -width = "wflow_riverwidth" -# Reservoir -resarea = "ResSimpleArea" -restrapeff = "ResTrapEff" -resareas = "wflow_reservoirareas" -reslocs = "wflow_reservoirlocs" -# Lake -lakearea = "LakeArea" -lakeareas = "wflow_lakeareas" -lakelocs = "wflow_lakelocs" - -[model] -dolake = false -doreservoir = true -landtransportmethod = "yalinpart" # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] -rainerosmethod = "answers" # Rainfall erosion equation: ["answers", "eurosem"] -reinit = true -rivtransportmethod = "bagnold" # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] -runrivermodel = true -type = "sediment" - -[output] -path = "output-moselle-sed.nc" - -[output.vertical] -TCclay = "TCclay" -TCsed = "TCsed" -erosclay = "erosclay" -pathfrac = "pathfrac" -precipitation = "prec" -sedov = "sedov" -sedspl = "sedspl" -soilloss = "soilloss" - -[output.lateral.land] -inlandclay = "inlandclay" -inlandsed = "inlandsed" -olclay = "olclay" -olsed = "olsed" - -[output.lateral.river] -Bedconc = "Bedconc" -SSconc = "SSconc" -Sedconc = "Sedconc" -clayload = "clayload" -h_riv = "h_riv" -inlandclay = "inlandclayriv" -outclay = "outclay" -width = "widthriv" - -[csv] -path = "output-moselle-sediment.csv" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "SL" -parameter = "vertical.soilloss" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "SSPL" -parameter = "vertical.sedspl" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "SOV" -parameter = "vertical.sedov" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "P" -parameter = "vertical.precipitation" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "ql" -parameter = "vertical.q_land" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "TCsed" -parameter = "vertical.TCsed" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "TCclay" -parameter = "vertical.TCclay" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "inlandsed" -parameter = "lateral.land.inlandsed" From 384775644f8267eb0f161186048b0b7b7ca6fbb6 Mon Sep 17 00:00:00 2001 From: hboisgon Date: Thu, 19 Sep 2024 17:46:07 +0800 Subject: [PATCH 09/42] refactor sediment lateral land --- src/Wflow.jl | 5 + src/bmi.jl | 5 +- src/sediment.jl | 673 ------------------ src/sediment_flux.jl | 77 ++ src/sediment_model.jl | 96 ++- src/sediment_transport/land_to_river.jl | 155 ++++ .../overland_flow_transport.jl | 197 +++++ src/sediment_transport/transport_capacity.jl | 424 +++++++++++ .../transport_capacity_process.jl | 287 ++++++++ test/run_sediment.jl | 37 +- test/runtests.jl | 28 +- test/sediment_config.toml | 50 +- 12 files changed, 1253 insertions(+), 781 deletions(-) create mode 100644 src/sediment_flux.jl create mode 100644 src/sediment_transport/land_to_river.jl create mode 100644 src/sediment_transport/overland_flow_transport.jl create mode 100644 src/sediment_transport/transport_capacity.jl create mode 100644 src/sediment_transport/transport_capacity_process.jl diff --git a/src/Wflow.jl b/src/Wflow.jl index 3c27a7e8b..092225cf4 100644 --- a/src/Wflow.jl +++ b/src/Wflow.jl @@ -121,6 +121,11 @@ include("erosion/erosion_process.jl") include("erosion/rainfall_erosion.jl") include("erosion/overland_flow_erosion.jl") include("erosion/soil_erosion.jl") +include("sediment_flux.jl") +include("sediment_transport/transport_capacity_process.jl") +include("sediment_transport/transport_capacity.jl") +include("sediment_transport/overland_flow_transport.jl") +include("sediment_transport/land_to_river.jl") include("vertical_process.jl") include("groundwater/connectivity.jl") include("groundwater/aquifer.jl") diff --git a/src/bmi.jl b/src/bmi.jl index eff39fb14..e53c94b14 100644 --- a/src/bmi.jl +++ b/src/bmi.jl @@ -537,9 +537,8 @@ exchange(::SBM, var) = ) ? 0 : 1 grid_location(::SBM, var) = var in (:n, :dt, :maxlayers) ? "none" : "node" -exchange(::Union{LandSediment, OverlandFlowSediment}, var) = var == :n ? 0 : 1 -grid_location(::Union{LandSediment, OverlandFlowSediment}, var) = - var == :n ? "none" : "node" +exchange(::Union{SoilLoss, OverlandFlowSediment}, var) = var == :n ? 0 : 1 +grid_location(::Union{SoilLoss, OverlandFlowSediment}, var) = var == :n ? "none" : "node" exchange(::RiverSediment, var) = var in (:n, :dt) ? 0 : 1 grid_location(::RiverSediment, var) = var in (:n, :dt) ? "none" : "node" diff --git a/src/sediment.jl b/src/sediment.jl index df572c8ba..647ec60f4 100644 --- a/src/sediment.jl +++ b/src/sediment.jl @@ -1,676 +1,3 @@ -### Soil erosion ### -@get_units @with_kw struct LandSediment{T} - # number of cells - n::Int | "-" - ### Soil erosion part ### - # length of cells in y direction [m] - yl::Vector{T} | "m" - # length of cells in x direction [m] - xl::Vector{T} | "m" - # Fraction of river [-] - riverfrac::Vector{T} | "-" - # Waterbodies areas [-] - wbcover::Vector{T} | "-" - # Depth of overland flow [m] - h_land::Vector{T} | "m" - # Canopy interception [mm Δt⁻¹] - interception::Vector{T} | "mm dt-1" - # Precipitation [mm Δt⁻¹] - precipitation::Vector{T} | "mm dt-1" - # Overland flow [m3/s] - q_land::Vector{T} | "m3 s-1" - # Canopy height [m] - canopyheight::Vector{T} | "m" - # Canopy gap fraction [-] - canopygapfraction::Vector{T} | "-" - # Coefficient for EUROSEM rainfall erosion [-] - erosk::Vector{T} | "-" - # Exponent for EUROSEM rainfall erosion [-] - erosspl::Vector{T} | "-" - # Coefficient for ANSWERS overland flow erosion [-] - erosov::Vector{T} | "-" - # Fraction of impervious area per grid cell [-] - pathfrac::Vector{T} | "-" - # Land slope [-] - slope::Vector{T} | "-" - # USLE crop management factor [-] - usleC::Vector{T} | "-" - # USLE soil erodibility factor [-] - usleK::Vector{T} | "-" - # Sediment eroded by rainfall [ton Δt⁻¹] - sedspl::Vector{T} | "t dt-1" - # Sediment eroded by overland flow [ton Δt⁻¹] - sedov::Vector{T} | "t dt-1" - # Total eroded soil [ton Δt⁻¹] - soilloss::Vector{T} | "t dt-1" - # Eroded soil per particle class [ton Δt⁻¹] - erosclay::Vector{T} | "t dt-1" # clay - erossilt::Vector{T} | "t dt-1" # silt - erossand::Vector{T} | "t dt-1" # sand - erossagg::Vector{T} | "t dt-1" # small aggregates - eroslagg::Vector{T} | "t dt-1" # large aggregates - ## 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" - ### Transport capacity part ### - # Drain length [m] - dl::Vector{T} | "m" - # Flow width [m] - dw::Vector{T} | "m" - # Govers transport capacity coefficients [-] - cGovers::Vector{T} | "-" - nGovers::Vector{T} | "-" - # Median particle diameter of the topsoil [mm] - D50::Vector{T} | "mm" - # Median diameter per particle class [um] - dmclay::Vector{T} | "µm" - dmsilt::Vector{T} | "µm" - dmsand::Vector{T} | "µm" - dmsagg::Vector{T} | "µm" - dmlagg::Vector{T} | "µm" - # Fraction of the different particle class [-] - fclay::Vector{T} | "-" - fsilt::Vector{T} | "-" - fsand::Vector{T} | "-" - fsagg::Vector{T} | "-" - flagg::Vector{T} | "-" - # Density of sediment [kg/m3] - rhos::Vector{T} | "kg m-3" - # Filter with river cells - rivcell::Vector{T} | "-" - # Total transport capacity of overland flow [ton Δt⁻¹] - TCsed::Vector{T} | "t dt-1" - # Transport capacity of overland flow per particle class [ton Δt⁻¹] - TCclay::Vector{T} | "t dt-1" - TCsilt::Vector{T} | "t dt-1" - TCsand::Vector{T} | "t dt-1" - TCsagg::Vector{T} | "t dt-1" - TClagg::Vector{T} | "t dt-1" - - function LandSediment{T}(args...) where {T} - equal_size_vectors(args) - return new(args...) - end -end - -function initialize_landsed(nc, config, river, riverfrac, xl, yl, inds) - # Initialize parameters for the soil loss part - n = length(inds) - do_river = get(config.model, "runrivermodel", false)::Bool - # Reservoir / lake - do_reservoirs = get(config.model, "doreservoir", false)::Bool - do_lakes = get(config.model, "dolake", false)::Bool - # Rainfall erosion equation: ["answers", "eurosem"] - rainerosmethod = get(config.model, "rainerosmethod", "answers")::String - # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] - landtransportmethod = get(config.model, "landtransportmethod", "yalinpart")::String - - altitude = - ncread(nc, config, "vertical.altitude"; optional = false, sel = inds, type = Float) - canopyheight = ncread( - nc, - config, - "vertical.canopyheight"; - sel = inds, - defaults = 3.0, - type = Float, - ) - erosk = ncread(nc, config, "vertical.erosk"; sel = inds, defaults = 0.6, type = Float) - erosspl = - ncread(nc, config, "vertical.erosspl"; sel = inds, defaults = 2.0, type = Float) - erosov = ncread(nc, config, "vertical.erosov"; sel = inds, defaults = 0.9, type = Float) - pathfrac = - ncread(nc, config, "vertical.pathfrac"; sel = inds, defaults = 0.01, type = Float) - slope = ncread(nc, config, "vertical.slope"; sel = inds, defaults = 0.01, type = Float) - usleC = ncread(nc, config, "vertical.usleC"; sel = inds, defaults = 0.01, type = Float) - usleK = ncread(nc, config, "vertical.usleK"; sel = inds, defaults = 0.1, type = Float) - - cmax, _, canopygapfraction, sl, swood, kext = initialize_canopy(nc, config, inds) - - # Initialise parameters for the transport capacity part - clamp!(slope, 0.00001, Inf) - ldd_2d = ncread(nc, config, "ldd"; optional = false, allow_missing = true) - ldd = ldd_2d[inds] - - dmclay = ncread(nc, config, "vertical.dmclay"; sel = inds, defaults = 2.0, type = Float) - dmsilt = - ncread(nc, config, "vertical.dmsilt"; sel = inds, defaults = 10.0, type = Float) - dmsand = - ncread(nc, config, "vertical.dmsand"; sel = inds, defaults = 200.0, type = Float) - dmsagg = - ncread(nc, config, "vertical.dmsagg"; sel = inds, defaults = 30.0, type = Float) - dmlagg = - ncread(nc, config, "vertical.dmlagg"; sel = inds, defaults = 500.0, type = Float) - pclay = ncread(nc, config, "vertical.pclay"; sel = inds, defaults = 0.1, type = Float) - psilt = ncread(nc, config, "vertical.psilt"; sel = inds, defaults = 0.1, type = Float) - rhos = - ncread(nc, config, "vertical.rhosed"; sel = inds, defaults = 2650.0, type = Float) - - ### Initialize transport capacity variables ### - rivcell = float(river) - # Percent Sand - psand = 100 .- pclay .- psilt - # Govers coefficient for transport capacity - if landtransportmethod != "yalinpart" - # Calculation of D50 and fraction of fine and very fine sand (fvfs) from Fooladmand et al, 2006 - psand999 = psand .* ((999 - 25) / (1000 - 25)) - vd50 = log.((1 ./ (0.01 .* (pclay .+ psilt)) .- 1) ./ (1 ./ (0.01 .* pclay) .- 1)) - wd50 = - log.( - (1 ./ (0.01 .* (pclay .+ psilt .+ psand999)) .- 1) ./ - (1 ./ (0.01 .* pclay) .- 1), - ) - ad50 = 1 / log((25 - 1) / (999 - 1)) - bd50 = ad50 ./ log((25 - 1) / 1) - cd50 = ad50 .* log.(vd50 ./ wd50) - ud50 = (.-vd50) .^ (1 .- bd50) ./ ((.-wd50) .^ (.-bd50)) - D50 = 1 .+ (-1 ./ ud50 .* log.(1 ./ (1 ./ (0.01 .* pclay) .- 1))) .^ (1 ./ cd50) #[um] - D50 = D50 ./ 1000 # [mm] - else - D50 = fill(mv, n) - end - if landtransportmethod == "govers" - cGovers = ((D50 .* 1000 .+ 5) ./ 0.32) .^ (-0.6) - nGovers = ((D50 .* 1000 .+ 5) ./ 300) .^ (0.25) - else - cGovers = fill(mv, n) - nGovers = fill(mv, n) - end - if do_river || landtransportmethod == "yalinpart" - # Determine sediment size distribution, estimated from primary particle size distribution (Foster et al., 1980) - fclay = 0.20 .* pclay ./ 100 - 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 in 1:n - if pclay[i] > 50.0 - fsagg[i] = 0.57 - elseif pclay[i] < 25 - fsagg[i] = 2.0 * 0.01 * pclay[i] - end - end - flagg = 1.0 .- fclay .- fsilt .- fsand .- fsagg - else - fclay = fill(mv, n) - fsilt = fill(mv, n) - fsand = fill(mv, n) - fsagg = fill(mv, n) - flagg = fill(mv, n) - end - - # Reservoir and lakes - wbcover = zeros(Float, n) - if do_reservoirs - rescoverage_2d = ncread( - nc, - config, - "vertical.resareas"; - optional = false, - sel = inds, - type = Float, - fill = 0.0, - ) - wbcover = wbcover .+ rescoverage_2d - end - if do_lakes - lakecoverage_2d = ncread( - nc, - config, - "vertical.lakeareas"; - optional = false, - sel = inds, - type = Float, - fill = 0.0, - ) - wbcover = wbcover .+ lakecoverage_2d - end - - eros = LandSediment{Float}(; - n = n, - yl = yl, - xl = xl, - riverfrac = riverfrac, - wbcover = wbcover, - ### Soil erosion part ### - # Forcing - interception = fill(mv, n), - h_land = fill(mv, n), - precipitation = fill(mv, n), - q_land = fill(mv, n), - # Parameters - canopyheight = canopyheight, - canopygapfraction = canopygapfraction, - erosk = erosk, - erosspl = erosspl, - erosov = erosov, - pathfrac = pathfrac, - slope = slope, - usleC = usleC, - usleK = usleK, - # Interception related to climatology (leaf_area_index) - sl = sl, - swood = swood, - kext = kext, - leaf_area_index = fill(mv, n), - # Outputs - sedspl = fill(mv, n), - sedov = fill(mv, n), - soilloss = fill(mv, n), - erosclay = fill(mv, n), - erossilt = fill(mv, n), - erossand = fill(mv, n), - erossagg = fill(mv, n), - eroslagg = fill(mv, n), - ### Transport capacity part ### - # Parameters - dl = map(detdrainlength, ldd, xl, yl), - dw = map(detdrainwidth, ldd, xl, yl), - cGovers = cGovers, - D50 = D50, - dmclay = dmclay, - dmsilt = dmsilt, - dmsand = dmsand, - dmsagg = dmsagg, - dmlagg = dmlagg, - fclay = fclay, - fsilt = fsilt, - fsand = fsand, - fsagg = fsagg, - flagg = flagg, - nGovers = nGovers, - rhos = rhos, - rivcell = rivcell, - # Outputs - TCsed = fill(mv, n), - TCclay = fill(mv, n), - TCsilt = fill(mv, n), - TCsand = fill(mv, n), - TCsagg = fill(mv, n), - TClagg = fill(mv, n), - ) - - return eros -end - -# Soil erosion -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 in 1:(eros.n) - - ### Splash / Rainfall erosion ### - # ANSWERS method - if rainerosmethod == "answers" - # calculate rainfall intensity [mm/min] - rintnsty = eros.precipitation[i] / (ts / 60) - # splash erosion [kg/min] - sedspl = - 0.108 * eros.usleC[i] * eros.usleK[i] * eros.xl[i] * eros.yl[i] * rintnsty^2 - # [ton/timestep] - sedspl = sedspl * (ts / 60) * 1e-3 - end - # TODO check eurosem method output values (too high!) - if rainerosmethod == "eurosem" - if do_lai - cmax = eros.sl[i] * eros.leaf_area_index[i] + eros.swood[i] - canopygapfraction = exp(-eros.kext[i] * eros.leaf_area_index[i]) - end - # calculate rainfall intensity [mm/h] - rintnsty = eros.precipitation[i] / (ts / 3600) - # Kinetic energy of direct throughfall [J/m2/mm] - # kedir = max(11.87 + 8.73 * log10(max(0.0001, rintnsty)),0.0) #basis used in USLE - kedir = max(8.95 + 8.44 * log10(max(0.0001, rintnsty)), 0.0) #variant used in most distributed mdoels - # Kinetic energy of leaf drainage [J/m2/mm] - pheff = 0.5 * eros.canopyheight[i] - keleaf = max((15.8 * pheff^0.5) - 5.87, 0.0) - - #Depths of rainfall (total, leaf drianage, direct) [mm] - rdtot = eros.precipitation[i] - rdleaf = rdtot * 0.1 * canopygapfraction #stemflow - rddir = max(rdtot - rdleaf - eros.interception[i], 0.0) #throughfall - - #Total kinetic energy by rainfall [J/m2] - ketot = (rddir * kedir + rdleaf * keleaf) * 0.001 - # Rainfall / splash erosion [g/m2] - sedspl = eros.erosk[i] * ketot * exp(-eros.erosspl[i] * eros.h_land[i]) - sedspl = sedspl * eros.xl[i] * eros.yl[i] * 1e-6 # ton/cell - end - - # Remove the impervious area - sedspl = sedspl * (1.0 - eros.pathfrac[i]) - - ### Overland flow erosion ### - # ANWERS method - # Overland flow rate [m2/min] - qr_land = eros.q_land[i] * 60 / ((eros.xl[i] + eros.yl[i]) / 2) - # Sine of the slope - sinslope = sin(atan(eros.slope[i])) - - # Overland flow erosion [kg/min] - # For a wide range of slope, it is better to use the sine of slope rather than tangeant - sedov = - eros.erosov[i] * - eros.usleC[i] * - eros.usleK[i] * - eros.xl[i] * - eros.yl[i] * - sinslope * - qr_land - # [ton/timestep] - sedov = sedov * (ts / 60) * 1e-3 - # Remove the impervious area - sedov = sedov * (1.0 - eros.pathfrac[i]) - - # Assume no erosion in reservoir/lake areas - if eros.wbcover[i] > 0 - sedspl = 0.0 - sedov = 0.0 - end - - # Total soil loss [ton/cell/timestep] - soilloss = sedspl + sedov - - # update the outputs and states - eros.sedspl[i] = sedspl - eros.sedov[i] = sedov - eros.soilloss[i] = soilloss - # Eroded amount per particle class - eros.erosclay[i] = soilloss * eros.fclay[i] - eros.erossilt[i] = soilloss * eros.fsilt[i] - eros.erossand[i] = soilloss * eros.fsand[i] - eros.erossagg[i] = soilloss * eros.fsagg[i] - eros.eroslagg[i] = soilloss * eros.flagg[i] - end -end - -### Sediment transport capacity in overland flow ### -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 in 1:(ols.n) - sinslope = sin(atan(ols.slope[i])) - - if !do_river - # Total transport capacity without particle differentiation - if tcmethod == "govers" - TC = tc_govers(ols, i, sinslope, ts) - end - - if tcmethod == "yalin" - TC = tc_yalin(ols, i, sinslope, ts) - end - - # Filter TC land for river cells (0 in order for sediment from land to stop when entering the river) - if ols.rivcell[i] == 1.0 - TC = 0.0 - end - - # Set particle TC to 0 - TCclay = 0.0 - TCsilt = 0.0 - TCsand = 0.0 - TCsagg = 0.0 - TClagg = 0.0 - end - - if do_river || tcmethod == "yalinpart" - TC, TCclay, TCsilt, TCsand, TCsagg, TClagg = tc_yalinpart(ols, i, sinslope, ts) - end - - # update the outputs and states - ols.TCsed[i] = TC - ols.TCclay[i] = TCclay - ols.TCsilt[i] = TCsilt - ols.TCsand[i] = TCsand - ols.TCsagg[i] = TCsagg - ols.TClagg[i] = TClagg - end -end - -function tc_govers(ols::LandSediment, i::Int, sinslope::Float, ts::Float)::Float - # Transport capacity from govers 1990 - # Unit stream power - if ols.h_land[i] > 0.0 - velocity = ols.q_land[i] / (ols.dw[i] * ols.h_land[i]) - else - velocity = 0.0 - end - omega = 10 * sinslope * 100 * velocity #cm/s - if omega > 0.4 - TCf = ols.cGovers[i] * (omega - 0.4)^(ols.nGovers[i]) * 2650 #kg/m3 - else - TCf = 0.0 - end - TC = TCf * ols.q_land[i] * ts * 1e-3 #[ton/cell] - - return TC -end - -function tc_yalin(ols::LandSediment, i::Int, sinslope::Float, ts::Float)::Float - # Transport capacity from Yalin without particle differentiation - delta = max( - ( - ols.h_land[i] * sinslope / (ols.D50[i] * 0.001 * (ols.rhos[i] / 1000 - 1)) / - 0.06 - 1 - ), - 0.0, - ) - alphay = delta * 2.45 / (0.001 * ols.rhos[i])^0.4 * 0.06^(0.5) - if ols.q_land[i] > 0.0 && alphay != 0.0 - TC = ( - ols.dw[i] / ols.q_land[i] * - (ols.rhos[i] - 1000) * - ols.D50[i] * - 0.001 * - (9.81 * ols.h_land[i] * sinslope) * - 0.635 * - delta * - (1 - log(1 + alphay) / (alphay)) - ) # [kg/m3] - TC = TC * ols.q_land[i] * ts * 1e-3 #[ton] - else - TC = 0.0 - end - return TC -end - -function tc_yalinpart(ols::LandSediment, i::Int, sinslope::Float, ts::Float) - # Transport capacity from Yalin with particle differentiation - # Delta parameter of Yalin for each particle class - delta = ols.h_land[i] * sinslope / (1e-6 * (ols.rhos[i] / 1000 - 1)) / 0.06 - dclay = max(1 / ols.dmclay[i] * delta - 1, 0.0) - dsilt = max(1 / ols.dmsilt[i] * delta - 1, 0.0) - dsand = max(1 / ols.dmsand[i] * delta - 1, 0.0) - dsagg = max(1 / ols.dmsagg[i] * delta - 1, 0.0) - dlagg = max(1 / ols.dmlagg[i] * delta - 1, 0.0) - # Total transportability - dtot = dclay + dsilt + dsand + dsagg + dlagg - - # Yalin transport capacity of overland flow for each particle class - if ols.q_land[i] > 0.0 - TCa = - ols.dw[i] / ols.q_land[i] * - (ols.rhos[i] - 1000) * - 1e-6 * - (9.81 * ols.h_land[i] * sinslope) - else - TCa = 0.0 - end - TCb = 2.45 / (ols.rhos[i] / 1000)^0.4 * 0.06^0.5 - if dtot != 0.0 && dclay != 0.0 - TCclay = - TCa * ols.dmclay[i] * dclay / dtot * - 0.635 * - dclay * - (1 - log(1 + dclay * TCb) / dclay * TCb) # [kg/m3] - TCclay = TCclay * ols.q_land[i] * ts * 1e-3 # [ton] - else - TCclay = 0.0 - end - if dtot != 0.0 && dsilt != 0.0 - TCsilt = - TCa * ols.dmsilt[i] * dsilt / dtot * - 0.635 * - dsilt * - (1 - log(1 + dsilt * TCb) / dsilt * TCb) # [kg/m3] - TCsilt = TCsilt * ols.q_land[i] * ts * 1e-3 # [ton] - else - TCsilt = 0.0 - end - if dtot != 0.0 && dsand != 0.0 - TCsand = - TCa * ols.dmsand[i] * dsand / dtot * - 0.635 * - dsand * - (1 - log(1 + dsand * TCb) / dsand * TCb) # [kg/m3] - TCsand = TCsand * ols.q_land[i] * ts * 1e-3 # [ton] - else - TCsand = 0.0 - end - if dtot != 0.0 && dsagg != 0.0 - TCsagg = - TCa * ols.dmsagg[i] * dsagg / dtot * - 0.635 * - dsagg * - (1 - log(1 + dsagg * TCb) / dsagg * TCb) # [kg/m3] - TCsagg = TCsagg * ols.q_land[i] * ts * 1e-3 # [ton] - else - TCsagg = 0.0 - end - if dtot != 0.0 && dlagg != 0.0 - TClagg = - TCa * ols.dmlagg[i] * dlagg / dtot * - 0.635 * - dlagg * - (1 - log(1 + dlagg * TCb) / dlagg * TCb) # [kg/m3] - TClagg = TClagg * ols.q_land[i] * ts * 1e-3 # [ton] - else - TClagg = 0.0 - end - - # Assume that ols all reach the river in reservoir/lake areas (very high TC) - if ols.wbcover[i] > 0 - TC = 1e9 - TCclay = 1e9 - TCsilt = 1e9 - TCsand = 1e9 - TCsagg = 1e9 - TClagg = 1e9 - end - - # Filter TC land for river cells (0 in order for sediment from land to stop when entering the river) - if ols.rivcell[i] == 1.0 - TCclay = 0.0 - TCsilt = 0.0 - TCsand = 0.0 - TCsagg = 0.0 - TClagg = 0.0 - end - - # Set total TC to 0 - TC = 0.0 - - return TC, TCclay, TCsilt, TCsand, TCsagg, TClagg -end - -### Sediment transport in overland flow ### -@get_units @with_kw struct OverlandFlowSediment{T} - # number of cells - n::Int | "-" - # Filter with river cells - rivcell::Vector{T} | "-" - # Total eroded soil [ton Δt⁻¹] - soilloss::Vector{T} | "t dt-1" - # Eroded soil per particle class [ton Δt⁻¹] - erosclay::Vector{T} | "t dt-1" - erossilt::Vector{T} | "t dt-1" - erossand::Vector{T} | "t dt-1" - erossagg::Vector{T} | "t dt-1" - eroslagg::Vector{T} | "t dt-1" - # Total transport capacity of overland flow [ton Δt⁻¹] - TCsed::Vector{T} | "t dt-1" - # Transport capacity of overland flow per particle class [ton Δt⁻¹] - TCclay::Vector{T} | "t dt-1" - TCsilt::Vector{T} | "t dt-1" - TCsand::Vector{T} | "t dt-1" - TCsagg::Vector{T} | "t dt-1" - TClagg::Vector{T} | "t dt-1" - # Sediment flux in overland flow [ton dt-1] - olsed::Vector{T} | "t dt-1" - olclay::Vector{T} | "t dt-1" - olsilt::Vector{T} | "t dt-1" - olsand::Vector{T} | "t dt-1" - olsagg::Vector{T} | "t dt-1" - ollagg::Vector{T} | "t dt-1" - # Sediment reaching the river with overland flow [ton dt-1] - inlandsed::Vector{T} | "t dt-1" - inlandclay::Vector{T} | "t dt-1" - inlandsilt::Vector{T} | "t dt-1" - inlandsand::Vector{T} | "t dt-1" - 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) - inland[i] = rivcell[i] == 1 ? eroded[i] : no_erosion - end - return inland -end - -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) - - # transport sediment down the network - if do_river || tcmethod == "yalinpart" - # clay - accucapacityflux!(ols.olclay, ols.erosclay, network, ols.TCclay) - partial_update!(ols.inlandclay, ols.rivcell, ols.erosclay) - # silt - accucapacityflux!(ols.olsilt, ols.erossilt, network, ols.TCsilt) - partial_update!(ols.inlandsilt, ols.rivcell, ols.erossilt) - # sand - accucapacityflux!(ols.olsand, ols.erossand, network, ols.TCsand) - partial_update!(ols.inlandsand, ols.rivcell, ols.erossand) - # small aggregates - accucapacityflux!(ols.olsagg, ols.erossagg, network, ols.TCsagg) - partial_update!(ols.inlandsagg, ols.rivcell, ols.erossagg) - # large aggregates - accucapacityflux!(ols.ollagg, ols.eroslagg, network, ols.TClagg) - partial_update!(ols.inlandlagg, ols.rivcell, ols.eroslagg) - - # total sediment, all particle classes - ols.olsed .= ols.olclay .+ ols.olsilt .+ ols.olsand .+ ols.olsagg .+ ols.ollagg - ols.inlandsed .= - ols.inlandclay .+ ols.inlandsilt .+ ols.inlandsand .+ ols.inlandsagg .+ - ols.inlandlagg - else - accucapacityflux!(ols.olsed, ols.soilloss, network, ols.TCsed) - ols.inlandsed .= ifelse.(ols.rivcell .== 1, ols.soilloss, zeroarr) - end -end - ### River transport and processes ### @get_units @with_kw struct RiverSediment{T} # number of cells diff --git a/src/sediment_flux.jl b/src/sediment_flux.jl new file mode 100644 index 000000000..fae0016b3 --- /dev/null +++ b/src/sediment_flux.jl @@ -0,0 +1,77 @@ +@get_units @with_kw struct OverlandFlowSediment{TT, SF, TR, T} + hydrometeo_forcing::HydrometeoForcing | "-" + transport_capacity::TT | "-" + sediment_flux::SF | "-" + to_river::TR | "-" + width::Vector{T} | "m" + waterbodies::Vector{Bool} | "-" + rivers::Vector{Bool} | "-" +end + +function initialize_overland_flow_sediment(nc, config, inds, width, waterbodies, rivers) + n = length(inds) + hydrometeo_forcing = initialize_hydrometeo_forcing(n) + # Check what transport capacity equation will be used + do_river = get(config.model, "runrivermodel", false)::Bool + # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] + landtransportmethod = get(config.model, "landtransportmethod", "yalinpart")::String + + if do_river || landtransportmethod == "yalinpart" + transport_capacity_model = + initialize_transport_capacity_yalin_diff_model(nc, config, inds) + elseif landtransportmethod == "govers" + transport_capacity_model = + initialize_transport_capacity_govers_model(nc, config, inds) + elseif landtransportmethod == "yalin" + transport_capacity_model = + initialize_transport_capacity_yalin_model(nc, config, inds) + else + error("Unknown land transport method: $landtransportmethod") + end + + if do_river || landtransportmethod == "yalinpart" + sediment_flux_model = initialize_sediment_land_transport_differentiation_model(inds) + to_river_model = initialize_sediment_to_river_differentiation_model(inds) + else + sediment_flux_model = initialize_sediment_land_transport_model(inds) + to_river_model = initialize_sediment_to_river_model(inds) + end + + overland_flow_sediment = OverlandFlowSediment{ + typeof(transport_capacity_model), + typeof(sediment_flux_model), + typeof(to_river_model), + Float, + }(; + hydrometeo_forcing = hydrometeo_forcing, + transport_capacity = transport_capacity_model, + sediment_flux = sediment_flux_model, + to_river = to_river_model, + width = width, + waterbodies = waterbodies, + rivers = rivers, + ) + return overland_flow_sediment +end + +function update!(model::OverlandFlowSediment, erosion_model::SoilErosionModel, network, dt) + # Convert dt to integer + ts = tosecond(dt) + # Update the boundary conditions of transport capacity + (; q, waterlevel) = model.transport_capacity.boundary_conditions + (; q_land, waterlevel_land) = model.hydrometeo_forcing + @. q = q_land + @. waterlevel = waterlevel_land + # Transport capacity + update!(model.transport_capacity, model.width, model.waterbodies, model.rivers, ts) + + # Update boundary conditions before transport + update_bc(model.sediment_flux, erosion_model, model.transport_capacity) + # Compute transport + update!(model.sediment_flux, network) + + # Update boundary conditions before computing sediment reaching the river + update_bc(model.to_river, model.sediment_flux) + # Compute sediment reaching the river + update!(model.to_river, model.rivers) +end \ No newline at end of file diff --git a/src/sediment_model.jl b/src/sediment_model.jl index c1f6203b9..141c4a526 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -13,9 +13,6 @@ function initialize_sediment_model(config::Config) reader = prepare_reader(config) clock = Clock(config, reader) - - do_river = get(config.model, "runrivermodel", false)::Bool - nc = NCDataset(static_path) subcatch_2d = ncread(nc, config, "subcatchment"; optional = false, allow_missing = true) @@ -47,7 +44,6 @@ function initialize_sediment_model(config::Config) sizeinmetres = get(config.model, "sizeinmetres", false)::Bool xl, yl = cell_lengths(y, cellength, sizeinmetres) - riverfrac = river_fraction(river, riverlength, riverwidth, xl, yl) area = xl .* yl landslope = ncread(nc, config, "vertical.slope"; optional = false, sel = inds, type = Float) @@ -55,49 +51,51 @@ function initialize_sediment_model(config::Config) soilloss = initialize_soil_loss(nc, config, inds, area, landslope) + # Get waterbodies mask + do_reservoirs = get(config.model, "reservoirs", false)::Bool + do_lakes = get(config.model, "lakes", false)::Bool + waterbodies = fill(0.0, n) + if do_reservoirs + reservoirs = ncread( + nc, + config, + "reservoir_areas"; + optional = false, + sel = inds, + type = Float, + fill = 0, + ) + waterbodies = waterbodies .+ reservoirs + end + if do_lakes + lakes = ncread( + nc, + config, + "lake_areas"; + optional = false, + sel = inds, + type = Float, + fill = 0, + ) + waterbodies = waterbodies .+ lakes + end + waterbodies = waterbodies .> 0 + ldd_2d = ncread(nc, config, "ldd"; optional = false, allow_missing = true) ldd = ldd_2d[inds] + drain_width = map(detdrainwidth, ldd, xl, yl) + # # lateral part sediment in overland flow - rivcell = float(river) - ols = OverlandFlowSediment{Float}(; - n = n, - rivcell = rivcell, - soilloss = fill(mv, n), - erosclay = fill(mv, n), - erossilt = fill(mv, n), - erossand = fill(mv, n), - erossagg = fill(mv, n), - eroslagg = fill(mv, n), - TCsed = fill(mv, n), - TCclay = fill(mv, n), - TCsilt = fill(mv, n), - TCsand = fill(mv, n), - TCsagg = fill(mv, n), - TClagg = fill(mv, n), - olsed = fill(mv, n), - olclay = fill(mv, n), - olsilt = fill(mv, n), - olsand = fill(mv, n), - olsagg = fill(mv, n), - ollagg = fill(mv, n), - inlandsed = fill(mv, n), - inlandclay = fill(mv, n), - inlandsilt = fill(mv, n), - inlandsand = fill(mv, n), - inlandsagg = fill(mv, n), - inlandlagg = fill(mv, n), - ) + overland_flow_sediment = + initialize_overland_flow_sediment(nc, config, inds, drain_width, waterbodies, river) graph = flowgraph(ldd, inds, pcr_dir) # River processes + do_river = get(config.model, "runrivermodel", false)::Bool # the indices of the river cells in the land(+river) cell vector - landslope = - ncread(nc, config, "lateral.land.slope"; optional = false, sel = inds, type = Float) - clamp!(landslope, 0.00001, Inf) - riverlength = riverlength_2d[inds_riv] riverwidth = riverwidth_2d[inds_riv] minimum(riverlength) > 0 || error("river length must be positive on river cells") @@ -111,7 +109,7 @@ function initialize_sediment_model(config::Config) rs = initialize_riversed(nc, config, riverwidth, riverlength, inds_riv) - modelmap = (vertical = soilloss, lateral = (land = ols, river = rs)) + modelmap = (vertical = soilloss, lateral = (land = overland_flow_sediment, river = rs)) indices_reverse = ( land = rev_inds, river = rev_inds_riv, @@ -139,7 +137,7 @@ function initialize_sediment_model(config::Config) model = Model( config, (; land, river, reservoir, lake, index_river, frac_toriver), - (land = ols, river = rs), + (land = overland_flow_sediment, river = rs), soilloss, clock, reader, @@ -159,23 +157,9 @@ function update(model::Model{N, L, V, R, W, T}) where {N, L, V, R, W, T <: Sedim # Soil erosion update!(vertical, dt) - # update_until_oltransport(vertical, config) - - # lateral.land.soilloss .= vertical.soilloss - # lateral.land.erosclay .= vertical.erosclay - # lateral.land.erossilt .= vertical.erossilt - # lateral.land.erossand .= vertical.erossand - # lateral.land.erossagg .= vertical.erossagg - # lateral.land.eroslagg .= vertical.eroslagg - - # lateral.land.TCsed .= vertical.TCsed - # lateral.land.TCclay .= vertical.TCclay - # lateral.land.TCsilt .= vertical.TCsilt - # lateral.land.TCsand .= vertical.TCsand - # lateral.land.TCsagg .= vertical.TCsagg - # lateral.land.TClagg .= vertical.TClagg - - # update(lateral.land, network.land, config) + + # Overland flow sediment transport + update!(lateral.land, vertical.soil_erosion, network.land, dt) # do_river = get(config.model, "runrivermodel", false)::Bool diff --git a/src/sediment_transport/land_to_river.jl b/src/sediment_transport/land_to_river.jl new file mode 100644 index 000000000..5457295e3 --- /dev/null +++ b/src/sediment_transport/land_to_river.jl @@ -0,0 +1,155 @@ +abstract type AbstractSedimentToRiverModel end + +## Total sediment transport in overland flow structs and functions +@get_units @with_kw struct SedimentToRiverVars{T} + # Total sediment reaching the river + amount::Vector{T} | "t dt-1" +end + +function sediment_to_river_vars(n) + vars = SedimentToRiverVars(; amount = fill(mv, n)) + return vars +end + +@get_units @with_kw struct SedimentToRiverBC{T} + # Deposited material + deposition::Vector{T} | "t dt-1" +end + +function sediment_to_river_bc(n) + bc = SedimentToRiverBC(; deposition = fill(mv, n)) + return bc +end + +@get_units @with_kw struct SedimentToRiverModel{T} <: AbstractSedimentToRiverModel + boundary_conditions::SedimentToRiverBC{T} | "-" + variables::SedimentToRiverVars{T} | "-" +end + +function initialize_sediment_to_river_model(inds) + n = length(inds) + vars = sediment_to_river_vars(n) + bc = sediment_to_river_bc(n) + model = SedimentToRiverModel(; boundary_conditions = bc, variables = vars) + return model +end + +function update_bc(model::SedimentToRiverModel, transport_model::SedimentLandTransportModel) + (; deposition) = model.boundary_conditions + (; amount) = transport_model.variables + @. deposition = amount +end + +function update!(model::SedimentToRiverModel, rivers) + (; deposition) = model.boundary_conditions + (; amount) = model.variables + + zeros = fill(0.0, length(amount)) + amount .= ifelse.(rivers, deposition, zeros) +end + +## Different particles reaching the river structs and functions +@get_units @with_kw struct SedimentToRiverDifferentiationVars{T} + # Total sediment flux + amount::Vector{T} | "t dt-1" + # Clay flux + clay::Vector{T} | "t dt-1" + # Silt + silt::Vector{T} | "t dt-1" + # Sand flux + sand::Vector{T} | "t dt-1" + # Small aggregates flux + sagg::Vector{T} | "t dt-1" + # Large aggregates flux + lagg::Vector{T} | "t dt-1" +end + +function sediment_to_river_differentiation_vars(n) + vars = SedimentToRiverDifferentiationVars(; + amount = fill(mv, n), + clay = fill(mv, n), + silt = fill(mv, n), + sand = fill(mv, n), + sagg = fill(mv, n), + lagg = fill(mv, n), + ) + return vars +end + +@get_units @with_kw struct SedimentToRiverDifferentiationBC{T} + # Deposited clay + deposition_clay::Vector{T} | "t dt-1" + # Deposited silt + deposition_silt::Vector{T} | "t dt-1" + # Deposited sand + deposition_sand::Vector{T} | "t dt-1" + # Deposited small aggregates + deposition_sagg::Vector{T} | "t dt-1" + # Deposited large aggregates + deposition_lagg::Vector{T} | "t dt-1" +end + +function sediment_to_river_differentiation_bc(n) + bc = SedimentToRiverDifferentiationBC(; + deposition_clay = fill(mv, n), + deposition_silt = fill(mv, n), + deposition_sand = fill(mv, n), + deposition_sagg = fill(mv, n), + deposition_lagg = fill(mv, n), + ) + return bc +end + +@get_units @with_kw struct SedimentToRiverDifferentiationModel{T} <: + AbstractSedimentToRiverModel + boundary_conditions::SedimentToRiverDifferentiationBC{T} | "-" + variables::SedimentToRiverDifferentiationVars{T} | "-" +end + +function initialize_sediment_to_river_differentiation_model(inds) + n = length(inds) + vars = sediment_to_river_differentiation_vars(n) + bc = sediment_to_river_differentiation_bc(n) + model = + SedimentToRiverDifferentiationModel(; boundary_conditions = bc, variables = vars) + return model +end + +function update_bc( + model::SedimentToRiverDifferentiationModel, + transport_model::SedimentLandTransportDifferentiationModel, +) + (; + deposition_clay, + deposition_silt, + deposition_sand, + deposition_sagg, + deposition_lagg, + ) = model.boundary_conditions + (; clay, silt, sand, sagg, lagg) = transport_model.variables + @. deposition_clay = clay + @. deposition_silt = silt + @. deposition_sand = sand + @. deposition_sagg = sagg + @. deposition_lagg = lagg +end + +function update!(model::SedimentToRiverDifferentiationModel, rivers) + (; + deposition_clay, + deposition_silt, + deposition_sand, + deposition_sagg, + deposition_lagg, + ) = model.boundary_conditions + (; amount, clay, silt, sand, sagg, lagg) = model.variables + + zeros = fill(0.0, length(amount)) + clay .= ifelse.(rivers, deposition_clay, zeros) + silt .= ifelse.(rivers, deposition_silt, zeros) + sand .= ifelse.(rivers, deposition_sand, zeros) + sagg .= ifelse.(rivers, deposition_sagg, zeros) + lagg .= ifelse.(rivers, deposition_lagg, zeros) + + amount .= clay .+ silt .+ sand .+ sagg .+ lagg +end diff --git a/src/sediment_transport/overland_flow_transport.jl b/src/sediment_transport/overland_flow_transport.jl new file mode 100644 index 000000000..94d733c8e --- /dev/null +++ b/src/sediment_transport/overland_flow_transport.jl @@ -0,0 +1,197 @@ +abstract type AbstractSedimentLandTransportModel end + +## Total sediment transport in overland flow structs and functions +@get_units @with_kw struct SedimentLandTransportVars{T} + # Total sediment reaching the river + amount::Vector{T} | "t dt-1" +end + +function sediment_land_transport_vars(n) + vars = SedimentLandTransportVars(; amount = fill(mv, n)) + return vars +end + +@get_units @with_kw struct SedimentLandTransportBC{T} + # Eroded material + erosion::Vector{T} | "t dt-1" + # Transport capacity + transport_capacity::Vector{T} | "t dt-1" +end + +function sediment_land_transport_bc(n) + bc = SedimentLandTransportBC(; erosion = fill(mv, n), transport_capacity = fill(mv, n)) + return bc +end + +@get_units @with_kw struct SedimentLandTransportModel{T} <: + AbstractSedimentLandTransportModel + boundary_conditions::SedimentLandTransportBC{T} | "-" + variables::SedimentLandTransportVars{T} | "-" +end + +function initialize_sediment_land_transport_model(inds) + n = length(inds) + vars = sediment_land_transport_vars(n) + bc = sediment_land_transport_bc(n) + model = SedimentLandTransportModel(; boundary_conditions = bc, variables = vars) + return model +end + +function update_bc( + model::SedimentLandTransportModel, + erosion_model::SoilErosionModel, + transport_capacity_model::AbstractTransportCapacityModel, +) + (; erosion, transport_capacity) = model.boundary_conditions + (; amount) = erosion_model.variables + @. erosion = amount + + (; amount) = transport_capacity_model.variables + @. transport_capacity = amount +end + +function update!(model::SedimentLandTransportModel, network) + (; erosion, transport_capacity) = model.boundary_conditions + (; amount) = model.variables + + accucapacityflux!(amount, erosion, network, transport_capacity) +end + +## Total transport capacity with particle differentiation structs and functions +@get_units @with_kw struct SedimentLandTransportDifferentiationVars{T} + # Total sediment flux + amount::Vector{T} | "t dt-1" + # Clay flux + clay::Vector{T} | "t dt-1" + # Silt + silt::Vector{T} | "t dt-1" + # Sand flux + sand::Vector{T} | "t dt-1" + # Small aggregates flux + sagg::Vector{T} | "t dt-1" + # Large aggregates flux + lagg::Vector{T} | "t dt-1" +end + +function sediment_land_transport_differentiation_vars(n) + vars = SedimentLandTransportDifferentiationVars(; + amount = fill(mv, n), + clay = fill(mv, n), + silt = fill(mv, n), + sand = fill(mv, n), + sagg = fill(mv, n), + lagg = fill(mv, n), + ) + return vars +end + +@get_units @with_kw struct SedimentLandTransportDifferentiationBC{T} + # Eroded clay + erosion_clay::Vector{T} | "t dt-1" + # Eroded silt + erosion_silt::Vector{T} | "t dt-1" + # Eroded sand + erosion_sand::Vector{T} | "t dt-1" + # Eroded small aggregates + erosion_sagg::Vector{T} | "t dt-1" + # Eroded large aggregates + erosion_lagg::Vector{T} | "t dt-1" + # Transport capacity clay + transport_capacity_clay::Vector{T} | "t dt-1" + # Transport capacity silt + transport_capacity_silt::Vector{T} | "t dt-1" + # Transport capacity sand + transport_capacity_sand::Vector{T} | "t dt-1" + # Transport capacity small aggregates + transport_capacity_sagg::Vector{T} | "t dt-1" + # Transport capacity large aggregates + transport_capacity_lagg::Vector{T} | "t dt-1" +end + +function sediment_land_transport_differentiation_bc(n) + bc = SedimentLandTransportDifferentiationBC(; + erosion_clay = fill(mv, n), + erosion_silt = fill(mv, n), + erosion_sand = fill(mv, n), + erosion_sagg = fill(mv, n), + erosion_lagg = fill(mv, n), + transport_capacity_clay = fill(mv, n), + transport_capacity_silt = fill(mv, n), + transport_capacity_sand = fill(mv, n), + transport_capacity_sagg = fill(mv, n), + transport_capacity_lagg = fill(mv, n), + ) + return bc +end + +@get_units @with_kw struct SedimentLandTransportDifferentiationModel{T} <: + AbstractSedimentLandTransportModel + boundary_conditions::SedimentLandTransportDifferentiationBC{T} | "-" + variables::SedimentLandTransportDifferentiationVars{T} | "-" +end + +function initialize_sediment_land_transport_differentiation_model(inds) + n = length(inds) + vars = sediment_land_transport_differentiation_vars(n) + bc = sediment_land_transport_differentiation_bc(n) + model = SedimentLandTransportDifferentiationModel(; + boundary_conditions = bc, + variables = vars, + ) + return model +end + +function update_bc( + model::SedimentLandTransportDifferentiationModel, + erosion_model::SoilErosionModel, + transport_capacity_model::TransportCapacityYalinDifferentiationModel, +) + (; + erosion_clay, + erosion_silt, + erosion_sand, + erosion_sagg, + erosion_lagg, + transport_capacity_clay, + transport_capacity_silt, + transport_capacity_sand, + transport_capacity_sagg, + transport_capacity_lagg, + ) = model.boundary_conditions + (; clay, silt, sand, sagg, lagg) = erosion_model.variables + @. erosion_clay = clay + @. erosion_silt = silt + @. erosion_sand = sand + @. erosion_sagg = sagg + @. erosion_lagg = lagg + + (; clay, silt, sand, sagg, lagg) = transport_capacity_model.variables + @. transport_capacity_clay = clay + @. transport_capacity_silt = silt + @. transport_capacity_sand = sand + @. transport_capacity_sagg = sagg + @. transport_capacity_lagg = lagg +end + +function update!(model::SedimentLandTransportDifferentiationModel, network) + (; + erosion_clay, + erosion_silt, + erosion_sand, + erosion_sagg, + erosion_lagg, + transport_capacity_clay, + transport_capacity_silt, + transport_capacity_sand, + transport_capacity_sagg, + transport_capacity_lagg, + ) = model.boundary_conditions + (; amount, clay, silt, sand, sagg, lagg) = model.variables + + accucapacityflux!(clay, erosion_clay, network, transport_capacity_clay) + accucapacityflux!(silt, erosion_silt, network, transport_capacity_silt) + accucapacityflux!(sand, erosion_sand, network, transport_capacity_sand) + accucapacityflux!(sagg, erosion_sagg, network, transport_capacity_sagg) + accucapacityflux!(lagg, erosion_lagg, network, transport_capacity_lagg) + amount .= clay .+ silt .+ sand .+ sagg .+ lagg +end \ No newline at end of file diff --git a/src/sediment_transport/transport_capacity.jl b/src/sediment_transport/transport_capacity.jl new file mode 100644 index 000000000..c3fbe41cc --- /dev/null +++ b/src/sediment_transport/transport_capacity.jl @@ -0,0 +1,424 @@ +abstract type AbstractTransportCapacityModel end + +## Total sediment transport capacity structs and functions +@get_units @with_kw struct TransportCapacityModelVars{T} + # Total sediment transport capacity + amount::Vector{T} | "t dt-1" +end + +function transport_capacity_model_vars(n) + vars = TransportCapacityModelVars(; amount = fill(mv, n)) + return vars +end + +@get_units @with_kw struct TransportCapacityBC{T} + # Discharge + q::Vector{T} | "m3 s-1" + # Flow depth + waterlevel::Vector{T} | "m" +end + +function transport_capacity_bc(n) + bc = TransportCapacityBC(; q = fill(mv, n), waterlevel = fill(mv, n)) + return bc +end + +# Common parameters for transport capacity models +@get_units @with_kw struct TransportCapacityGoversParameters{T} + # Drain slope + slope::Vector{T} | "m m-1" + # Particle density + density::Vector{T} | "kg m-3" + # Govers transport capacity coefficient + c_govers::Vector{T} | "-" + # Govers transport capacity exponent + n_govers::Vector{T} | "-" +end + +function initialize_transport_capacity_govers_params(nc, config, inds) + slope = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.slope"; + sel = inds, + defaults = 0.01, + type = Float, + ) + density = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.density"; + sel = inds, + defaults = 2650.0, + type = Float, + ) + c_govers = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.c_govers"; + sel = inds, + defaults = 0.000505, + type = Float, + ) + n_govers = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.n_govers"; + sel = inds, + defaults = 4.27, + type = Float, + ) + tc_parameters = TransportCapacityGoversParameters(; + slope = slope, + density = density, + c_govers = c_govers, + n_govers = n_govers, + ) + + return tc_parameters +end + +@get_units @with_kw struct TransportCapacityGoversModel{T} <: AbstractTransportCapacityModel + boundary_conditions::TransportCapacityBC{T} | "-" + parameters::TransportCapacityGoversParameters{T} | "-" + variables::TransportCapacityModelVars{T} | "-" +end + +function initialize_transport_capacity_govers_model(nc, config, inds) + n = length(inds) + vars = transport_capacity_model_vars(n) + params = initialize_transport_capacity_govers_params(nc, config, inds) + bc = transport_capacity_bc(n) + model = TransportCapacityGoversModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::TransportCapacityGoversModel, width, waterbodies, rivers, ts) + (; q, waterlevel) = model.boundary_conditions + (; slope, density, c_govers, n_govers) = model.parameters + (; amount) = model.variables + + n = length(q) + threaded_foreach(1:n; basesize = 1000) do i + amount[i] = transport_capacity_govers( + q[i], + waterlevel[i], + c_govers[i], + n_govers[i], + density[i], + slope[i], + width[i], + waterbodies[i], + rivers[i], + ts, + ) + end +end + +# Common parameters for transport capacity models +@get_units @with_kw struct TransportCapacityYalinParameters{T} + # Drain slope + slope::Vector{T} | "m m-1" + # Particle density + density::Vector{T} | "kg m-3" + # Particle mean diameter + d50::Vector{T} | "mm" +end + +function initialize_transport_capacity_yalin_params(nc, config, inds) + slope = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.slope"; + sel = inds, + defaults = 0.01, + type = Float, + ) + density = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.density"; + sel = inds, + defaults = 2650.0, + type = Float, + ) + d50 = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.d50"; + sel = inds, + defaults = 0.1, + type = Float, + ) + tc_parameters = + TransportCapacityYalinParameters(; slope = slope, density = density, d50 = d50) + + return tc_parameters +end + +@get_units @with_kw struct TransportCapacityYalinModel{T} <: AbstractTransportCapacityModel + boundary_conditions::TransportCapacityBC{T} | "-" + parameters::TransportCapacityYalinParameters{T} | "-" + variables::TransportCapacityModelVars{T} | "-" +end + +function initialize_transport_capacity_yalin_model(nc, config, inds) + n = length(inds) + vars = transport_capacity_model_vars(n) + params = initialize_transport_capacity_yalin_params(nc, config, inds) + bc = transport_capacity_bc(n) + model = TransportCapacityYalinModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::TransportCapacityYalinModel, width, waterbodies, rivers, ts) + (; q, waterlevel) = model.boundary_conditions + (; slope, density, d50) = model.parameters + (; amount) = model.variables + + n = length(q) + threaded_foreach(1:n; basesize = 1000) do i + amount[i] = transport_capacity_yalin( + q[i], + waterlevel[i], + density[i], + d50[i], + slope[i], + width[i], + waterbodies[i], + rivers[i], + ts, + ) + end +end + +## Total transport capacity with particle differentiation structs and functions +@get_units @with_kw struct TransportCapacityYalinDifferentiationModelVars{T} + # Total sediment transport capacity + amount::Vector{T} | "t dt-1" + # Transport capacity clay + clay::Vector{T} | "t dt-1" + # Transport capacity silt + silt::Vector{T} | "t dt-1" + # Transport capacity sand + sand::Vector{T} | "t dt-1" + # Transport capacity small aggregates + sagg::Vector{T} | "t dt-1" + # Transport capacity large aggregates + lagg::Vector{T} | "t dt-1" +end + +function transport_capacity_yalin_differentiation_model_vars(n) + vars = TransportCapacityYalinDifferentiationModelVars(; + amount = fill(mv, n), + clay = fill(mv, n), + silt = fill(mv, n), + sand = fill(mv, n), + sagg = fill(mv, n), + lagg = fill(mv, n), + ) + return vars +end + +# Common parameters for transport capacity models +@get_units @with_kw struct TransportCapacityYalinDifferentiationParameters{T} + # Drain slope + slope::Vector{T} | "m m-1" + # Particle density + density::Vector{T} | "kg m-3" + # Clay mean diameter + dm_clay::Vector{T} | "µm" + # Silt mean diameter + dm_silt::Vector{T} | "µm" + # Sand mean diameter + dm_sand::Vector{T} | "µm" + # Small aggregates mean diameter + dm_sagg::Vector{T} | "µm" + # Large aggregates mean diameter + dm_lagg::Vector{T} | "µm" +end + +function initialize_transport_capacity_yalin_diff_params(nc, config, inds) + slope = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.slope"; + sel = inds, + defaults = 0.01, + type = Float, + ) + density = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.density"; + sel = inds, + defaults = 2650.0, + type = Float, + ) + dm_clay = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.dm_clay"; + sel = inds, + defaults = 2.0, + type = Float, + ) + dm_silt = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.dm_silt"; + sel = inds, + defaults = 10.0, + type = Float, + ) + dm_sand = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.dm_sand"; + sel = inds, + defaults = 200.0, + type = Float, + ) + dm_sagg = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.dm_sagg"; + sel = inds, + defaults = 30.0, + type = Float, + ) + dm_lagg = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.dm_lagg"; + sel = inds, + defaults = 500.0, + type = Float, + ) + tc_parameters = TransportCapacityYalinDifferentiationParameters(; + slope = slope, + density = density, + dm_clay = dm_clay, + dm_silt = dm_silt, + dm_sand = dm_sand, + dm_sagg = dm_sagg, + dm_lagg = dm_lagg, + ) + + return tc_parameters +end + +@get_units @with_kw struct TransportCapacityYalinDifferentiationModel{T} <: + AbstractTransportCapacityModel + boundary_conditions::TransportCapacityBC{T} | "-" + parameters::TransportCapacityYalinDifferentiationParameters{T} | "-" + variables::TransportCapacityYalinDifferentiationModelVars{T} | "-" +end + +function initialize_transport_capacity_yalin_diff_model(nc, config, inds) + n = length(inds) + vars = transport_capacity_yalin_differentiation_model_vars(n) + params = initialize_transport_capacity_yalin_diff_params(nc, config, inds) + bc = transport_capacity_bc(n) + model = TransportCapacityYalinDifferentiationModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!( + model::TransportCapacityYalinDifferentiationModel, + width, + waterbodies, + rivers, + ts, +) + (; q, waterlevel) = model.boundary_conditions + (; slope, density, dm_clay, dm_silt, dm_sand, dm_sagg, dm_lagg) = model.parameters + (; amount, clay, silt, sand, sagg, lagg) = model.variables + + n = length(q) + threaded_foreach(1:n; basesize = 1000) do i + dtot = transportability_yalin_differentiation( + waterlevel[i], + density[i], + dm_clay[i], + dm_silt[i], + dm_sand[i], + dm_sagg[i], + dm_lagg[i], + slope[i], + ) + clay[i] = transport_capacity_yalin_differentiation( + q[i], + waterlevel[i], + density[i], + dm_clay[i], + slope[i], + width[i], + waterbodies[i], + rivers[i], + dtot, + ts, + ) + silt[i] = transport_capacity_yalin_differentiation( + q[i], + waterlevel[i], + density[i], + dm_silt[i], + slope[i], + width[i], + waterbodies[i], + rivers[i], + dtot, + ts, + ) + sand[i] = transport_capacity_yalin_differentiation( + q[i], + waterlevel[i], + density[i], + dm_sand[i], + slope[i], + width[i], + waterbodies[i], + rivers[i], + dtot, + ts, + ) + sagg[i] = transport_capacity_yalin_differentiation( + q[i], + waterlevel[i], + density[i], + dm_sagg[i], + slope[i], + width[i], + waterbodies[i], + rivers[i], + dtot, + ts, + ) + lagg[i] = transport_capacity_yalin_differentiation( + q[i], + waterlevel[i], + density[i], + dm_lagg[i], + slope[i], + width[i], + waterbodies[i], + rivers[i], + dtot, + ts, + ) + amount[i] = clay[i] + silt[i] + sand[i] + sagg[i] + lagg[i] + end +end \ No newline at end of file diff --git a/src/sediment_transport/transport_capacity_process.jl b/src/sediment_transport/transport_capacity_process.jl new file mode 100644 index 000000000..7a900bded --- /dev/null +++ b/src/sediment_transport/transport_capacity_process.jl @@ -0,0 +1,287 @@ +""" + mask_transport_capacity( + transport_capacity, + waterbodies, + rivers, + ) + +Mask transport capacity values for waterbodies and rivers. + +# Arguments +- `transport_capacity` (total sediment transport capacity [t dt-1]) +- `waterbodies` (waterbodies mask [-]) +- `rivers` (rivers mask [-]) + +# Output +- `transport_capacity` (masked total sediment transport capacity [t dt-1]) +""" +function mask_transport_capacity(transport_capacity, waterbodies, rivers) + # Full deposition in rivers to switch to river concept + if rivers + tc = 0.0 + # Sediment flux in waterbodies will all reach the river + elseif waterbodies + tc = 1e9 + else + tc = transport_capacity + end + + return tc +end + +""" + transport_capacity_govers( + q, + waterlevel, + c_govers, + n_govers, + density, + slope, + width, + waterbodies, + rivers, + ts, + ) + +Total sediment transport capacity based on Govers. + +# Arguments +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `c_govers` (Govers transport capacity coefficient [-]) +- `n_govers` (Govers transport capacity exponent [-]) +- `density` (sediment density [kg m-3]) +- `slope` (slope [-]) +- `width` (drain width [m]) +- `waterbodies` (waterbodies mask [-]) +- `rivers` (rivers mask [-]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function transport_capacity_govers( + q, + waterlevel, + c_govers, + n_govers, + density, + slope, + width, + waterbodies, + rivers, + ts, +) + # Transport capacity from govers 1990 + sinslope = sin(atan(slope)) #slope in radians + # Unit stream power + if waterlevel > 0.0 + velocity = q / (width * waterlevel) #m/s + else + velocity = 0.0 + end + omega = 10 * sinslope * 100 * velocity #cm/s + if omega > 0.4 + TCf = c_govers * (omega - 0.4)^(n_govers) * density #kg/m3 + else + TCf = 0.0 + end + transport_capacity = TCf * q * ts * 1e-3 #[ton/cell] + + # Mask transport capacity values for waterbodies and rivers + transport_capacity = mask_transport_capacity(transport_capacity, waterbodies, rivers) + + return transport_capacity +end + +""" + transport_capacity_yalin( + q, + waterlevel, + density, + d50, + slope, + width, + waterbodies, + rivers, + ts, + ) + +Total sediment transport capacity based on Yalin. + +# Arguments +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `density` (sediment density [kg m-3]) +- `d50` (median grain size [m]) +- `slope` (slope [-]) +- `width` (drain width [m]) +- `waterbodies` (waterbodies mask [-]) +- `rivers` (rivers mask [-]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function transport_capacity_yalin( + q, + waterlevel, + density, + d50, + slope, + width, + waterbodies, + rivers, + ts, +) + sinslope = sin(atan(slope)) #slope in radians + # Transport capacity from Yalin without particle differentiation + delta = + max((waterlevel * sinslope / (d50 * 0.001 * (density / 1000 - 1)) / 0.06 - 1), 0.0) + alphay = delta * 2.45 / (0.001 * density)^0.4 * 0.06^(0.5) + if q > 0.0 && alphay != 0.0 + TC = ( + width / q * + (density - 1000) * + d50 * + 0.001 * + (9.81 * waterlevel * sinslope) * + 0.635 * + delta * + (1 - log(1 + alphay) / (alphay)) + ) # [kg/m3] + transport_capacity = TC * q * ts * 1e-3 #[ton] + else + transport_capacity = 0.0 + end + + # Mask transport capacity values for waterbodies and rivers + transport_capacity = mask_transport_capacity(transport_capacity, waterbodies, rivers) + + return transport_capacity +end + +""" + transportability_yalin_differentiation( + waterlevel, + density, + dm_clay, + dm_silt, + dm_sand, + dm_sagg, + dm_lagg, + slope, + ) + +Total flow transportability based on Yalin with particle differentiation. + +# Arguments +- `waterlevel` (water level [m]) +- `density` (sediment density [kg m-3]) +- `dm_clay` (clay median grain size [m]) +- `dm_silt` (silt median grain size [m]) +- `dm_sand` (sand median grain size [m]) +- `dm_sagg` (small aggregates median grain size [m]) +- `dm_lagg` (large aggregates median grain size [m]) +- `slope` (slope [-]) + +# Output +- `dtot` (total transportability of the flow [-]) +""" +function transportability_yalin_differentiation( + waterlevel, + density, + dm_clay, + dm_silt, + dm_sand, + dm_sagg, + dm_lagg, + slope, +) + sinslope = sin(atan(slope)) #slope in radians + # Delta parameter of Yalin for each particle class + delta = waterlevel * sinslope / (1e-6 * (density / 1000 - 1)) / 0.06 + dclay = max(1 / dm_clay * delta - 1, 0.0) + dsilt = max(1 / dm_silt * delta - 1, 0.0) + dsand = max(1 / dm_sand * delta - 1, 0.0) + dsagg = max(1 / dm_sagg * delta - 1, 0.0) + dlagg = max(1 / dm_lagg * delta - 1, 0.0) + # Total transportability + dtot = dclay + dsilt + dsand + dsagg + dlagg + + return dtot +end + +""" + transport_capacity_yalin_differentiation( + q, + waterlevel, + density, + dm, + slope, + width, + waterbodies, + rivers, + dtot, + ts, + ) + +Transport capacity for a specific grain size based on Yalin with particle differentiation. + +# Arguments +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `density` (sediment density [kg m-3]) +- `dm` (median grain size [m]) +- `slope` (slope [-]) +- `width` (drain width [m]) +- `waterbodies` (waterbodies mask [-]) +- `rivers` (rivers mask [-]) +- `dtot` (total flow transportability [t dt-1]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function transport_capacity_yalin_differentiation( + q, + waterlevel, + density, + dm, + slope, + width, + waterbodies, + rivers, + dtot, + ts, +) + sinslope = sin(atan(slope)) #slope in radians + # Transport capacity from Yalin with particle differentiation + # Delta parameter of Yalin for the specific particle class + delta = waterlevel * sinslope / (1e-6 * (density / 1000 - 1)) / 0.06 + d_part = max(1 / dm * delta - 1, 0.0) + + if q > 0.0 + TCa = width / q * (density - 1000) * 1e-6 * (9.81 * waterlevel * sinslope) + else + TCa = 0.0 + end + + TCb = 2.45 / (0.001 * density)^0.4 * 0.06^0.5 + + if dtot != 0.0 && d_part != 0.0 + TC = + TCa * dm * d_part / dtot * + 0.635 * + d_part * + (1 - log(1 + d_part * TCb) / d_part * TCb) # [kg/m3] + transport_capacity = TC * q * ts * 1e-3 #[ton] + else + transport_capacity = 0.0 + end + + # Mask transport capacity values for waterbodies and rivers + transport_capacity = mask_transport_capacity(transport_capacity, waterbodies, rivers) + + return transport_capacity +end \ No newline at end of file diff --git a/test/run_sediment.jl b/test/run_sediment.jl index 16c60593e..3c5fcf9d4 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -20,8 +20,6 @@ model = Wflow.run_timestep(model) @test eros.rainfall_erosion.variables.amount[1] ≈ 0.00027245577922893746f0 @test model.clock.iteration == 1 #@test mean(eros.leaf_area_index) ≈ 1.7120018886212223f0 - #@test eros.dmsand[1] == 200.0f0 - #@test eros.dmlagg[1] == 500.0f0 #@test mean(eros.interception) ≈ 0.4767846753916875f0 @test mean(eros.overland_flow_erosion.variables.amount) ≈ 0.00861079076689589f0 @test mean(eros.rainfall_erosion.variables.amount) ≈ 0.00016326203201620437f0 @@ -40,26 +38,29 @@ model = Wflow.run_timestep(model) @test mean(eros.soil_erosion.variables.sand) ≈ 0.026301393837924607f0 @test mean(eros.soil_erosion.variables.lagg) ≈ 0.022577957752547836f0 @test mean(eros.soil_erosion.variables.sagg) ≈ 0.022874695590802723f0 - #@test mean(eros.TCsed) == 0.0 - #@test mean(eros.TCsilt) ≈ 1.0988158364353527f6 - #@test mean(eros.TCsand) ≈ 1.0987090622888755f6 - #@test mean(eros.TCclay) ≈ 1.0992655197016734f6 - #@test mean(eros.TCsilt) ≈ 1.0988158364353527f6 end -# @testset "second timestep sediment model (lateral)" begin -# lat = model.lateral +@testset "second timestep sediment model (lateral)" begin + land = model.lateral.land -# @test mean(lat.land.inlandsed) ≈ 0.07463801685030906f0 -# @test mean(lat.land.inlandclay) ≈ 0.0022367786781657497f0 -# @test mean(lat.land.inlandsand) ≈ 0.02519222037812127f0 -# @test mean(lat.land.olclay) ≈ 0.006443036462118322f0 + @test land.transport_capacity.parameters.dm_sand[1] == 200.0f0 + @test land.transport_capacity.parameters.dm_lagg[1] == 500.0f0 -# @test mean(lat.river.SSconc) ≈ 0.8259993252994058f0 -# @test mean(lat.river.inlandclay) ≈ 0.01980468760667709f0 -# @test lat.river.h_riv[network.river.order[end]] ≈ 0.006103649735450745f0 -# @test lat.river.outclay[5649] ≈ 2.359031898208781f-9 -# end + @test mean(land.transport_capacity.boundary_conditions.q) ≈ 0.006879398771052133f0 + @test mean(land.transport_capacity.variables.silt) ≈ 1.0988158364353527f6 + @test mean(land.transport_capacity.variables.sand) ≈ 1.0987090622888755f6 + @test mean(land.transport_capacity.variables.clay) ≈ 1.0992655197016734f6 + + @test mean(land.to_river.variables.amount) ≈ 0.07463801685030906f0 + @test mean(land.to_river.variables.clay) ≈ 0.0022367786781657497f0 + @test mean(land.to_river.variables.sand) ≈ 0.02519222037812127f0 + @test mean(land.sediment_flux.variables.clay) ≈ 0.006443036462118322f0 + + # @test mean(lat.river.SSconc) ≈ 0.8259993252994058f0 + # @test mean(lat.river.inlandclay) ≈ 0.01980468760667709f0 + # @test lat.river.h_riv[network.river.order[end]] ≈ 0.006103649735450745f0 + # @test lat.river.outclay[5649] ≈ 2.359031898208781f-9 +end # @testset "Exchange and grid location sediment" begin # @test Wflow.exchange(model.vertical, :n) == 0 diff --git a/test/runtests.jl b/test/runtests.jl index cd8782d7e..e55b1658b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -79,21 +79,21 @@ include("testing_utils.jl") with_logger(NullLogger()) do ## run all tests @testset "Wflow.jl" begin - include("horizontal_process.jl") - include("io.jl") - include("vertical_process.jl") - include("reservoir_lake.jl") - include("run_sbm.jl") - include("run_sbm_piave.jl") - include("run_sbm_gwf_piave.jl") - include("run_sbm_gwf.jl") - include("run.jl") - include("groundwater.jl") - include("utils.jl") - include("bmi.jl") + #include("horizontal_process.jl") + #include("io.jl") + #include("vertical_process.jl") + #include("reservoir_lake.jl") + #include("run_sbm.jl") + #include("run_sbm_piave.jl") + #include("run_sbm_gwf_piave.jl") + #include("run_sbm_gwf.jl") + #include("run.jl") + #include("groundwater.jl") + #include("utils.jl") + #include("bmi.jl") include("run_sediment.jl") - include("subdomains.jl") + #include("subdomains.jl") - Aqua.test_all(Wflow; ambiguities = false) + #Aqua.test_all(Wflow; ambiguities = false) end end diff --git a/test/sediment_config.toml b/test/sediment_config.toml index 946f0786f..ac2dd05ee 100644 --- a/test/sediment_config.toml +++ b/test/sediment_config.toml @@ -47,14 +47,18 @@ gauges = "wflow_gauges" ldd = "wflow_ldd" river_location = "wflow_river" subcatchment = "wflow_subcatch" +reservoir_areas = "wflow_reservoirareas" +lake_areas = "wflow_lakeareas" # 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.hydrometeo_forcing.waterlevel_land", #"vertical.interception", "vertical.hydrometeo_forcing.precipitation", + "vertical.hydrometeo_forcing.waterlevel_land", + "lateral.land.hydrometeo_forcing.waterlevel_land", "vertical.hydrometeo_forcing.q_land", + "lateral.land.hydrometeo_forcing.q_land", "lateral.river.h_riv", "lateral.river.q_riv", ] @@ -69,12 +73,7 @@ forcing = [ #storage_wood = "Swood" ## other parameters pathfrac = "PathFrac" -rivcell = "wflow_river" slope = "Slope" -# Reservoir -resareas = "wflow_reservoirareas" -# Lake -lakeareas = "wflow_lakeareas" [input.vertical.hydrometeo_forcing] precipitation = "P" @@ -102,8 +101,21 @@ sand_fraction = "fsand_soil" sagg_fraction = "fsagg_soil" lagg_fraction = "flagg_soil" -[input.lateral.land] +[input.lateral.land.hydrometeo_forcing] +waterlevel_land = "levKinL" +q_land = "runL" + +[input.lateral.land.transport_capacity.parameters] slope = "Slope" +density = "sediment_density" +d50 = "d50_soil" +c_govers = "c_govers" +n_govers = "n_govers" +dm_clay = "dm_clay" +dm_silt = "dm_silt" +dm_sand = "dm_sand" +dm_sagg = "dm_sagg" +dm_lagg = "dm_lagg" [input.lateral.river] h_riv = "h" @@ -152,13 +164,17 @@ amount = "overland_flow_erosion" amount = "soilloss" clay = "erosclay" -# [output.lateral.land] -# #TCclay = "TCclay" -# #TCsed = "TCsed" -# inlandclay = "inlandclay" -# inlandsed = "inlandsed" -# olclay = "olclay" -# olsed = "olsed" +[output.lateral.land.transport_capacity.variables] +clay = "TCclay" +amount = "TCsed" + +[output.lateral.land.to_river.variables] +clay = "inlandclay" +amount = "inlandsed" + +[output.lateral.land.sediment_flux.variables] +clay = "olclay" +amount = "olsed" # [output.lateral.river] # Bedconc = "Bedconc" @@ -207,16 +223,16 @@ parameter = "vertical.hydrometeo_forcing.q_land" # coordinate.x = 6.931 # coordinate.y = 48.085 # header = "TCsed" -# parameter = "vertical.TCsed" +# parameter = "lateral.land.transport_capacity.variables.amount" # [[csv.column]] # coordinate.x = 6.931 # coordinate.y = 48.085 # header = "TCclay" -# parameter = "vertical.TCclay" +# parameter = "lateral.land.transport_capacity.variables.clay" # [[csv.column]] # coordinate.x = 6.931 # coordinate.y = 48.085 # header = "inlandsed" -# parameter = "lateral.land.inlandsed" +# parameter = "lateral.land.to_river.variables.amount" From 4f0383e5861247d29cf15666234dd6f8a7a63bd1 Mon Sep 17 00:00:00 2001 From: hboisgon Date: Fri, 20 Sep 2024 09:54:52 +0800 Subject: [PATCH 10/42] working refactor of lateral land sediment --- src/sediment_model.jl | 4 +- src/sediment_transport/land_to_river.jl | 14 +++--- .../overland_flow_transport.jl | 49 +++++++++++++++++-- test/run_sediment.jl | 8 +-- test/sediment_config.toml | 2 +- 5 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/sediment_model.jl b/src/sediment_model.jl index 141c4a526..3ea45f293 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -52,8 +52,8 @@ function initialize_sediment_model(config::Config) soilloss = initialize_soil_loss(nc, config, inds, area, landslope) # Get waterbodies mask - do_reservoirs = get(config.model, "reservoirs", false)::Bool - do_lakes = get(config.model, "lakes", false)::Bool + do_reservoirs = get(config.model, "doreservoir", false)::Bool + do_lakes = get(config.model, "dolake", false)::Bool waterbodies = fill(0.0, n) if do_reservoirs reservoirs = ncread( diff --git a/src/sediment_transport/land_to_river.jl b/src/sediment_transport/land_to_river.jl index 5457295e3..f1910c993 100644 --- a/src/sediment_transport/land_to_river.jl +++ b/src/sediment_transport/land_to_river.jl @@ -36,8 +36,7 @@ end function update_bc(model::SedimentToRiverModel, transport_model::SedimentLandTransportModel) (; deposition) = model.boundary_conditions - (; amount) = transport_model.variables - @. deposition = amount + @. deposition = transport_model.variables.deposition end function update!(model::SedimentToRiverModel, rivers) @@ -126,12 +125,11 @@ function update_bc( deposition_sagg, deposition_lagg, ) = model.boundary_conditions - (; clay, silt, sand, sagg, lagg) = transport_model.variables - @. deposition_clay = clay - @. deposition_silt = silt - @. deposition_sand = sand - @. deposition_sagg = sagg - @. deposition_lagg = lagg + @. deposition_clay = transport_model.variables.deposition_clay + @. deposition_silt = transport_model.variables.deposition_silt + @. deposition_sand = transport_model.variables.deposition_sand + @. deposition_sagg = transport_model.variables.deposition_sagg + @. deposition_lagg = transport_model.variables.deposition_lagg end function update!(model::SedimentToRiverDifferentiationModel, rivers) diff --git a/src/sediment_transport/overland_flow_transport.jl b/src/sediment_transport/overland_flow_transport.jl index 94d733c8e..89113effb 100644 --- a/src/sediment_transport/overland_flow_transport.jl +++ b/src/sediment_transport/overland_flow_transport.jl @@ -2,12 +2,13 @@ abstract type AbstractSedimentLandTransportModel end ## Total sediment transport in overland flow structs and functions @get_units @with_kw struct SedimentLandTransportVars{T} - # Total sediment reaching the river + # Total sediment flux amount::Vector{T} | "t dt-1" + deposition::Vector{T} | "t dt-1" end function sediment_land_transport_vars(n) - vars = SedimentLandTransportVars(; amount = fill(mv, n)) + vars = SedimentLandTransportVars(; amount = fill(mv, n), deposition = fill(mv, n)) return vars end @@ -52,35 +53,54 @@ end function update!(model::SedimentLandTransportModel, network) (; erosion, transport_capacity) = model.boundary_conditions - (; amount) = model.variables + (; amount, deposition) = model.variables accucapacityflux!(amount, erosion, network, transport_capacity) + deposition .= erosion end ## Total transport capacity with particle differentiation structs and functions @get_units @with_kw struct SedimentLandTransportDifferentiationVars{T} # Total sediment flux amount::Vector{T} | "t dt-1" + # Deposition + deposition::Vector{T} | "t dt-1" # Clay flux clay::Vector{T} | "t dt-1" + # Deposition clay + deposition_clay::Vector{T} | "t dt-1" # Silt silt::Vector{T} | "t dt-1" + # Deposition silt + deposition_silt::Vector{T} | "t dt-1" # Sand flux sand::Vector{T} | "t dt-1" + # Deposition sand + deposition_sand::Vector{T} | "t dt-1" # Small aggregates flux sagg::Vector{T} | "t dt-1" + # Deposition small aggregates + deposition_sagg::Vector{T} | "t dt-1" # Large aggregates flux lagg::Vector{T} | "t dt-1" + # Deposition large aggregates + deposition_lagg::Vector{T} | "t dt-1" end function sediment_land_transport_differentiation_vars(n) vars = SedimentLandTransportDifferentiationVars(; amount = fill(mv, n), + deposition = fill(mv, n), clay = fill(mv, n), + deposition_clay = fill(mv, n), silt = fill(mv, n), + deposition_silt = fill(mv, n), sand = fill(mv, n), + deposition_sand = fill(mv, n), sagg = fill(mv, n), + deposition_sagg = fill(mv, n), lagg = fill(mv, n), + deposition_lagg = fill(mv, n), ) return vars end @@ -186,12 +206,33 @@ function update!(model::SedimentLandTransportDifferentiationModel, network) transport_capacity_sagg, transport_capacity_lagg, ) = model.boundary_conditions - (; amount, clay, silt, sand, sagg, lagg) = model.variables + (; + amount, + deposition, + clay, + deposition_clay, + silt, + deposition_silt, + sand, + deposition_sand, + sagg, + deposition_sagg, + lagg, + deposition_lagg, + ) = model.variables accucapacityflux!(clay, erosion_clay, network, transport_capacity_clay) + deposition_clay .= erosion_clay accucapacityflux!(silt, erosion_silt, network, transport_capacity_silt) + deposition_silt .= erosion_silt accucapacityflux!(sand, erosion_sand, network, transport_capacity_sand) + deposition_sand .= erosion_sand accucapacityflux!(sagg, erosion_sagg, network, transport_capacity_sagg) + deposition_sagg .= erosion_sagg accucapacityflux!(lagg, erosion_lagg, network, transport_capacity_lagg) + deposition_lagg .= erosion_lagg amount .= clay .+ silt .+ sand .+ sagg .+ lagg + deposition .= + deposition_clay .+ deposition_silt .+ deposition_sand .+ deposition_sagg .+ + deposition_lagg end \ No newline at end of file diff --git a/test/run_sediment.jl b/test/run_sediment.jl index 3c5fcf9d4..dce0b4d02 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -51,10 +51,10 @@ end @test mean(land.transport_capacity.variables.sand) ≈ 1.0987090622888755f6 @test mean(land.transport_capacity.variables.clay) ≈ 1.0992655197016734f6 - @test mean(land.to_river.variables.amount) ≈ 0.07463801685030906f0 - @test mean(land.to_river.variables.clay) ≈ 0.0022367786781657497f0 - @test mean(land.to_river.variables.sand) ≈ 0.02519222037812127f0 - @test mean(land.sediment_flux.variables.clay) ≈ 0.006443036462118322f0 + @test mean(land.to_river.variables.amount) ≈ 0.07624135182616738f0 + @test mean(land.to_river.variables.clay) ≈ 0.002285341387958068f0 + @test mean(land.to_river.variables.sand) ≈ 0.02575351604673049f0 + @test mean(land.sediment_flux.variables.clay) ≈ 0.006578791733506439f0 # @test mean(lat.river.SSconc) ≈ 0.8259993252994058f0 # @test mean(lat.river.inlandclay) ≈ 0.01980468760667709f0 diff --git a/test/sediment_config.toml b/test/sediment_config.toml index ac2dd05ee..48abb84db 100644 --- a/test/sediment_config.toml +++ b/test/sediment_config.toml @@ -143,7 +143,7 @@ lakelocs = "wflow_lakelocs" [model] dolake = false -doreservoir = true +doreservoir = true # cannot use reservoirs as in sbm because then states/volume need to be added landtransportmethod = "yalinpart" # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] rainerosmethod = "answers" # Rainfall erosion equation: ["answers", "eurosem"] reinit = true From b2413040f91c2760a007e9ff01c8ebfdb018fc4d Mon Sep 17 00:00:00 2001 From: hboisgon Date: Mon, 23 Sep 2024 17:50:08 +0800 Subject: [PATCH 11/42] draft refactor river sediment --- src/Wflow.jl | 8 +- src/erosion.jl | 11 +- src/erosion/erosion_process.jl | 96 ++ src/erosion/overland_flow_erosion.jl | 17 +- src/erosion/river_erosion.jl | 81 ++ src/geometry.jl | 79 ++ src/sediment_flux.jl | 139 ++- src/sediment_model.jl | 69 +- src/sediment_transport/deposition.jl | 49 + src/sediment_transport/river_transport.jl | 867 ++++++++++++++++++ src/sediment_transport/transport_capacity.jl | 336 ++++++- .../transport_capacity_process.jl | 361 ++++++++ src/states.jl | 45 +- test/run_sediment.jl | 10 +- test/sediment_config.toml | 125 ++- 15 files changed, 2143 insertions(+), 150 deletions(-) create mode 100644 src/erosion/river_erosion.jl create mode 100644 src/geometry.jl create mode 100644 src/sediment_transport/deposition.jl create mode 100644 src/sediment_transport/river_transport.jl diff --git a/src/Wflow.jl b/src/Wflow.jl index 092225cf4..a7ee89f8a 100644 --- a/src/Wflow.jl +++ b/src/Wflow.jl @@ -108,11 +108,12 @@ struct SedimentModel end # "sediment" type / sediment_model.jl Base.show(io::IO, m::Model) = print(io, "model of type ", typeof(m)) include("forcing.jl") +include("geometry.jl") include("horizontal_process.jl") include("flow.jl") include("water_demand.jl") include("sbm.jl") -include("sediment.jl") +#include("sediment.jl") include("reservoir_lake.jl") include("sbm_model.jl") include("sediment_model.jl") @@ -121,11 +122,14 @@ include("erosion/erosion_process.jl") include("erosion/rainfall_erosion.jl") include("erosion/overland_flow_erosion.jl") include("erosion/soil_erosion.jl") -include("sediment_flux.jl") +include("erosion/river_erosion.jl") +include("sediment_transport/deposition.jl") include("sediment_transport/transport_capacity_process.jl") include("sediment_transport/transport_capacity.jl") include("sediment_transport/overland_flow_transport.jl") include("sediment_transport/land_to_river.jl") +include("sediment_transport/river_transport.jl") +include("sediment_flux.jl") include("vertical_process.jl") include("groundwater/connectivity.jl") include("groundwater/aquifer.jl") diff --git a/src/erosion.jl b/src/erosion.jl index 8b328bd9f..18ad50797 100644 --- a/src/erosion.jl +++ b/src/erosion.jl @@ -1,14 +1,15 @@ @get_units @with_kw struct SoilLoss{RE, OLE, SE, T} hydrometeo_forcing::HydrometeoForcing | "-" + geometry::LandGeometry | "-" rainfall_erosion::RE | "-" overland_flow_erosion::OLE | "-" soil_erosion::SE | "-" - area::Vector{T} | "m²" end -function initialize_soil_loss(nc, config, inds, area, slope) +function initialize_soil_loss(nc, config, inds) n = length(inds) hydrometeo_forcing = initialize_hydrometeo_forcing(n) + geometry = initialize_land_geometry(nc, config, inds) # Rainfall erosion rainfallerosionmodel = get(config.model, "rainfall_erosion", "answers")::String if rainfallerosionmodel == "answers" @@ -23,7 +24,7 @@ function initialize_soil_loss(nc, config, inds, area, slope) overlandflowerosionmodel = get(config.model, "overland_flow_erosion", "answers")::String if overlandflowerosionmodel == "answers" overland_flow_erosion_model = - initialize_answers_overland_flow_erosion_model(nc, config, inds, slope) + initialize_answers_overland_flow_erosion_model(nc, config, inds) else error("Unknown overland flow erosion model: $overlandflowerosionmodel") # overland_flow_erosion_model = NoOverlandFlowErosionModel() @@ -39,10 +40,10 @@ function initialize_soil_loss(nc, config, inds, area, slope) Float, }(; hydrometeo_forcing = hydrometeo_forcing, + geometry = geometry, rainfall_erosion = rainfall_erosion_model, overland_flow_erosion = overland_flow_erosion_model, soil_erosion = soil_erosion_model, - area = area, ) return soil_loss end @@ -55,7 +56,7 @@ function update!(model::SoilLoss, dt) # Rainfall erosion update!(model.rainfall_erosion, model.hydrometeo_forcing, model.area, ts) # Overland flow erosion - update!(model.overland_flow_erosion, model.hydrometeo_forcing, model.area, ts) + update!(model.overland_flow_erosion, model.hydrometeo_forcing, model.geometry, ts) # Total soil erosion and particle differentiation re = get_rainfall_erosion(model.rainfall_erosion) ole = get_overland_flow_erosion(model.overland_flow_erosion) diff --git a/src/erosion/erosion_process.jl b/src/erosion/erosion_process.jl index 00a3d5415..eaf9d184c 100644 --- a/src/erosion/erosion_process.jl +++ b/src/erosion/erosion_process.jl @@ -201,4 +201,100 @@ function total_soil_erosion( sand_erosion, sagg_erosion, lagg_erosion +end + +""" + function river_erosion_julian_torres( + waterlevel, + d50, + width, + length, + slope, + ts, + ) + +River erosion model based on Julian Torres. +Repartition of the effective shear stress between the bank and the bed from Knight et al. 1984 [%] + +# Arguments +- `waterlevel` (water level [m]) +- `d50` (median grain size [m]) +- `width` (width [m]) +- `length` (length [m]) +- `slope` (slope [-]) +- `ts` (timestep [seconds]) + +# Output +- `bed` (potential river erosion [t Δt⁻¹]) +- `bank` (potential bank erosion [t Δt⁻¹]) +""" +function river_erosion_julian_torres(waterlevel, d50, width, length, slope, ts) + if waterlevel > 0.0 + # Bed and Bank from Shields diagram, Da Silva & Yalin (2017) + E_ = (2.65 - 1) * 9.81 + E = (E_ * (d50 * 1e-3)^3 / 1e-12)^0.33 + TCrbed = + E_ * + d50 * + (0.13 * E^(-0.392) * exp(-0.015 * E^2) + 0.045 * (1 - exp(-0.068 * E))) + TCrbank = TCrbed + # kd from Hanson & Simon 2001 + kdbank = 0.2 * TCrbank^(-0.5) * 1e-6 + kdbed = 0.2 * TCrbed^(-0.5) * 1e-6 + + # Hydraulic radius of the river [m] (rectangular channel) + hydrad = waterlevel * width / (width + 2 * waterlevel) + + # Repartition of the effective shear stress between the bank and the Bed + SFbank = exp(-3.23 * log10(width / waterlevel + 3) + 6.146) + # Effective shear stress on river bed and banks [N/m2] + TEffbank = + 1000 * 9.81 * hydrad * slope * SFbank / 100 * (1 + width / (2 * waterlevel)) + TEffbed = + 1000 * 9.81 * hydrad * slope * (1 - SFbank / 100) * (1 + 2 * waterlevel / width) + + # Potential erosion rates of the bed and bank [t/cell/timestep] + #(assuming only one bank is eroding) + Tex = max(TEffbank - TCrbank, 0.0) + # 1.4 is bank default bulk density + ERbank = kdbank * Tex * length * waterlevel * 1.4 * ts + # 1.5 is bed default bulk density + ERbed = kdbed * (TEffbed - TCrbed) * length * width * 1.5 * ts + + # Potential maximum bed/bank erosion + bed = max(ERbed, 0.0) + bank = max(ERbank, 0.0) + + else + bed = 0.0 + bank = 0.0 + end + + return bed, bank +end + +""" + function river_erosion_store( + excess_sediment, + store, + ) + +River erosion of the previously deposited sediment. + +# Arguments +- `excess_sediment` (excess sediment [t Δt⁻¹]) +- `store` (sediment store [t]) + +# Output +- `erosion` (river erosion [t Δt⁻¹]) +- `excess_sediment` (updated excess sediment [t Δt⁻¹]) +- `store` (updated sediment store [t]) +""" +function river_erosion_store(excess_sediment, store) + # River erosion of the previously deposited sediment + erosion = min(store, excess_sediment) + # Update the excess sediment and the sediment store + excess_sediment -= erosion + store -= erosion + return erosion, excess_sediment, store end \ No newline at end of file diff --git a/src/erosion/overland_flow_erosion.jl b/src/erosion/overland_flow_erosion.jl index 43a4da684..567c36757 100644 --- a/src/erosion/overland_flow_erosion.jl +++ b/src/erosion/overland_flow_erosion.jl @@ -21,8 +21,6 @@ end usle_c::Vector{T} | "-" # Answers overland flow factor answers_k::Vector{T} | "-" - # slope - slope::Vector{T} | "-" end @get_units @with_kw struct OverlandFlowErosionAnswersModel{T} <: @@ -31,7 +29,7 @@ end variables::OverlandFlowErosionModelVars{T} | "-" end -function initialize_answers_params_overland_flow(nc, config, inds, slope) +function initialize_answers_params_overland_flow(nc, config, inds) usle_k = ncread( nc, config, @@ -60,15 +58,14 @@ function initialize_answers_params_overland_flow(nc, config, inds, slope) usle_k = usle_k, usle_c = usle_c, answers_k = answers_k, - slope = slope, ) return answers_parameters end -function initialize_answers_overland_flow_erosion_model(nc, config, inds, slope) +function initialize_answers_overland_flow_erosion_model(nc, config, inds) n = length(inds) vars = overland_flow_erosion_model_vars(n) - params = initialize_answers_params_overland_flow(nc, config, inds, slope) + params = initialize_answers_params_overland_flow(nc, config, inds) model = OverlandFlowErosionAnswersModel(; parameters = params, variables = vars) return model end @@ -76,11 +73,11 @@ end function update!( model::OverlandFlowErosionAnswersModel, hydrometeo_forcing::HydrometeoForcing, - area, + geometry::LandGeometry, ts, ) (; q_land) = hydrometeo_forcing - (; usle_k, usle_c, answers_k, slope) = model.parameters + (; usle_k, usle_c, answers_k) = model.parameters (; amount) = model.variables n = length(q_land) @@ -90,8 +87,8 @@ function update!( usle_k[i], usle_c[i], answers_k[i], - slope[i], - area[i], + geometry.slope[i], + geometry.area[i], ts, ) end diff --git a/src/erosion/river_erosion.jl b/src/erosion/river_erosion.jl new file mode 100644 index 000000000..d7d0a52bc --- /dev/null +++ b/src/erosion/river_erosion.jl @@ -0,0 +1,81 @@ +abstract type AbstractRiverErosionModel end + +## Potential direct river erosion structs and functions +@get_units @with_kw struct RiverErosionModelVars{T} + # Potential river bed erosion + bed::Vector{T} | "t dt-1" + # Potential river bank erosion + bank::Vector{T} | "t dt-1" +end + +function river_erosion_model_vars(n) + vars = RiverErosionModelVars(; bed = fill(mv, n), bank = fill(mv, n)) + return vars +end + +@get_units @with_kw struct RiverErosionBC{T} + # Waterlevel + waterlevel::Vector{T} | "t dt-1" +end + +function river_erosion_bc(n) + bc = RiverErosionBC(; waterlevel = fill(mv, n)) + return bc +end + +# Parameters for the Julian Torres river erosion model +@get_units @with_kw struct RiverErosionParameters{T} + # Mean diameter in the river bed/bank + d50::Vector{T} | "mm" +end + +@get_units @with_kw struct RiverErosionJulianTorresModel{T} <: AbstractRiverErosionModel + boundary_conditions::RiverErosionBC{T} | "-" + parameters::RiverErosionParameters{T} | "-" + variables::RiverErosionModelVars{T} | "-" +end + +function initialize_river_erosion_params(nc, config, inds) + d50 = ncread( + nc, + config, + "lateral.river.potential_erosion.parameters.d50"; + sel = inds, + defaults = 0.1, + type = Float, + ) + river_parameters = RiverErosionParameters(; d50 = d50) + + return river_parameters +end + +function initialize_river_erosion_julian_torres_model(nc, config, inds) + n = length(inds) + vars = river_erosion_model_vars(n) + params = initialize_river_erosion_params(nc, config, inds) + bc = river_erosion_bc(n) + model = RiverErosionJulianTorresModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::RiverErosionJulianTorresModel, geometry::RiverGeometry, ts) + (; waterlevel) = model.boundary_conditions + (; d50) = model.parameters + (; bed, bank) = model.variables + + n = length(waterlevel) + threaded_foreach(1:n; basesize = 1000) do i + bed[i], bank[i] = river_erosion_julian_torres( + waterlevel[i], + d50[i], + geometry.width[i], + geometry.length[i], + geometry.slope[i], + ts, + ) + end +end \ No newline at end of file diff --git a/src/geometry.jl b/src/geometry.jl new file mode 100644 index 000000000..4695e8d4e --- /dev/null +++ b/src/geometry.jl @@ -0,0 +1,79 @@ + +@get_units @with_kw struct LandGeometry{T} + # cell area [m^2] + area::Vector{T} | "m^2" + # drain width [m] + width::Vector{T} | "m" + # drain slope + slope::Vector{T} | "-" +end + +function initialize_land_geometry(nc, config, inds) + # 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) + area = xl .* yl + ldd = ncread(nc, config, "ldd"; optional = false, sel = inds, allow_missing = true) + drain_width = map(detdrainwidth, ldd, xl, yl) + landslope = ncread( + nc, + config, + "vertical.geometry.slope"; + optional = false, + sel = inds, + type = Float, + ) + clamp!(landslope, 0.00001, Inf) + + land_geometry = + LandGeometry{Float}(; area = area, width = drain_width, slope = landslope) + return land_geometry +end + +@get_units @with_kw struct RiverGeometry{T} + # drain width [m] + width::Vector{T} | "m" + # drain length + length::Vector{T} | "m" + # slope + slope::Vector{T} | "-" +end + +function initialize_river_geometry(nc, config, inds) + riverwidth = ncread( + nc, + config, + "lateral.river.geometry.width"; + optional = false, + sel = inds, + type = Float, + ) + riverlength = ncread( + nc, + config, + "lateral.river.geometry.length"; + optional = false, + sel = inds, + type = Float, + ) + riverslope = ncread( + nc, + config, + "lateral.river.geometry.slope"; + optional = false, + sel = inds, + type = Float, + ) + minimum(riverlength) > 0 || error("river length must be positive on river cells") + minimum(riverwidth) > 0 || error("river width must be positive on river cells") + clamp!(riverslope, 0.00001, Inf) + + river_geometry = + RiverGeometry{Float}(; width = riverwidth, length = riverlength, slope = riverslope) + return river_geometry +end \ No newline at end of file diff --git a/src/sediment_flux.jl b/src/sediment_flux.jl index fae0016b3..7d4a5c987 100644 --- a/src/sediment_flux.jl +++ b/src/sediment_flux.jl @@ -1,16 +1,18 @@ +### Overland flow ### @get_units @with_kw struct OverlandFlowSediment{TT, SF, TR, T} hydrometeo_forcing::HydrometeoForcing | "-" + geometry::LandGeometry | "-" transport_capacity::TT | "-" sediment_flux::SF | "-" to_river::TR | "-" - width::Vector{T} | "m" waterbodies::Vector{Bool} | "-" rivers::Vector{Bool} | "-" end -function initialize_overland_flow_sediment(nc, config, inds, width, waterbodies, rivers) +function initialize_overland_flow_sediment(nc, config, inds, waterbodies, rivers) n = length(inds) hydrometeo_forcing = initialize_hydrometeo_forcing(n) + geometry = initialize_land_geometry(nc, config, inds) # Check what transport capacity equation will be used do_river = get(config.model, "runrivermodel", false)::Bool # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] @@ -44,10 +46,10 @@ function initialize_overland_flow_sediment(nc, config, inds, width, waterbodies, Float, }(; hydrometeo_forcing = hydrometeo_forcing, + geometry = geometry, transport_capacity = transport_capacity_model, sediment_flux = sediment_flux_model, to_river = to_river_model, - width = width, waterbodies = waterbodies, rivers = rivers, ) @@ -74,4 +76,135 @@ function update!(model::OverlandFlowSediment, erosion_model::SoilErosionModel, n update_bc(model.to_river, model.sediment_flux) # Compute sediment reaching the river update!(model.to_river, model.rivers) +end + +### River ### +@get_units @with_kw struct RiverSediment{TTR, ER, SFR, CR, T} + hydrometeo_forcing::HydrometeoForcing | "-" + geometry::RiverGeometry | "-" + transport_capacity::TTR | "-" + potential_erosion::ER | "-" + sediment_flux::SFR | "-" + concentrations::CR | "-" + waterbodies::Vector{Bool} | "-" +end + +function initialize_river_flow_sediment(nc, config, inds, waterbodies) + n = length(inds) + hydrometeo_forcing = initialize_hydrometeo_forcing(n) + geometry = initialize_river_geometry(nc, config, inds) + + # Check what transport capacity equation will be used + # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] + transport_method = get(config.model, "rivtransportmethod", "bagnold")::String + if transport_method == "bagnold" + transport_capacity_model = + initialize_transport_capacity_bagnold_model(nc, config, inds) + elseif transport_method == "engelund" + transport_capacity_model = + initialize_transport_capacity_engelund_model(nc, config, inds) + elseif transport_method == "yang" + transport_capacity_model = + initialize_transport_capacity_yang_model(nc, config, inds) + elseif transport_method == "kodatie" + transport_capacity_model = + initialize_transport_capacity_kodatie_model(nc, config, inds) + elseif transport_method == "molinas" + transport_capacity_model = + initialize_transport_capacity_molinas_model(nc, config, inds) + else + error("Unknown river transport method: $transport_method") + end + + # Potential river erosion + potential_erosion_model = initialize_river_erosion_julian_torres_model(nc, config, inds) + + # Sediment flux in river / mass balance + sediment_flux_model = initialize_sediment_river_transport_model(nc, config, inds) + + # Concentrations + concentrations_model = initialize_sediment_concentrations_river_model(nc, config, inds) + + river_sediment = RiverSediment{ + typeof(transport_capacity_model), + typeof(potential_erosion_model), + typeof(sediment_flux_model), + typeof(concentrations_model), + Float, + }(; + hydrometeo_forcing = hydrometeo_forcing, + geometry = geometry, + transport_capacity = transport_capacity_model, + potential_erosion = potential_erosion_model, + sediment_flux = sediment_flux_model, + concentrations = concentrations_model, + waterbodies = waterbodies, + ) + return river_sediment +end + +function update!( + model::RiverSediment, + to_river_model::SedimentToRiverDifferentiationModel, + network, + inds_riv, + dt, +) + # Convert dt to integer + ts = tosecond(dt) + # Update the boundary conditions of transport capacity + (; q, waterlevel) = model.transport_capacity.boundary_conditions + (; q_river, waterlevel_river) = model.hydrometeo_forcing + @. q = q_river + @. waterlevel = waterlevel_river + + # Transport capacity + update!(model.transport_capacity, model.geometry, ts) + + # Potential maximum river erosion + (; waterlevel) = model.potential_erosion.boundary_conditions + @. waterlevel = waterlevel_river + update!(model.potential_erosion, model.geometry, ts) + + # River transport + (; + waterlevel, + q, + transport_capacity, + erosion_land_clay, + erosion_land_silt, + erosion_land_sand, + erosion_land_sagg, + erosion_land_lagg, + potential_erosion_river, + ) = model.boundary_conditions + @. q = q_river + @. waterlevel = waterlevel_river + + @. transport_capacity = model.transport_capacity.variables.amount + + (; clay, silt, sand, sagg, lagg) = to_river_model.variables + @. erosion_land_clay = clay[inds_riv] + @. erosion_land_silt = silt[inds_riv] + @. erosion_land_sand = sand[inds_riv] + @. erosion_land_sagg = sagg[inds_riv] + @. erosion_land_lagg = lagg[inds_riv] + + @. potential_erosion_river = model.potential_erosion.variables.amount + + update!(model.sediment_flux, network, model.geometry, ts) + + # Concentrations + (; q, waterlevel, clay, silt, sand, sagg, lagg, gravel) = + model.concentrations.boundary_conditions + @. q = q_river + @. waterlevel = waterlevel_river + @. clay = model.sediment_flux.variables.clay + @. silt = model.sediment_flux.variables.silt + @. sand = model.sediment_flux.variables.sand + @. sagg = model.sediment_flux.variables.sagg + @. lagg = model.sediment_flux.variables.lagg + @. gravel = model.sediment_flux.variables.gravel + + update!(model.concentrations, model.geometry, ts) end \ No newline at end of file diff --git a/src/sediment_model.jl b/src/sediment_model.jl index 3ea45f293..59a4eaed2 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -23,33 +23,12 @@ function initialize_sediment_model(config::Config) 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) # Needed to update the forcing reservoir = () lake = () - # 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) - area = xl .* yl - landslope = - ncread(nc, config, "vertical.slope"; optional = false, sel = inds, type = Float) - clamp!(landslope, 0.00001, Inf) - - soilloss = initialize_soil_loss(nc, config, inds, area, landslope) + soilloss = initialize_soil_loss(nc, config, inds) # Get waterbodies mask do_reservoirs = get(config.model, "doreservoir", false)::Bool @@ -84,30 +63,40 @@ function initialize_sediment_model(config::Config) ldd_2d = ncread(nc, config, "ldd"; optional = false, allow_missing = true) ldd = ldd_2d[inds] - drain_width = map(detdrainwidth, ldd, xl, yl) - # # lateral part sediment in overland flow overland_flow_sediment = - initialize_overland_flow_sediment(nc, config, inds, drain_width, waterbodies, river) + initialize_overland_flow_sediment(nc, config, inds, waterbodies, river) graph = flowgraph(ldd, inds, pcr_dir) # River processes do_river = get(config.model, "runrivermodel", false)::Bool + # TODO: see if we can skip init if the river model is not needed + # or if we leave it when we restructure the Wflow Model struct - # the indices of the river cells in the land(+river) cell vector - 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") + inds_riv, rev_inds_riv = active_indices(river_2d, 0) ldd_riv = ldd_2d[inds_riv] graph_riv = flowgraph(ldd_riv, inds_riv, pcr_dir) + # Needed for frac_to_river? + landslope = ncread( + nc, + config, + "vertical.geometry.slope"; + optional = false, + sel = inds, + type = Float, + ) + clamp!(landslope, 0.00001, Inf) + index_river = filter(i -> !isequal(river[i], 0), 1:n) frac_toriver = fraction_runoff_toriver(graph, ldd, index_river, landslope, n) - rs = initialize_riversed(nc, config, riverwidth, riverlength, inds_riv) + rs = initialize_river_flow_sediment(nc, config, inds_riv, waterbodies) + + y_nc = read_y_axis(nc) + x_nc = read_x_axis(nc) modelmap = (vertical = soilloss, lateral = (land = overland_flow_sediment, river = rs)) indices_reverse = ( @@ -161,18 +150,12 @@ function update(model::Model{N, L, V, R, W, T}) where {N, L, V, R, W, T <: Sedim # Overland flow sediment transport update!(lateral.land, vertical.soil_erosion, network.land, dt) - # do_river = get(config.model, "runrivermodel", false)::Bool - - # if do_river - # inds_riv = network.index_river - # lateral.river.inlandclay .= lateral.land.inlandclay[inds_riv] - # lateral.river.inlandsilt .= lateral.land.inlandsilt[inds_riv] - # lateral.river.inlandsand .= lateral.land.inlandsand[inds_riv] - # lateral.river.inlandsagg .= lateral.land.inlandsagg[inds_riv] - # lateral.river.inlandlagg .= lateral.land.inlandlagg[inds_riv] - - # update(lateral.river, network.river, config) - # end + # River sediment transport + do_river = get(config.model, "runrivermodel", false)::Bool + if do_river + inds_riv = network.index_river + update!(lateral.river, lateral.land.to_river, network.river, inds_riv, dt) + end return model end diff --git a/src/sediment_transport/deposition.jl b/src/sediment_transport/deposition.jl new file mode 100644 index 000000000..720c6685f --- /dev/null +++ b/src/sediment_transport/deposition.jl @@ -0,0 +1,49 @@ +""" + reservoir_deposition_camp( + input, + q, + waterlevel, + wb_area, + wb_trapping_efficiency, + dm, + slope, + ) + +Deposition of sediment in waterbodies from Camp 1945. + +# Arguments +- `input` (sediment input [t Δt⁻¹]) +- `q` (discharge [m³ Δt⁻¹]) +- `waterlevel` (water level [m]) +- `wb_area` (waterbody area [m²]) +- `wb_trapping_efficiency` (waterbody trapping efficiency [-]) +- `dm` (mean diameter [m]) +- `slope` (slope [-]) + +# Output +- `deposition` (deposition [t Δt⁻¹]) +""" +function reservoir_deposition_camp( + input, + q, + waterlevel, + wb_area, + wb_trapping_efficiency, + dm, + slope, +) + # Compute critical velocity + vcres = q / wb_area + DCres = 411 / 3600 / vcres + # Natural deposition + deposition = input * min(1.0, (DCres * (dm / 1000)^2)) + + # Check if the particles was travelling in suspension or bed load using Rouse number + dsuspf = 1e3 * (1.2 * 3600 * 0.41 / 411 * (9.81 * waterlevel * slope)^0.5)^0.5 + # If bed load, we have extra deposition depending on the reservoir type + if dm > dsuspf + deposition = max(deposition, wb_trapping_efficiency * input) + end + + return deposition +end \ No newline at end of file diff --git a/src/sediment_transport/river_transport.jl b/src/sediment_transport/river_transport.jl new file mode 100644 index 000000000..b258be3bd --- /dev/null +++ b/src/sediment_transport/river_transport.jl @@ -0,0 +1,867 @@ +abstract type AbstractSedimentRiverTransportModel end + +## Total sediment transport in overland flow structs and functions +@get_units @with_kw struct SedimentRiverTransportVars{T} + # Sediment flux [ton] + amount::Vector{T} | "t dt-1" + clay::Vector{T} | "t dt-1" + silt::Vector{T} | "t dt-1" + sand::Vector{T} | "t dt-1" + sagg::Vector{T} | "t dt-1" + lagg::Vector{T} | "t dt-1" + gravel::Vector{T} | "t dt-1" + # Total Sediment deposition [ton] + deposition::Vector{T} | "t dt-1" + # Total sediment erosion (from store + direct river bed/bank) [ton] + erosion::Vector{T} | "t dt-1" + # Sediment / particle left in the cell [ton] - states + leftover_clay::Vector{T} | "t dt-1" + leftover_silt::Vector{T} | "t dt-1" + leftover_sand::Vector{T} | "t dt-1" + leftover_sagg::Vector{T} | "t dt-1" + leftover_lagg::Vector{T} | "t dt-1" + leftover_gravel::Vector{T} | "t dt-1" + # Sediment / particle stored on the river bed after deposition [ton] -states + store_clay::Vector{T} | "t dt-1" + store_silt::Vector{T} | "t dt-1" + store_sand::Vector{T} | "t dt-1" + store_sagg::Vector{T} | "t dt-1" + store_lagg::Vector{T} | "t dt-1" + store_gravel::Vector{T} | "t dt-1" +end + +function sediment_river_transport_vars(n) + vars = SedimentRiverTransportVars(; + amount = fill(mv, n), + clay = fill(mv, n), + silt = fill(mv, n), + sand = fill(mv, n), + sagg = fill(mv, n), + lagg = fill(mv, n), + gravel = fill(mv, n), + deposition = fill(mv, n), + erosion = fill(mv, n), + leftover_clay = fill(mv, n), + leftover_silt = fill(mv, n), + leftover_sand = fill(mv, n), + leftover_sagg = fill(mv, n), + leftover_lagg = fill(mv, n), + leftover_gravel = fill(mv, n), + store_clay = fill(mv, n), + store_silt = fill(mv, n), + store_sand = fill(mv, n), + store_sagg = fill(mv, n), + store_lagg = fill(mv, n), + store_gravel = fill(mv, n), + ) + return vars +end + +@get_units @with_kw struct SedimentRiverTransportBC{T} + # Waterlevel + waterlevel::Vector{T} | "t dt-1" + # Discharge + q::Vector{T} | "m3 s-1" + # Transport capacity of the flow + transport_capacity::Vector{T} | "t dt-1" + # Sediment input from land erosion + erosion_land_clay::Vector{T} | "t dt-1" + erosion_land_silt::Vector{T} | "t dt-1" + erosion_land_sand::Vector{T} | "t dt-1" + erosion_land_sagg::Vector{T} | "t dt-1" + erosion_land_lagg::Vector{T} | "t dt-1" + # Sediment available from direct river erosion + potential_erosion_river::Vector{T} | "t dt-1" +end + +function sediment_river_transport_bc(n) + bc = SedimentRiverTransportBC(; + waterlevel = fill(mv, n), + q = fill(mv, n), + transport_capacity = fill(mv, n), + erosion_land_clay = fill(mv, n), + erosion_land_silt = fill(mv, n), + erosion_land_sand = fill(mv, n), + erosion_land_sagg = fill(mv, n), + erosion_land_lagg = fill(mv, n), + potential_erosion_river = fill(mv, n), + ) + return bc +end + +# Parameters for river transport +@get_units @with_kw struct SedimentRiverTransportParameters{T} + # River bed/bank content clay + clay_fraction::Vector{T} | "-" + # River bed/bank content silt + silt_fraction::Vector{T} | "-" + # River bed/bank content sand + sand_fraction::Vector{T} | "-" + # River bed/bank content gravel + gravel_fraction::Vector{T} | "-" + # Clay mean diameter + dm_clay::Vector{T} | "µm" + # Silt mean diameter + dm_silt::Vector{T} | "µm" + # Sand mean diameter + dm_sand::Vector{T} | "µm" + # Small aggregates mean diameter + dm_sagg::Vector{T} | "µm" + # Large aggregates mean diameter + dm_lagg::Vector{T} | "µm" + # Gravel mean diameter + dm_gravel::Vector{T} | "µm" + # Waterbodies outlets + waterbodies_locs::Vector{Bool} | "-" + # Waterbodies area + waterbodies_area::Vector{T} | "m2" + # Waterbodies trapping efficiency + waterbodies_trapping_efficiency::Vector{T} | "-" +end + +function initialize_sediment_river_transport_params(nc, config, inds) + n = length(inds) + clay_fraction = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.clay_fraction"; + sel = inds, + defaults = 0.15, + type = Float, + ) + silt_fraction = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.silt_fraction"; + sel = inds, + defaults = 0.65, + type = Float, + ) + sand_fraction = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.sand_fraction"; + sel = inds, + defaults = 0.15, + type = Float, + ) + gravel_fraction = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.sagg_fraction"; + sel = inds, + defaults = 0.05, + type = Float, + ) + # Check that river fractions sum to 1 + river_fractions = clay_fraction + silt_fraction + sand_fraction + gravel_fraction + if any(abs.(river_fractions .- 1.0) .> 1e-3) + error("Particle fractions in the river bed must sum to 1") + end + dm_clay = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.dm_clay"; + sel = inds, + defaults = 2.0, + type = Float, + ) + dm_silt = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.dm_silt"; + sel = inds, + defaults = 10.0, + type = Float, + ) + dm_sand = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.dm_sand"; + sel = inds, + defaults = 200.0, + type = Float, + ) + dm_sagg = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.dm_sagg"; + sel = inds, + defaults = 30.0, + type = Float, + ) + dm_lagg = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.dm_lagg"; + sel = inds, + defaults = 500.0, + type = Float, + ) + dm_gravel = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.dm_gravel"; + sel = inds, + defaults = 2000.0, + type = Float, + ) + # Waterbodies + wblocs = zeros(Float, n) + wbarea = zeros(Float, n) + wbtrap = zeros(Float, n) + do_reservoirs = get(config.model, "doreservoir", false)::Bool + do_lakes = get(config.model, "dolake", false)::Bool + + if do_reservoirs + reslocs = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.reslocs"; + optional = false, + sel = inds, + type = Float, + fill = 0, + ) + resarea = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.resarea"; + optional = false, + sel = inds, + type = Float, + fill = 0.0, + ) + restrapefficiency = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.restrapeff"; + optional = false, + sel = inds, + type = Float, + defaults = 1.0, + fill = 0.0, + ) + wblocs = wblocs .+ reslocs + wbarea = wbarea .+ resarea + wbtrap = wbtrap .+ restrapefficiency + end + + if do_lakes + lakelocs = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.lakelocs"; + optional = false, + sel = inds, + type = Float, + fill = 0, + ) + lakearea = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.lakearea"; + optional = false, + sel = inds, + type = Float, + fill = 0.0, + ) + wblocs = wblocs .+ lakelocs + wbarea = wbarea .+ lakearea + end + + river_parameters = SedimentRiverTransportParameters(; + clay_fraction = clay_fraction, + silt_fraction = silt_fraction, + sand_fraction = sand_fraction, + gravel_fraction = gravel_fraction, + dm_clay = dm_clay, + dm_silt = dm_silt, + dm_sand = dm_sand, + dm_sagg = dm_sagg, + dm_lagg = dm_lagg, + dm_gravel = dm_gravel, + waterbodies_locs = wblocs .> 0, + waterbodies_area = wbarea, + waterbodies_trapping_efficiency = wbtrap, + ) + + return river_parameters +end + +@get_units @with_kw struct SedimentRiverTransportModel{T} <: + AbstractSedimentRiverTransportModel + boundary_conditions::SedimentRiverTransportBC{T} | "-" + parameters::SedimentRiverTransportParameters{T} | "-" + variables::SedimentRiverTransportVars{T} | "-" +end + +function initialize_sediment_river_transport_model(nc, config, inds) + n = length(inds) + vars = sediment_river_transport_vars(n) + params = initialize_sediment_river_transport_params(nc, config, inds) + bc = sediment_river_transport_bc(n) + model = SedimentRiverTransportModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeometry, ts) + (; + waterlevel, + q, + transport_capacity, + erosion_land_clay, + erosion_land_silt, + erosion_land_sand, + erosion_land_sagg, + erosion_land_lagg, + potential_erosion_river_bed, + potential_erosion_river_bank, + ) = model.boundary_conditions + (; clay_fraction, silt_fraction, sand_fraction, gravel_fraction) = model.parameters + (; + amount, + clay, + silt, + sand, + sagg, + lagg, + gravel, + deposition, + erosion, + leftover_clay, + leftover_silt, + leftover_sand, + leftover_sagg, + leftover_lagg, + leftover_gravel, + store_clay, + store_silt, + store_sand, + store_sagg, + store_lagg, + store_gravel, + ) = model.variables + + @unpack graph, order = network + + # Sediment transport - water balance in the river + for v in order + ### Sediment input in the cell (left from previous timestep + from land + from upstream outflux) ### + input_clay = leftover_clay[v] + erosion_land_clay[v] + input_silt = leftover_silt[v] + erosion_land_silt[v] + input_sand = leftover_sand[v] + erosion_land_sand[v] + input_sagg = leftover_sagg[v] + erosion_land_sagg[v] + input_lagg = leftover_lagg[v] + erosion_land_lagg[v] + input_gravel = leftover_gravel[v] + + # Add upstream contribution + upstream_nodes = inneighbors(graph, v) + if !isempty(upstream_nodes) + for i in upstream_nodes + if clay[i] >= 0.0 # avoid NaN from upstream non-river cells + input_clay += clay[i] + input_silt += silt[i] + input_sand += sand[i] + input_sagg += sagg[i] + input_lagg += lagg[i] + input_gravel += gravel[i] + end + end + end + + input_sediment = + input_clay + input_silt + input_sand + input_sagg + input_lagg + input_gravel + + ### River erosion ### + # Erosion only if the load is below the transport capacity of the flow. + sediment_need = max(transport_capacity - input_sediment, 0.0) + # No erosion in reservoirs + if waterbodies[v] + sediment_need = 0.0 + end + + # Available sediment stored from previous deposition + store_sediment = + store_clay[v] + + store_silt[v] + + store_sand[v] + + store_sagg[v] + + store_lagg[v] + + store_gravel[v] + + # Direct erosion from the river bed/bank + if sediment_need > store_sediment + # Effective sediment needed fom river bed and bank erosion [ton] + effsediment_need = sediment_need - store_sediment + # Relative potential erosion rates of the bed and the bank [-] + if (potential_erosion_river_bank + potential_erosion_river_bed > 0.0) + RTEbank = + potential_erosion_river_bank / + (potential_erosion_river_bank + potential_erosion_river_bed) + else + RTEbank = 0.0 + end + RTEbed = 1.0 - RTEbank + + # Actual bed and bank erosion + erosion_bank = max(RTEbank * effsediment_need, potential_erosion_river_bank) + erosion_bed = max(RTEbed * effsediment_need, potential_erosion_river_bed) + erosion_river = erosion_bank + erosion_bed + # Per particle + erosion_clay = erosion_river * clay_fraction[v] + erosion_silt = erosion_river * silt_fraction[v] + erosion_sand = erosion_river * sand_fraction[v] + erosion_gravel = erosion_river * gravel_fraction[v] + # No small and large aggregates in the river bed/bank + erosion_sagg = 0.0 + erosion_lagg = 0.0 + else + erosion_clay = 0.0 + erosion_silt = 0.0 + erosion_sand = 0.0 + erosion_gravel = 0.0 + erosion_sagg = 0.0 + erosion_lagg = 0.0 + end + + # Erosion/degradation of the previously deposited sediment (from clay to gravel) [ton] + if sediment_need > 0.0 + # Erosion in priority of the smaller particles + # Clay + if store_clay[v] > 0.0 + erosion_store_clay, sediment_need, store_clay[v] = + river_erosion_store(sediment_need, store_clay[v]) + # Update the clay erosion + erosion_clay += erosion_store_clay + end + # Silt + if store_silt[v] > 0.0 + erosion_store_silt, sediment_need, store_silt[v] = + river_erosion_store(sediment_need, store_silt[v]) + # Update the silt erosion + erosion_silt += erosion_store_silt + end + # Small aggregates + if store_sagg[v] > 0.0 + erosion_store_sagg, sediment_need, store_sagg[v] = + river_erosion_store(sediment_need, store_sagg[v]) + # Update the sagg erosion + erosion_sagg += erosion_store_sagg + end + # Sand + if store_sand[v] > 0.0 + erosion_store_sand, sediment_need, store_sand[v] = + river_erosion_store(sediment_need, store_sand[v]) + # Update the sand erosion + erosion_sand += erosion_store_sand + end + # Large aggregates + if store_lagg[v] > 0.0 + erosion_store_lagg, sediment_need, store_lagg[v] = + river_erosion_store(sediment_need, store_lagg[v]) + # Update the lagg erosion + erosion_lagg += erosion_store_lagg + end + # Gravel + if store_gravel[v] > 0.0 + erosion_store_gravel, sediment_need, store_gravel[v] = + river_erosion_store(sediment_need, store_gravel[v]) + # Update the gravel erosion + erosion_gravel += erosion_store_gravel + end + end + + # Compute total erosion + erosion[v] = + erosion_clay + + erosion_silt + + erosion_sand + + erosion_sagg + + erosion_lagg + + erosion_gravel + + ### Deposition / settling ### + # Different deposition if waterbody outlet or river + if waterbodies_locs[v] + # Deposition in waterbodies outlets + deposition_clay = reservoir_deposition_camp( + (input_clay + erosion_clay), + q[v], + waterlevel[v], + waterbodies_area[v], + waterbodies_trapping_efficiency[v], + dm_clay[v], + geometry.slope[v], + ) + deposition_silt = reservoir_deposition_camp( + (input_silt + erosion_silt), + q[v], + waterlevel[v], + waterbodies_area[v], + waterbodies_trapping_efficiency[v], + dm_silt[v], + geometry.slope[v], + ) + deposition_sand = reservoir_deposition_camp( + (input_sand + erosion_sand), + q[v], + waterlevel[v], + waterbodies_area[v], + waterbodies_trapping_efficiency[v], + dm_sand[v], + geometry.slope[v], + ) + deposition_sagg = reservoir_deposition_camp( + (input_sagg + erosion_sagg), + q[v], + waterlevel[v], + waterbodies_area[v], + waterbodies_trapping_efficiency[v], + dm_sagg[v], + geometry.slope[v], + ) + deposition_lagg = reservoir_deposition_camp( + (input_lagg + erosion_lagg), + q[v], + waterlevel[v], + waterbodies_area[v], + waterbodies_trapping_efficiency[v], + dm_lagg[v], + geometry.slope[v], + ) + deposition_gravel = reservoir_deposition_camp( + (input_gravel + erosion_gravel), + q[v], + waterlevel[v], + waterbodies_area[v], + waterbodies_trapping_efficiency[v], + dm_gravel[v], + geometry.slope[v], + ) + elseif waterbodies[v] + # No deposition in waterbodies, only at the outlets + deposition_clay = 0.0 + deposition_silt = 0.0 + deposition_sand = 0.0 + deposition_sagg = 0.0 + deposition_lagg = 0.0 + deposition_gravel = 0.0 + else + # Deposition in the river + # From trasnport capacity exceedance + excess_sediment = max(input_sediment - transport_capacity, 0.0) + if excess_sediment > 0.0 + # Sediment deposited in the channel (from gravel to clay) [ton] + # Gravel + deposition_gravel = ifelse( + excess_sediment > (input_gravel + erosion_gravel), + (input_gravel + erosion_gravel), + excess_sediment, + ) + excess_sediment = max(excess_sediment - deposition_gravel, 0.0) + # Large aggregates + deposition_lagg = ifelse( + excess_sediment > (input_lagg + erosion_lagg), + (input_lagg + erosion_lagg), + excess_sediment, + ) + excess_sediment = max(excess_sediment - deposition_lagg, 0.0) + # Sand + deposition_sand = ifelse( + excess_sediment > (input_sand + erosion_sand), + (input_sand + erosion_sand), + excess_sediment, + ) + excess_sediment = max(excess_sediment - deposition_sand, 0.0) + # Small aggregates + deposition_sagg = ifelse( + excess_sediment > (input_sagg + erosion_sagg), + (input_sagg + erosion_sagg), + excess_sediment, + ) + excess_sediment = max(excess_sediment - deposition_sagg, 0.0) + # Silt + deposition_silt = ifelse( + excess_sediment > (input_silt + erosion_silt), + (input_silt + erosion_silt), + excess_sediment, + ) + excess_sediment = max(excess_sediment - deposition_silt, 0.0) + # Clay + deposition_clay = ifelse( + excess_sediment > (input_clay + erosion_clay), + (input_clay + erosion_clay), + excess_sediment, + ) + excess_sediment = max(excess_sediment - deposition_clay, 0.0) + else + # Natural deposition from Einstein's formula (density controlled) + # Particle fall velocity [m/s] from Stokes + xs = ifelse( + q[v] > 0.0, + 1.055 * geometry.length[v] / (q[v] / geometry.width[v]), + 0.0, + ) + xclay = + min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_clay[v] / 1000)^2 / 3600))) + deposition_clay = xclay * (input_clay + erosion_clay) + xsilt = + min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_silt[v] / 1000)^2 / 3600))) + deposition_silt = xsilt * (input_silt + erosion_silt) + xsand = + min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_sand[v] / 1000)^2 / 3600))) + deposition_sand = xsand * (input_sand + erosion_sand) + xsagg = + min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_sagg[v] / 1000)^2 / 3600))) + deposition_sagg = xsagg * (input_sagg + erosion_sagg) + xlagg = + min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_lagg[v] / 1000)^2 / 3600))) + deposition_lagg = xlagg * (input_lagg + erosion_lagg) + xgrav = + min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_grav[v] / 1000)^2 / 3600))) + deposition_gravel = xgrav * (input_gravel + erosion_gravel) + end + end + + # Update the sediment store + store_clay[v] += deposition_clay + store_silt[v] += deposition_silt + store_sand[v] += deposition_sand + store_sagg[v] += deposition_sagg + store_lagg[v] += deposition_lagg + store_gravel[v] += deposition_gravel + + # Compute total deposition + deposition[v] = + deposition_clay + + deposition_silt + + deposition_sand + + deposition_sagg + + deposition_lagg + + deposition_gravel + + ### Output loads ### + # Sediment transported out of the cell during the timestep [ton] + # 0 in case all sediment are deposited in the cell + # Reduce the fraction so that there is still some sediment staying in the river cell + fwaterout = + min(q[v] * ts / (waterlevel[v] * geometry.width[v] * geometry.length[v]), 1.0) + clay = fwaterout * (input_clay + erosion_clay - deposition_clay) + silt = fwaterout * (input_silt + erosion_silt - deposition_silt) + sand = fwaterout * (input_sand + erosion_sand - deposition_sand) + sagg = fwaterout * (input_sagg + erosion_sagg - deposition_sagg) + lagg = fwaterout * (input_lagg + erosion_lagg - deposition_lagg) + gravel = fwaterout * (input_gravel + erosion_gravel - deposition_gravel) + + amount = clay + silt + sand + sagg + lagg + gravel + + ### Leftover / mass balance ### + # Sediment left in the cell [ton] + leftover_clay[v] = input_clay + erosion_clay - deposition_clay - clay + leftover_silt[v] = input_silt + erosion_silt - deposition_silt - silt + leftover_sand[v] = input_sand + erosion_sand - deposition_sand - sand + leftover_sagg[v] = input_sagg + erosion_sagg - deposition_sagg - sagg + leftover_lagg[v] = input_lagg + erosion_lagg - deposition_lagg - lagg + leftover_gravel[v] = input_gravel + erosion_gravel - deposition_gravel - gravel + end +end + +abstract type AbstractSedimentConcentrationsRiverModel end + +## Total sediment transport in overland flow structs and functions +@get_units @with_kw struct SedimentConcentrationsRiverVars{T} + # Total sediment concentration in the river + total::Vector{T} | "g m-3" + # suspended sediemnt concentration in the river + suspended::Vector{T} | "g m-3" + # bed load sediment concentration in the river + bed::Vector{T} | "g m-3" +end + +function sediment_concentrations_river_vars(n) + vars = SedimentConcentrationsRiverVars(; + total = fill(mv, n), + suspended = fill(mv, n), + bed = fill(mv, n), + ) + return vars +end + +@get_units @with_kw struct SedimentConcentrationsRiverBC{T} + # Discharge + q::Vector{T} | "m3 s-1" + waterlevel::Vector{T} | "m" + # Clay load + clay::Vector{T} | "g m-3" + # Silt load + silt::Vector{T} | "g m-3" + # Sand load + sand::Vector{T} | "g m-3" + # Small aggregates load + sagg::Vector{T} | "g m-3" + # Large aggregates load + lagg::Vector{T} | "g m-3" + # Gravel load + gravel::Vector{T} | "g m-3" +end + +function sediment_concentrations_river_bc(n) + bc = SedimentConcentrationsRiverBC(; + q = fill(mv, n), + waterlevel = fill(mv, n), + clay = fill(mv, n), + silt = fill(mv, n), + sand = fill(mv, n), + sagg = fill(mv, n), + lagg = fill(mv, n), + gravel = fill(mv, n), + ) + return bc +end + +# Common parameters for transport capacity models +@get_units @with_kw struct SedimentConcentrationsRiverParameters{T} + # Clay mean diameter + dm_clay::Vector{T} | "µm" + # Silt mean diameter + dm_silt::Vector{T} | "µm" + # Sand mean diameter + dm_sand::Vector{T} | "µm" + # Small aggregates mean diameter + dm_sagg::Vector{T} | "µm" + # Large aggregates mean diameter + dm_lagg::Vector{T} | "µm" + # Gravel mean diameter + dm_gravel::Vector{T} | "µm" +end + +function initialize_sediment_concentrations_river_params(nc, config, inds) + dm_clay = ncread( + nc, + config, + "lateral.river.concentrations.parameters.dm_clay"; + sel = inds, + defaults = 2.0, + type = Float, + ) + dm_silt = ncread( + nc, + config, + "lateral.river.concentrations.parameters.dm_silt"; + sel = inds, + defaults = 10.0, + type = Float, + ) + dm_sand = ncread( + nc, + config, + "lateral.river.concentrations.parameters.dm_sand"; + sel = inds, + defaults = 200.0, + type = Float, + ) + dm_sagg = ncread( + nc, + config, + "lateral.river.concentrations.parameters.dm_sagg"; + sel = inds, + defaults = 30.0, + type = Float, + ) + dm_lagg = ncread( + nc, + config, + "lateral.river.concentrations.parameters.dm_lagg"; + sel = inds, + defaults = 500.0, + type = Float, + ) + dm_gravel = ncread( + nc, + config, + "lateral.river.concentrations.parameters.dm_gravel"; + sel = inds, + defaults = 2000.0, + type = Float, + ) + conc_parameters = SedimentConcentrationsRiverParameters(; + dm_clay = dm_clay, + dm_silt = dm_silt, + dm_sand = dm_sand, + dm_sagg = dm_sagg, + dm_lagg = dm_lagg, + dm_gravel = dm_gravel, + ) + + return conc_parameters +end + +@get_units @with_kw struct SedimentConcentrationsRiverModel{T} <: + AbstractSedimentConcentrationsRiverModel + boundary_conditions::SedimentConcentrationsRiverBC{T} | "-" + parameters::SedimentConcentrationsRiverParameters{T} | "-" + variables::SedimentConcentrationsRiverVars{T} | "-" +end + +function initialize_sediment_concentrations_river_model(nc, config, inds) + n = length(inds) + vars = sediment_concentrations_river_vars(n) + params = initialize_sediment_concentrations_river_params(nc, config, inds) + bc = sediment_concentrations_river_bc(n) + model = SedimentConcentrationsRiverModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::SedimentConcentrationsRiverModel, geometry::RiverGeometry, ts) + (; q, waterlevel, clay, silt, sand, sagg, lagg, gravel) = model.boundary_conditions + (; dm_clay, dm_silt, dm_sand, dm_sagg, dm_lagg, dm_gravel) = model.parameters + (; total, suspended, bed) = model.variables + + zeros = zeros(Float, length(q)) + # Conversion from load [ton] to concentration for rivers [mg/L] + toconc .= ifelse.(q .> 0.0, 1e6 ./ (q .* ts), zeros) + + # Differentiation of bed and suspended load using Rouse number for suspension + # threshold diameter between bed load and mixed load using Rouse number + dbedf .= + 1e3 .* + (2.5 .* 3600 .* 0.41 ./ 411 .* (9.81 .* waterlevel .* geometry.slope) .^ 0.5) .^ 0.5 + # threshold diameter between suspended load and mixed load using Rouse number + dsuspf .= + 1e3 .* + (1.2 .* 3600 .* 0.41 ./ 411 .* (9.81 .* waterlevel .* geometry.slope) .^ 0.5) .^ 0.5 + + # Rouse with diameter + SSclay .= + ifelse.(dm_clay .<= dsuspf, clay, ifelse.(dm_clay .<= dbedf, clay ./ 2, zeros)) + SSsilt .= + ifelse.(dm_silt .<= dsuspf, silt, ifelse.(dm_silt .<= dbedf, silt ./ 2, zeros)) + SSsand .= + ifelse.(dm_sand .<= dsuspf, sand, ifelse.(dm_sand .<= dbedf, sand ./ 2, zeros)) + SSsagg .= + ifelse.(dm_sagg .<= dsuspf, sagg, ifelse.(dm_sagg .<= dbedf, sagg ./ 2, zeros)) + SSlagg .= + ifelse.(dm_lagg .<= dsuspf, lagg, ifelse.(dm_lagg .<= dbedf, lagg ./ 2, zeros)) + SSgrav .= + ifelse.( + dm_gravel .<= dsuspf, + gravel, + ifelse.(dm_gravel .<= dbedf, gravel ./ 2, zeros), + ) + + SS .= SSclay .+ SSsilt .+ SSsagg .+ SSsand .+ SSlagg .+ SSgrav + Bedload .= (clay .+ silt .+ sagg .+ sand .+ lagg .+ gravel) .- SS + + suspended .= SS .* toconc + bed .= Bedload .* toconc + total .= (clay .+ silt .+ sagg .+ sand .+ lagg .+ gravel) .* toconc +end \ No newline at end of file diff --git a/src/sediment_transport/transport_capacity.jl b/src/sediment_transport/transport_capacity.jl index c3fbe41cc..c0d4f999c 100644 --- a/src/sediment_transport/transport_capacity.jl +++ b/src/sediment_transport/transport_capacity.jl @@ -23,7 +23,9 @@ function transport_capacity_bc(n) return bc end -# Common parameters for transport capacity models +##################### Overland Flow ##################### + +# Govers parameters for transport capacity models @get_units @with_kw struct TransportCapacityGoversParameters{T} # Drain slope slope::Vector{T} | "m m-1" @@ -230,8 +232,6 @@ end # Common parameters for transport capacity models @get_units @with_kw struct TransportCapacityYalinDifferentiationParameters{T} - # Drain slope - slope::Vector{T} | "m m-1" # Particle density density::Vector{T} | "kg m-3" # Clay mean diameter @@ -247,14 +247,6 @@ end end function initialize_transport_capacity_yalin_diff_params(nc, config, inds) - slope = ncread( - nc, - config, - "lateral.land.transport_capacity.parameters.slope"; - sel = inds, - defaults = 0.01, - type = Float, - ) density = ncread( nc, config, @@ -304,7 +296,6 @@ function initialize_transport_capacity_yalin_diff_params(nc, config, inds) type = Float, ) tc_parameters = TransportCapacityYalinDifferentiationParameters(; - slope = slope, density = density, dm_clay = dm_clay, dm_silt = dm_silt, @@ -421,4 +412,325 @@ function update!( ) amount[i] = clay[i] + silt[i] + sand[i] + sagg[i] + lagg[i] end +end + +##################### River Flow ##################### +@get_units @with_kw struct TransportCapacityRiverParameters{T} + # Particle density + density::Vector{T} | "kg m-3" + # Particle mean diameter + d50::Vector{T} | "mm" +end + +function initialize_transport_capacity_river_params(nc, config, inds) + density = ncread( + nc, + config, + "lateral.river.transport_capacity.parameters.density"; + sel = inds, + defaults = 2650.0, + type = Float, + ) + d50 = ncread( + nc, + config, + "lateral.river.transport_capacity.parameters.d50"; + sel = inds, + defaults = 0.1, + type = Float, + ) + tc_parameters = TransportCapacityRiverParameters(; density = density, d50 = d50) + + return tc_parameters +end + +# Bagnold parameters for transport capacity models +@get_units @with_kw struct TransportCapacityBagnoldParameters{T} + # Bagnold transport capacity coefficient + c_bagnold::Vector{T} | "-" + # Bagnold transport capacity exponent + e_bagnold::Vector{T} | "-" +end + +function initialize_transport_capacity_bagnold_params(nc, config, inds) + c_bagnold = ncread( + nc, + config, + "lateral.river.transport_capacity.parameters.c_bagnold"; + sel = inds, + optional = false, + type = Float, + ) + e_bagnold = ncread( + nc, + config, + "lateral.river.transport_capacity.parameters.e_bagnold"; + sel = inds, + optional = false, + type = Float, + ) + tc_parameters = + TransportCapacityBagnoldParameters(; c_bagnold = c_bagnold, e_bagnold = e_bagnold) + + return tc_parameters +end + +@get_units @with_kw struct TransportCapacityBagnoldModel{T} <: + AbstractTransportCapacityModel + boundary_conditions::TransportCapacityBC{T} | "-" + parameters::TransportCapacityBagnoldParameters{T} | "-" + variables::TransportCapacityModelVars{T} | "-" +end + +function initialize_transport_capacity_bagnold_model(nc, config, inds) + n = length(inds) + vars = transport_capacity_model_vars(n) + params = initialize_transport_capacity_bagnold_params(nc, config, inds) + bc = transport_capacity_bc(n) + model = TransportCapacityBagnoldModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::TransportCapacityBagnoldModel, geometry::RiverGeometry, ts) + (; q, waterlevel) = model.boundary_conditions + (; c_bagnold, e_bagnold) = model.parameters + (; amount) = model.variables + + n = length(q) + # Note: slope is not used here but this allows for a consistent interface of update! functions + # Only Bagnold does not use it + threaded_foreach(1:n; basesize = 1000) do i + amount[i] = transport_capacity_bagnold( + q[i], + waterlevel[i], + c_bagnold[i], + e_bagnold[i], + geometry.width[i], + geometry.length[i], + ts, + ) + end +end + +# Engelund and Hansen parameters for transport capacity models +@get_units @with_kw struct TransportCapacityEngelundModel{T} <: + AbstractTransportCapacityModel + boundary_conditions::TransportCapacityBC{T} | "-" + parameters::TransportCapacityRiverParameters{T} | "-" + variables::TransportCapacityModelVars{T} | "-" +end + +function initialize_transport_capacity_engelund_model(nc, config, inds) + n = length(inds) + vars = transport_capacity_model_vars(n) + params = initialize_transport_capacity_river_params(nc, config, inds) + bc = transport_capacity_bc(n) + model = TransportCapacityEngelundModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::TransportCapacityEngelundModel, geometry::RiverGeometry, ts) + (; q, waterlevel) = model.boundary_conditions + (; density, d50) = model.parameters + (; amount) = model.variables + + n = length(q) + threaded_foreach(1:n; basesize = 1000) do i + amount[i] = transport_capacity_engelund( + q[i], + waterlevel[i], + density[i], + d50[i], + geometry.width[i], + geometry.length[i], + geometry.slope[i], + ts, + ) + end +end + +# Kodatie parameters for transport capacity models +@get_units @with_kw struct TransportCapacityKodatieParameters{T} + # Kodatie transport capacity coefficient a + a_kodatie::Vector{T} | "-" + # Kodatie transport capacity coefficient b + b_kodatie::Vector{T} | "-" + # Kodatie transport capacity coefficient c + c_kodatie::Vector{T} | "-" + # Kodatie transport capacity coefficient d + d_kodatie::Vector{T} | "-" +end + +function initialize_transport_capacity_kodatie_params(nc, config, inds) + a_kodatie = ncread( + nc, + config, + "lateral.river.transport_capacity.parameters.a_kodatie"; + sel = inds, + optional = false, + type = Float, + ) + b_kodatie = ncread( + nc, + config, + "lateral.river.transport_capacity.parameters.b_kodatie"; + sel = inds, + optional = false, + type = Float, + ) + c_kodatie = ncread( + nc, + config, + "lateral.river.transport_capacity.parameters.c_kodatie"; + sel = inds, + optional = false, + type = Float, + ) + d_kodatie = ncread( + nc, + config, + "lateral.river.transport_capacity.parameters.d_kodatie"; + sel = inds, + optional = false, + type = Float, + ) + tc_parameters = TransportCapacityKodatieParameters(; + a_kodatie = a_kodatie, + b_kodatie = b_kodatie, + c_kodatie = c_kodatie, + d_kodatie = d_kodatie, + ) + + return tc_parameters +end + +@get_units @with_kw struct TransportCapacityKodatieModel{T} <: + AbstractTransportCapacityModel + boundary_conditions::TransportCapacityBC{T} | "-" + parameters::TransportCapacityKodatieParameters{T} | "-" + variables::TransportCapacityModelVars{T} | "-" +end + +function initialize_transport_capacity_kodatie_model(nc, config, inds) + n = length(inds) + vars = transport_capacity_model_vars(n) + params = initialize_transport_capacity_kodatie_params(nc, config, inds) + bc = transport_capacity_bc(n) + model = TransportCapacityKodatieModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::TransportCapacityKodatieModel, geometry::RiverGeometry, ts) + (; q, waterlevel) = model.boundary_conditions + (; a_kodatie, b_kodatie, c_kodatie, d_kodatie) = model.parameters + (; amount) = model.variables + + n = length(q) + threaded_foreach(1:n; basesize = 1000) do i + amount[i] = transport_capacity_kodatie( + q[i], + waterlevel[i], + a_kodatie[i], + b_kodatie[i], + c_kodatie[i], + d_kodatie[i], + geometry.width[i], + geometry.length[i], + geometry.slope[i], + ts, + ) + end +end + +# Yang parameters for transport capacity models +@get_units @with_kw struct TransportCapacityYangModel{T} <: AbstractTransportCapacityModel + boundary_conditions::TransportCapacityBC{T} | "-" + parameters::TransportCapacityRiverParameters{T} | "-" + variables::TransportCapacityModelVars{T} | "-" +end + +function initialize_transport_capacity_yang_model(nc, config, inds) + n = length(inds) + vars = transport_capacity_model_vars(n) + params = initialize_transport_capacity_river_params(nc, config, inds) + bc = transport_capacity_bc(n) + model = TransportCapacityYangModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::TransportCapacityYangModel, geometry::RiverGeometry, ts) + (; q, waterlevel) = model.boundary_conditions + (; density, d50) = model.parameters + (; amount) = model.variables + + n = length(q) + threaded_foreach(1:n; basesize = 1000) do i + amount[i] = transport_capacity_yang( + q[i], + waterlevel[i], + density[i], + d50[i], + geometry.width[i], + geometry.length[i], + geometry.slope[i], + ts, + ) + end +end + +# Molinas and Wu parameters for transport capacity models +@get_units @with_kw struct TransportCapacityMolinasModel{T} <: + AbstractTransportCapacityModel + boundary_conditions::TransportCapacityBC{T} | "-" + parameters::TransportCapacityRiverParameters{T} | "-" + variables::TransportCapacityModelVars{T} | "-" +end + +function initialize_transport_capacity_molinas_model(nc, config, inds) + n = length(inds) + vars = transport_capacity_model_vars(n) + params = initialize_transport_capacity_river_params(nc, config, inds) + bc = transport_capacity_bc(n) + model = TransportCapacityMolinasModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::TransportCapacityMolinasModel, geometry::RiverGeometry, ts) + (; q, waterlevel) = model.boundary_conditions + (; density, d50) = model.parameters + (; amount) = model.variables + + n = length(q) + threaded_foreach(1:n; basesize = 1000) do i + amount[i] = transport_capacity_molinas( + q[i], + waterlevel[i], + density[i], + d50[i], + geometry.width[i], + geometry.length[i], + geometry.slope[i], + ts, + ) + end end \ No newline at end of file diff --git a/src/sediment_transport/transport_capacity_process.jl b/src/sediment_transport/transport_capacity_process.jl index 7a900bded..f4b85e4df 100644 --- a/src/sediment_transport/transport_capacity_process.jl +++ b/src/sediment_transport/transport_capacity_process.jl @@ -29,6 +29,45 @@ function mask_transport_capacity(transport_capacity, waterbodies, rivers) return tc end +""" + limit_and_convert_transport_capacity( + transport_capacity, + q, + waterlevel, + width, + length, + ts, + ) + +Limit to stremaflow and not debris flow and convert transport in ton/m3 to ton. + +# Arguments +- `transport_capacity` (total sediment transport capacity [t m-3]) +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `width` (drain width [m]) +- `length` (drain length [m]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function limit_and_convert_transport_capacity( + transport_capacity, + q, + waterlevel, + width, + length, + ts, +) + # 1285 g/L: boundary between streamflow and debris flow (Costa, 1988) + transport_capacity = min(transport_capacity, 1.285) + # Transport capacity [ton] + transport_capacity = transport_capacity * (waterlevel * width * length + q * ts) + + return transport_capacity +end + """ transport_capacity_govers( q, @@ -283,5 +322,327 @@ function transport_capacity_yalin_differentiation( # Mask transport capacity values for waterbodies and rivers transport_capacity = mask_transport_capacity(transport_capacity, waterbodies, rivers) + return transport_capacity +end + +""" + function trasnport_capacity_bagnold( + q, + waterlevel, + c_bagnold, + e_bagnold, + width, + length, + ts, + ) + +Total sediment transport capacity based on Bagnold. + +# Arguments +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `c_bagnold` (Bagnold transport capacity coefficient [-]) +- `e_bagnold` (Bagnold transport capacity exponent [-]) +- `width` (drain width [m]) +- `length` (drain length [m]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function transport_capacity_bagnold(q, waterlevel, c_bagnold, e_bagnold, width, length, ts) + # Transport capacity from Bagnold + if waterlevel > 0.0 + # Transport capacity [tons/m3] + transport_capacity = c_bagnold * (q / (waterlevel * width))^e_bagnold + transport_capacity = limit_and_convert_transport_capacity( + transport_capacity, + q, + waterlevel, + width, + length, + ts, + ) + else + transport_capacity = 0.0 + end + + return transport_capacity +end + +""" + function trasnport_capacity_engelund( + q, + waterlevel, + density, + d50, + width, + length, + slope, + ts, + ) + +Total sediment transport capacity based on Engelund and Hansen. + +# Arguments +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `density` (sediment density [kg m-3]) +- `d50` (median grain size [m]) +- `width` (drain width [m]) +- `length` (drain length [m]) +- `slope` (slope [-]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function transport_capacity_engelund(q, waterlevel, density, d50, width, length, slope, ts) + # Transport capacity from Engelund and Hansen + if waterlevel > 0.0 + # Hydraulic radius of the river [m] (rectangular channel) + hydrad = waterlevel * width / (width + 2 * waterlevel) + vshear = sqrt(9.81 * hydrad * slope) + + # Flow velocity [m/s] + velocity = (q / (waterlevel * width)) + + # Concentration by weight + cw = + density / 1000 * 0.05 * velocity * vshear^3 / + ((density / 1000 - 1)^2 * 9.81^2 * d50 * hydrad) + cw = min(1.0, cw) + + # Transport capacity [tons/m3] + transport_capacity = cw / (cw + (1 - cw) * density / 1000) * density / 1000 + transport_capacity = max(transport_capacity, 0.0) + # Transport capacity [tons] + transport_capacity = limit_and_convert_transport_capacity( + transport_capacity, + q, + waterlevel, + width, + length, + ts, + ) + + else + transport_capacity = 0.0 + end + + return transport_capacity +end + +""" + function trasnport_capacity_kodatie( + q, + waterlevel, + a_kodatie, + b_kodatie, + c_kodatie, + d_kodatie, + width, + length, + slope, + ts, + ) + +Total sediment transport capacity based on Kodatie. + +# Arguments +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `a_kodatie` (Kodatie transport capacity coefficient [-]) +- `b_kodatie` (Kodatie transport capacity coefficient [-]) +- `c_kodatie` (Kodatie transport capacity coefficient [-]) +- `d_kodatie` (Kodatie transport capacity coefficient [-]) +- `width` (drain width [m]) +- `slope` (slope [-]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function transport_capacity_kodatie( + q, + waterlevel, + a_kodatie, + b_kodatie, + c_kodatie, + d_kodatie, + width, + length, + slope, + ts, +) + # Transport capacity from Kodatie + if waterlevel > 0.0 + # Flow velocity [m/s] + velocity = (q / (waterlevel * width)) + + # Concentration + transport_capacity = + a_kodatie * velocity^b_kodatie * waterlevel^c_kodatie * slope^d_kodatie + + # Transport capacity [tons/m3] + transport_capacity = transport_capacity * width / (q * ts) + transport_capacity = limit_and_convert_transport_capacity( + transport_capacity, + q, + waterlevel, + width, + length, + ts, + ) + + else + transport_capacity = 0.0 + end + + return transport_capacity +end + +""" + function trasnport_capacity_yang( + q, + waterlevel, + density, + d50, + width, + length, + slope, + ts, + ) + +Total sediment transport capacity based on Yang sand and gravel equations. + +# Arguments +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `density` (sediment density [kg m-3]) +- `d50` (median grain size [m]) +- `width` (drain width [m]) +- `length` (drain length [m]) +- `slope` (slope [-]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function transport_capacity_yang(q, waterlevel, density, d50, width, length, slope, ts) + # Transport capacity from Yang + omegas = 411 * d50^2 / 3600 + # Hydraulic radius of the river [m] (rectangular channel) + hydrad = waterlevel * width / (width + 2 * waterlevel) + # Critical shear stress velocity + vshear = sqrt(9.81 * hydrad * slope) + var1 = vshear * d50 / 1000 / (1.16 * 1e-6) + var2 = omegas * d50 / 1000 / (1.16 * 1e-6) + vcr = ifelse(var1 >= 70.0, 2.05 * omegas, omegas * (2.5 / (log10(var1) - 0.06) + 0.66)) + vcr = min(vcr, 0.0) + + # Sand equation + if (width * waterlevel) > vcr && d50 < 2.0 + logcppm = ( + 5.435 - 0.286 * log10(var2) - 0.457 * log10(vshear / omegas) + 1.799 - + 0.409 * log10(var2) - + 0.314 * + log10(vshear / omegas) * + log10((q / (width * waterlevel) - vcr) * slope / omegas) + ) + # Gravel equation + elseif (width * waterlevel) > vcr && d50 >= 2.0 + logcppm = ( + 6.681 - 0.633 * log10(var2) - 4.816 * log10(vshear / omegas) + 2.784 - + 0.305 * log10(var2) - + 0.282 * + log10(vshear / omegas) * + log10((q / (width * waterlevel) - vcr) * slope / omegas) + ) + else + logcppm = 0.0 + end + + # Sediment concentration by weight + cw = 10^logcppm * 1e-6 + # Transport capacity [tons/m3] + transport_capacity = cw / (cw + (1 - cw) * density / 1000) * density / 1000 + transport_capacity = max(transport_capacity, 0.0) + # Transport capacity [tons] + transport_capacity = limit_and_convert_transport_capacity( + transport_capacity, + q, + waterlevel, + width, + length, + ts, + ) + + return transport_capacity +end + +""" + function trasnport_capacity_molinas( + q, + waterlevel, + density, + d50, + width, + length, + slope, + ts, + ) + +Total sediment transport capacity based on Molinas and Wu. + +# Arguments +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `density` (sediment density [kg m-3]) +- `d50` (median grain size [m]) +- `width` (drain width [m]) +- `length` (drain length [m]) +- `slope` (slope [-]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function transport_capacity_molinas(q, waterlevel, density, d50, width, length, slope, ts) + # Transport capacity from Molinas and Wu + if waterlevel > 0.0 + # Flow velocity [m/s] + velocity = (q / (waterlevel * width)) + omegas = 411 * d50^2 / 3600 + + # PSI parameter + psi = ( + velocity^3 / ( + (density / 1000 - 1) * + 9.81 * + waterlevel * + omegas * + log10(1000 * waterlevel / d50)^2 + ) + ) + # Concentration by weight + cw = 1430 * (0.86 + psi^0.5) * psi^1.5 / (0.016 + psi) * 1e-6 + # Transport capacity [tons/m3] + transport_capacity = cw / (cw + (1 - cw) * density / 1000) * density / 1000 + transport_capacity = max(transport_capacity, 0.0) + # Transport capacity [tons] + transport_capacity = limit_and_convert_transport_capacity( + transport_capacity, + q, + waterlevel, + width, + length, + ts, + ) + + else + transport_capacity = 0.0 + end + return transport_capacity end \ No newline at end of file diff --git a/src/states.jl b/src/states.jl index f1c14e551..7cb8f47e7 100644 --- a/src/states.jl +++ b/src/states.jl @@ -81,7 +81,7 @@ 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) @@ -118,24 +118,24 @@ function extract_required_states(config::Config) # River states if model_type == "sediment" river_states = ( - :clayload, - :siltload, - :sandload, - :saggload, - :laggload, - :gravload, - :claystore, - :siltstore, - :sandstore, - :saggstore, - :laggstore, - :gravstore, - :outclay, - :outsilt, - :outsand, - :outsagg, - :outlagg, - :outgrav, + :leftover_clay, + :leftover_silt, + :leftover_sand, + :leftover_sagg, + :leftover_lagg, + :leftover_gravel, + :store_clay, + :store_silt, + :store_sand, + :store_sagg, + :store_lagg, + :store_gravel, + :clay, + :silt, + :sand, + :sagg, + :lagg, + :gravel, ) elseif model_type == "sbm" || model_type == "sbm_gwf" river_states = (:q, :h, :h_av) @@ -187,11 +187,8 @@ function extract_required_states(config::Config) reservoir_states, ) # Add paddy states to dict - required_states = add_to_required_states( - required_states, - (:vertical, :paddy), - paddy_states, - ) + required_states = + add_to_required_states(required_states, (:vertical, :paddy), paddy_states) return required_states end diff --git a/test/run_sediment.jl b/test/run_sediment.jl index dce0b4d02..d43eba054 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -56,10 +56,12 @@ end @test mean(land.to_river.variables.sand) ≈ 0.02575351604673049f0 @test mean(land.sediment_flux.variables.clay) ≈ 0.006578791733506439f0 - # @test mean(lat.river.SSconc) ≈ 0.8259993252994058f0 - # @test mean(lat.river.inlandclay) ≈ 0.01980468760667709f0 - # @test lat.river.h_riv[network.river.order[end]] ≈ 0.006103649735450745f0 - # @test lat.river.outclay[5649] ≈ 2.359031898208781f-9 + @test mean(lat.river.concentrations.variables.suspended) ≈ 0.8259993252994058f0 + @test mean(lat.river.sediment_flux.boundary_conditions.erosion_land_clay) ≈ + 0.01980468760667709f0 + @test lat.river.hydrometeo_forcing.waterlevel_river[network.river.order[end]] ≈ + 0.006103649735450745f0 + @test lat.river.sediment_flux.varibales.clay[5649] ≈ 2.359031898208781f-9 end # @testset "Exchange and grid location sediment" begin diff --git a/test/sediment_config.toml b/test/sediment_config.toml index 48abb84db..29a5eea21 100644 --- a/test/sediment_config.toml +++ b/test/sediment_config.toml @@ -18,25 +18,25 @@ path_output = "outstates-moselle-sed.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.lateral.river] -clayload = "clayload" -claystore = "claystore" -gravload = "gravload" -gravstore = "gravstore" -laggload = "laggload" -laggstore = "laggstore" -outclay = "outclay" -outgrav = "outgrav" -outlagg = "outlagg" -outsagg = "outsagg" -outsand = "outsand" -outsilt = "outsilt" -saggload = "saggload" -saggstore = "saggstore" -sandload = "sandload" -sandstore = "sandstore" -siltload = "siltload" -siltstore = "siltstore" +[state.lateral.river.sediment_flux.variables] +leftover_clay = "clayload" +store_clay = "claystore" +leftover_gravel = "gravload" +store_gravel = "gravstore" +leftover_lagg = "laggload" +store_lagg = "laggstore" +clay = "outclay" +gravel = "outgrav" +lagg= "outlagg" +sagg = "outsagg" +sand = "outsand" +silt = "outsilt" +leftover_sagg= "saggload" +store_sagg = "saggstore" +leftover_sand = "sandload" +store_sand = "sandstore" +leftover_silt = "siltload" +store_silt = "siltstore" [input] path_forcing = "forcing-moselle-sed.nc" @@ -59,21 +59,19 @@ forcing = [ "lateral.land.hydrometeo_forcing.waterlevel_land", "vertical.hydrometeo_forcing.q_land", "lateral.land.hydrometeo_forcing.q_land", - "lateral.river.h_riv", - "lateral.river.q_riv", + "lateral.river.hydrometeo_forcing.waterlevel_river", + "lateral.river.hydrometeo_forcing.q_river", ] #cyclic = ["vertical.leaf_area_index"] -[input.vertical] +#[input.vertical] ## interception parameters to be moved #kext = "Kext" #leaf_area_index = "LAI" # cyclic #specific_leaf = "Sl" #storage_wood = "Swood" ## other parameters -pathfrac = "PathFrac" -slope = "Slope" [input.vertical.hydrometeo_forcing] precipitation = "P" @@ -81,6 +79,9 @@ waterlevel_land = "levKinL" #interception = "int" q_land = "runL" +[input.vertical.geometry] +slope = "Slope" + [input.vertical.rainfall_erosion.parameters] soil_detachability = "soil_detachability" eurosem_exponent = "eros_spl_EUROSEM" @@ -88,6 +89,7 @@ canopyheight = "CanopyHeight" canopygapfraction = "CanopyGapFraction" usle_k = "usle_k" usle_c = "USLE_C" +pathfrac = "PathFrac" [input.vertical.overland_flow_erosion.parameters] usle_k = "usle_k" @@ -106,7 +108,6 @@ waterlevel_land = "levKinL" q_land = "runL" [input.lateral.land.transport_capacity.parameters] -slope = "Slope" density = "sediment_density" d50 = "d50_soil" c_govers = "c_govers" @@ -117,30 +118,55 @@ dm_sand = "dm_sand" dm_sagg = "dm_sagg" dm_lagg = "dm_lagg" -[input.lateral.river] -h_riv = "h" -q_riv = "q" -cbagnold = "c_Bagnold" -d50 = "D50_River" -d50engelund = "D50_River" -ebagnold = "exp_Bagnold" -fclayriv = "ClayF_River" -fgravriv = "GravelF_River" -fsandriv = "SandF_River" -fsiltriv = "SiltF_River" +[input.lateral.river.hydrometeo_forcing] +waterlevel_river = "h" +q_river = "q" + +[input.lateral.river.geometry] length = "wflow_riverlength" slope = "RiverSlope" width = "wflow_riverwidth" + +[input.lateral.river.transport_capacity.parameters] +density = "sediment_density" +d50 = "D50_River" +c_bagnold = "c_Bagnold" +e_bagnold = "exp_Bagnold" +a_kodatie = "a_kodatie" +b_kodatie = "b_kodatie" +c_kodatie = "c_kodatie" +d_kodatie = "d_kodatie" + +[input.lateral.river.potential_erosion.parameters] +d50 = "D50_River" + +[input.lateral.river.sediment_flux.parameters] +clay_fraction = "ClayF_River" +silt_fraction = "SiltF_River" +sand_fraction = "SandF_River" +gravel_fraction = "GravelF_River" +dm_clay = "dm_clay" +dm_silt = "dm_silt" +dm_sand = "dm_sand" +dm_sagg = "dm_sagg" +dm_lagg = "dm_lagg" +dm_gravel = "dm_gravel" # Reservoir resarea = "ResSimpleArea" restrapeff = "ResTrapEff" -resareas = "wflow_reservoirareas" reslocs = "wflow_reservoirlocs" # Lake lakearea = "LakeArea" -lakeareas = "wflow_lakeareas" lakelocs = "wflow_lakelocs" +[input.lateral.river.concentrations.parameters] +dm_clay = "dm_clay" +dm_silt = "dm_silt" +dm_sand = "dm_sand" +dm_sagg = "dm_sagg" +dm_lagg = "dm_lagg" +dm_gravel = "dm_gravel" + [model] dolake = false doreservoir = true # cannot use reservoirs as in sbm because then states/volume need to be added @@ -176,15 +202,20 @@ amount = "inlandsed" clay = "olclay" amount = "olsed" -# [output.lateral.river] -# Bedconc = "Bedconc" -# SSconc = "SSconc" -# Sedconc = "Sedconc" -# clayload = "clayload" -# h_riv = "h_riv" -# inlandclay = "inlandclayriv" -# outclay = "outclay" -# width = "widthriv" +[output.lateral.river.concentrations.variables] +bed = "Bedconc" +suspended = "SSconc" +total = "Sedconc" + +[output.lateral.river.hydrometeo_forcing] +waterlevel_river = "h_riv" + +[output.lateral.river.sediment_flux.variables] +leftover_clay = "clayload" +clay = "outclay" + +[output.lateral.river.sediment_flux.boundary_conditions] +erosion_land_clay = "inlandclayriv" [csv] path = "output-moselle-sediment.csv" From 903508c49462d1b1a5b0ad4c24e3e551b69f180c Mon Sep 17 00:00:00 2001 From: hboisgon Date: Wed, 25 Sep 2024 15:06:24 +0800 Subject: [PATCH 12/42] working tests and all results comparable to master --- src/erosion.jl | 2 +- src/erosion/rainfall_erosion.jl | 15 +- src/sediment_flux.jl | 75 ++---- src/sediment_model.jl | 14 +- src/sediment_transport/land_to_river.jl | 17 +- .../overland_flow_transport.jl | 4 +- src/sediment_transport/river_transport.jl | 222 ++++++++++++------ src/sediment_transport/transport_capacity.jl | 43 +++- src/states.jl | 57 +++-- test/run_sediment.jl | 30 ++- test/sediment_config.toml | 5 +- 11 files changed, 304 insertions(+), 180 deletions(-) diff --git a/src/erosion.jl b/src/erosion.jl index 18ad50797..696f7b79e 100644 --- a/src/erosion.jl +++ b/src/erosion.jl @@ -54,7 +54,7 @@ function update!(model::SoilLoss, dt) #TODO add interception/canopygapfraction calculation here for eurosem #need SBM refactor # Rainfall erosion - update!(model.rainfall_erosion, model.hydrometeo_forcing, model.area, ts) + update!(model.rainfall_erosion, model.hydrometeo_forcing, model.geometry, ts) # Overland flow erosion update!(model.overland_flow_erosion, model.hydrometeo_forcing, model.geometry, ts) # Total soil erosion and particle differentiation diff --git a/src/erosion/rainfall_erosion.jl b/src/erosion/rainfall_erosion.jl index b7643aa53..d6e16e950 100644 --- a/src/erosion/rainfall_erosion.jl +++ b/src/erosion/rainfall_erosion.jl @@ -110,7 +110,7 @@ end function update!( model::RainfallErosionEurosemModel, hydrometeo_forcing::HydrometeoForcing, - area, + geometry::LandGeometry, ts, ) (; precipitation, waterlevel_land) = hydrometeo_forcing @@ -135,7 +135,7 @@ function update!( canopyheight[i], canopygapfraction[i], soilcover_fraction[i], - area[i], + geometry.area[i], ts, ) end @@ -187,7 +187,7 @@ end function update!( model::RainfallErosionAnswersModel, hydrometeo_forcing::HydrometeoForcing, - area, + geometry::LandGeometry, ts, ) (; precipitation) = hydrometeo_forcing @@ -196,8 +196,13 @@ function update!( n = length(precipitation) threaded_foreach(1:n; basesize = 1000) do i - amount[i] = - rainfall_erosion_answers(precipitation[i], usle_k[i], usle_c[i], area[i], ts) + amount[i] = rainfall_erosion_answers( + precipitation[i], + usle_k[i], + usle_c[i], + geometry.area[i], + ts, + ) end end diff --git a/src/sediment_flux.jl b/src/sediment_flux.jl index 7d4a5c987..fc4c6feaa 100644 --- a/src/sediment_flux.jl +++ b/src/sediment_flux.jl @@ -59,21 +59,22 @@ end function update!(model::OverlandFlowSediment, erosion_model::SoilErosionModel, network, dt) # Convert dt to integer ts = tosecond(dt) - # Update the boundary conditions of transport capacity - (; q, waterlevel) = model.transport_capacity.boundary_conditions - (; q_land, waterlevel_land) = model.hydrometeo_forcing - @. q = q_land - @. waterlevel = waterlevel_land + # Transport capacity - update!(model.transport_capacity, model.width, model.waterbodies, model.rivers, ts) + update_boundary_conditions!(model.transport_capacity, model.hydrometeo_forcing, :land) + update!(model.transport_capacity, model.geometry, model.waterbodies, model.rivers, ts) # Update boundary conditions before transport - update_bc(model.sediment_flux, erosion_model, model.transport_capacity) + update_boundary_conditions!( + model.sediment_flux, + erosion_model, + model.transport_capacity, + ) # Compute transport update!(model.sediment_flux, network) # Update boundary conditions before computing sediment reaching the river - update_bc(model.to_river, model.sediment_flux) + update_boundary_conditions!(model.to_river, model.sediment_flux) # Compute sediment reaching the river update!(model.to_river, model.rivers) end @@ -152,59 +153,33 @@ function update!( ) # Convert dt to integer ts = tosecond(dt) - # Update the boundary conditions of transport capacity - (; q, waterlevel) = model.transport_capacity.boundary_conditions - (; q_river, waterlevel_river) = model.hydrometeo_forcing - @. q = q_river - @. waterlevel = waterlevel_river # Transport capacity + update_boundary_conditions!(model.transport_capacity, model.hydrometeo_forcing, :river) update!(model.transport_capacity, model.geometry, ts) # Potential maximum river erosion (; waterlevel) = model.potential_erosion.boundary_conditions + (; waterlevel_river) = model.hydrometeo_forcing @. waterlevel = waterlevel_river update!(model.potential_erosion, model.geometry, ts) # River transport - (; - waterlevel, - q, - transport_capacity, - erosion_land_clay, - erosion_land_silt, - erosion_land_sand, - erosion_land_sagg, - erosion_land_lagg, - potential_erosion_river, - ) = model.boundary_conditions - @. q = q_river - @. waterlevel = waterlevel_river - - @. transport_capacity = model.transport_capacity.variables.amount - - (; clay, silt, sand, sagg, lagg) = to_river_model.variables - @. erosion_land_clay = clay[inds_riv] - @. erosion_land_silt = silt[inds_riv] - @. erosion_land_sand = sand[inds_riv] - @. erosion_land_sagg = sagg[inds_riv] - @. erosion_land_lagg = lagg[inds_riv] - - @. potential_erosion_river = model.potential_erosion.variables.amount - - update!(model.sediment_flux, network, model.geometry, ts) + update_boundary_conditions!( + model.sediment_flux, + model.hydrometeo_forcing, + model.transport_capacity, + to_river_model, + model.potential_erosion, + inds_riv, + ) + update!(model.sediment_flux, network, model.geometry, model.waterbodies, ts) # Concentrations - (; q, waterlevel, clay, silt, sand, sagg, lagg, gravel) = - model.concentrations.boundary_conditions - @. q = q_river - @. waterlevel = waterlevel_river - @. clay = model.sediment_flux.variables.clay - @. silt = model.sediment_flux.variables.silt - @. sand = model.sediment_flux.variables.sand - @. sagg = model.sediment_flux.variables.sagg - @. lagg = model.sediment_flux.variables.lagg - @. gravel = model.sediment_flux.variables.gravel - + update_boundary_conditions!( + model.concentrations, + model.hydrometeo_forcing, + model.sediment_flux, + ) update!(model.concentrations, model.geometry, ts) end \ No newline at end of file diff --git a/src/sediment_model.jl b/src/sediment_model.jl index 59a4eaed2..ecae94839 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -93,18 +93,20 @@ function initialize_sediment_model(config::Config) index_river = filter(i -> !isequal(river[i], 0), 1:n) frac_toriver = fraction_runoff_toriver(graph, ldd, index_river, landslope, n) - rs = initialize_river_flow_sediment(nc, config, inds_riv, waterbodies) + river_sediment = initialize_river_flow_sediment(nc, config, inds_riv, waterbodies) - y_nc = read_y_axis(nc) - x_nc = read_x_axis(nc) - - modelmap = (vertical = soilloss, lateral = (land = overland_flow_sediment, river = rs)) + modelmap = ( + vertical = soilloss, + lateral = (land = overland_flow_sediment, river = river_sediment), + ) indices_reverse = ( land = rev_inds, river = rev_inds_riv, reservoir = isempty(reservoir) ? nothing : reservoir.reverse_indices, lake = isempty(lake) ? nothing : lake.reverse_indices, ) + y_nc = read_y_axis(nc) + x_nc = read_x_axis(nc) writer = prepare_writer(config, modelmap, indices_reverse, x_nc, y_nc, nc) close(nc) @@ -126,7 +128,7 @@ function initialize_sediment_model(config::Config) model = Model( config, (; land, river, reservoir, lake, index_river, frac_toriver), - (land = overland_flow_sediment, river = rs), + (land = overland_flow_sediment, river = river_sediment), soilloss, clock, reader, diff --git a/src/sediment_transport/land_to_river.jl b/src/sediment_transport/land_to_river.jl index f1910c993..5ece4f514 100644 --- a/src/sediment_transport/land_to_river.jl +++ b/src/sediment_transport/land_to_river.jl @@ -34,7 +34,10 @@ function initialize_sediment_to_river_model(inds) return model end -function update_bc(model::SedimentToRiverModel, transport_model::SedimentLandTransportModel) +function update_boundary_conditions!( + model::SedimentToRiverModel, + transport_model::SedimentLandTransportModel, +) (; deposition) = model.boundary_conditions @. deposition = transport_model.variables.deposition end @@ -114,7 +117,7 @@ function initialize_sediment_to_river_differentiation_model(inds) return model end -function update_bc( +function update_boundary_conditions!( model::SedimentToRiverDifferentiationModel, transport_model::SedimentLandTransportDifferentiationModel, ) @@ -143,11 +146,11 @@ function update!(model::SedimentToRiverDifferentiationModel, rivers) (; amount, clay, silt, sand, sagg, lagg) = model.variables zeros = fill(0.0, length(amount)) - clay .= ifelse.(rivers, deposition_clay, zeros) - silt .= ifelse.(rivers, deposition_silt, zeros) - sand .= ifelse.(rivers, deposition_sand, zeros) - sagg .= ifelse.(rivers, deposition_sagg, zeros) - lagg .= ifelse.(rivers, deposition_lagg, zeros) + clay .= ifelse.(rivers .> 0, deposition_clay, zeros) + silt .= ifelse.(rivers .> 0, deposition_silt, zeros) + sand .= ifelse.(rivers .> 0, deposition_sand, zeros) + sagg .= ifelse.(rivers .> 0, deposition_sagg, zeros) + lagg .= ifelse.(rivers .> 0, deposition_lagg, zeros) amount .= clay .+ silt .+ sand .+ sagg .+ lagg end diff --git a/src/sediment_transport/overland_flow_transport.jl b/src/sediment_transport/overland_flow_transport.jl index 89113effb..66eca1bde 100644 --- a/src/sediment_transport/overland_flow_transport.jl +++ b/src/sediment_transport/overland_flow_transport.jl @@ -38,7 +38,7 @@ function initialize_sediment_land_transport_model(inds) return model end -function update_bc( +function update_boundary_conditions!( model::SedimentLandTransportModel, erosion_model::SoilErosionModel, transport_capacity_model::AbstractTransportCapacityModel, @@ -161,7 +161,7 @@ function initialize_sediment_land_transport_differentiation_model(inds) return model end -function update_bc( +function update_boundary_conditions!( model::SedimentLandTransportDifferentiationModel, erosion_model::SoilErosionModel, transport_capacity_model::TransportCapacityYalinDifferentiationModel, diff --git a/src/sediment_transport/river_transport.jl b/src/sediment_transport/river_transport.jl index b258be3bd..a658bb497 100644 --- a/src/sediment_transport/river_transport.jl +++ b/src/sediment_transport/river_transport.jl @@ -33,26 +33,26 @@ end function sediment_river_transport_vars(n) vars = SedimentRiverTransportVars(; amount = fill(mv, n), - clay = fill(mv, n), - silt = fill(mv, n), - sand = fill(mv, n), - sagg = fill(mv, n), - lagg = fill(mv, n), - gravel = fill(mv, n), + clay = fill(0.0, n), + silt = fill(0.0, n), + sand = fill(0.0, n), + sagg = fill(0.0, n), + lagg = fill(0.0, n), + gravel = fill(0.0, n), deposition = fill(mv, n), erosion = fill(mv, n), - leftover_clay = fill(mv, n), - leftover_silt = fill(mv, n), - leftover_sand = fill(mv, n), - leftover_sagg = fill(mv, n), - leftover_lagg = fill(mv, n), - leftover_gravel = fill(mv, n), - store_clay = fill(mv, n), - store_silt = fill(mv, n), - store_sand = fill(mv, n), - store_sagg = fill(mv, n), - store_lagg = fill(mv, n), - store_gravel = fill(mv, n), + leftover_clay = fill(0.0, n), + leftover_silt = fill(0.0, n), + leftover_sand = fill(0.0, n), + leftover_sagg = fill(0.0, n), + leftover_lagg = fill(0.0, n), + leftover_gravel = fill(0.0, n), + store_clay = fill(0.0, n), + store_silt = fill(0.0, n), + store_sand = fill(0.0, n), + store_sagg = fill(0.0, n), + store_lagg = fill(0.0, n), + store_gravel = fill(0.0, n), ) return vars end @@ -71,7 +71,8 @@ end erosion_land_sagg::Vector{T} | "t dt-1" erosion_land_lagg::Vector{T} | "t dt-1" # Sediment available from direct river erosion - potential_erosion_river::Vector{T} | "t dt-1" + potential_erosion_river_bed::Vector{T} | "t dt-1" + potential_erosion_river_bank::Vector{T} | "t dt-1" end function sediment_river_transport_bc(n) @@ -84,7 +85,8 @@ function sediment_river_transport_bc(n) erosion_land_sand = fill(mv, n), erosion_land_sagg = fill(mv, n), erosion_land_lagg = fill(mv, n), - potential_erosion_river = fill(mv, n), + potential_erosion_river_bed = fill(mv, n), + potential_erosion_river_bank = fill(mv, n), ) return bc end @@ -309,7 +311,14 @@ function initialize_sediment_river_transport_model(nc, config, inds) return model end -function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeometry, ts) +function update_boundary_conditions!( + model::SedimentRiverTransportModel, + hydrometeo_forcing::HydrometeoForcing, + transport_capacity_model::AbstractTransportCapacityModel, + to_river_model::SedimentToRiverDifferentiationModel, + potential_erosion_model::AbstractRiverErosionModel, + inds_riv, +) (; waterlevel, q, @@ -322,7 +331,59 @@ function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeo potential_erosion_river_bed, potential_erosion_river_bank, ) = model.boundary_conditions - (; clay_fraction, silt_fraction, sand_fraction, gravel_fraction) = model.parameters + + # HydroMeteo forcing + (; q_river, waterlevel_river) = hydrometeo_forcing + @. q = q_river + @. waterlevel = waterlevel_river + # Transport capacity + @. transport_capacity = transport_capacity_model.variables.amount + # Input from soil erosion + (; clay, silt, sand, sagg, lagg) = to_river_model.variables + @. erosion_land_clay = clay[inds_riv] + @. erosion_land_silt = silt[inds_riv] + @. erosion_land_sand = sand[inds_riv] + @. erosion_land_sagg = sagg[inds_riv] + @. erosion_land_lagg = lagg[inds_riv] + # Maximum direct river bed/bank erosion + @. potential_erosion_river_bed = potential_erosion_model.variables.bed + @. potential_erosion_river_bank = potential_erosion_model.variables.bank +end + +function update!( + model::SedimentRiverTransportModel, + network, + geometry::RiverGeometry, + waterbodies, + ts, +) + (; + waterlevel, + q, + transport_capacity, + erosion_land_clay, + erosion_land_silt, + erosion_land_sand, + erosion_land_sagg, + erosion_land_lagg, + potential_erosion_river_bed, + potential_erosion_river_bank, + ) = model.boundary_conditions + (; + clay_fraction, + silt_fraction, + sand_fraction, + gravel_fraction, + dm_clay, + dm_silt, + dm_sand, + dm_sagg, + dm_lagg, + dm_gravel, + waterbodies_locs, + waterbodies_area, + waterbodies_trapping_efficiency, + ) = model.parameters (; amount, clay, @@ -379,7 +440,7 @@ function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeo ### River erosion ### # Erosion only if the load is below the transport capacity of the flow. - sediment_need = max(transport_capacity - input_sediment, 0.0) + sediment_need = max(transport_capacity[v] - input_sediment, 0.0) # No erosion in reservoirs if waterbodies[v] sediment_need = 0.0 @@ -399,18 +460,18 @@ function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeo # Effective sediment needed fom river bed and bank erosion [ton] effsediment_need = sediment_need - store_sediment # Relative potential erosion rates of the bed and the bank [-] - if (potential_erosion_river_bank + potential_erosion_river_bed > 0.0) + if (potential_erosion_river_bank[v] + potential_erosion_river_bed[v] > 0.0) RTEbank = - potential_erosion_river_bank / - (potential_erosion_river_bank + potential_erosion_river_bed) + potential_erosion_river_bank[v] / + (potential_erosion_river_bank[v] + potential_erosion_river_bed[v]) else RTEbank = 0.0 end RTEbed = 1.0 - RTEbank # Actual bed and bank erosion - erosion_bank = max(RTEbank * effsediment_need, potential_erosion_river_bank) - erosion_bed = max(RTEbed * effsediment_need, potential_erosion_river_bed) + erosion_bank = min(RTEbank * effsediment_need, potential_erosion_river_bank[v]) + erosion_bed = min(RTEbed * effsediment_need, potential_erosion_river_bed[v]) erosion_river = erosion_bank + erosion_bed # Per particle erosion_clay = erosion_river * clay_fraction[v] @@ -484,6 +545,15 @@ function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeo erosion_sagg + erosion_lagg + erosion_gravel + #if erosion[v] > transport_capacity[v] + # println("Erosion exceeds transport capacity for node $v") + #end + if v == 5642 + println("Erosion at node 5642: ", erosion[v]) + println("Transport capacity at node 5642: ", transport_capacity[v]) + println("Sediment stored at node 5642: ", store_sediment) + println("Sediemnt need: ", sediment_need) + end ### Deposition / settling ### # Different deposition if waterbody outlet or river @@ -553,8 +623,8 @@ function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeo deposition_gravel = 0.0 else # Deposition in the river - # From trasnport capacity exceedance - excess_sediment = max(input_sediment - transport_capacity, 0.0) + # From transport capacity exceedance + excess_sediment = max(input_sediment - transport_capacity[v], 0.0) if excess_sediment > 0.0 # Sediment deposited in the channel (from gravel to clay) [ton] # Gravel @@ -623,7 +693,7 @@ function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeo min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_lagg[v] / 1000)^2 / 3600))) deposition_lagg = xlagg * (input_lagg + erosion_lagg) xgrav = - min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_grav[v] / 1000)^2 / 3600))) + min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_gravel[v] / 1000)^2 / 3600))) deposition_gravel = xgrav * (input_gravel + erosion_gravel) end end @@ -649,25 +719,31 @@ function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeo # Sediment transported out of the cell during the timestep [ton] # 0 in case all sediment are deposited in the cell # Reduce the fraction so that there is still some sediment staying in the river cell - fwaterout = - min(q[v] * ts / (waterlevel[v] * geometry.width[v] * geometry.length[v]), 1.0) - clay = fwaterout * (input_clay + erosion_clay - deposition_clay) - silt = fwaterout * (input_silt + erosion_silt - deposition_silt) - sand = fwaterout * (input_sand + erosion_sand - deposition_sand) - sagg = fwaterout * (input_sagg + erosion_sagg - deposition_sagg) - lagg = fwaterout * (input_lagg + erosion_lagg - deposition_lagg) - gravel = fwaterout * (input_gravel + erosion_gravel - deposition_gravel) + if waterlevel[v] > 0.0 + fwaterout = min( + q[v] * ts / (waterlevel[v] * geometry.width[v] * geometry.length[v]), + 1.0, + ) + else + fwaterout = 1.0 + end + clay[v] = fwaterout * (input_clay + erosion_clay - deposition_clay) + silt[v] = fwaterout * (input_silt + erosion_silt - deposition_silt) + sand[v] = fwaterout * (input_sand + erosion_sand - deposition_sand) + sagg[v] = fwaterout * (input_sagg + erosion_sagg - deposition_sagg) + lagg[v] = fwaterout * (input_lagg + erosion_lagg - deposition_lagg) + gravel[v] = fwaterout * (input_gravel + erosion_gravel - deposition_gravel) - amount = clay + silt + sand + sagg + lagg + gravel + amount[v] = clay[v] + silt[v] + sand[v] + sagg[v] + lagg[v] + gravel[v] ### Leftover / mass balance ### # Sediment left in the cell [ton] - leftover_clay[v] = input_clay + erosion_clay - deposition_clay - clay - leftover_silt[v] = input_silt + erosion_silt - deposition_silt - silt - leftover_sand[v] = input_sand + erosion_sand - deposition_sand - sand - leftover_sagg[v] = input_sagg + erosion_sagg - deposition_sagg - sagg - leftover_lagg[v] = input_lagg + erosion_lagg - deposition_lagg - lagg - leftover_gravel[v] = input_gravel + erosion_gravel - deposition_gravel - gravel + leftover_clay[v] = input_clay + erosion_clay - deposition_clay - clay[v] + leftover_silt[v] = input_silt + erosion_silt - deposition_silt - silt[v] + leftover_sand[v] = input_sand + erosion_sand - deposition_sand - sand[v] + leftover_sagg[v] = input_sagg + erosion_sagg - deposition_sagg - sagg[v] + leftover_lagg[v] = input_lagg + erosion_lagg - deposition_lagg - lagg[v] + leftover_gravel[v] = input_gravel + erosion_gravel - deposition_gravel - gravel[v] end end @@ -821,47 +897,61 @@ function initialize_sediment_concentrations_river_model(nc, config, inds) return model end +function update_boundary_conditions!( + model::SedimentConcentrationsRiverModel, + hydrometeo_forcing::HydrometeoForcing, + sediment_flux_model::AbstractSedimentRiverTransportModel, +) + (; q, waterlevel, clay, silt, sand, sagg, lagg, gravel) = model.boundary_conditions + # Hydrometeo forcing + (; q_river, waterlevel_river) = hydrometeo_forcing + @. q = q_river + @. waterlevel = waterlevel_river + # Sediment flux per particle + @. clay = sediment_flux_model.variables.clay + @. silt = sediment_flux_model.variables.silt + @. sand = sediment_flux_model.variables.sand + @. sagg = sediment_flux_model.variables.sagg + @. lagg = sediment_flux_model.variables.lagg + @. gravel = sediment_flux_model.variables.gravel +end + function update!(model::SedimentConcentrationsRiverModel, geometry::RiverGeometry, ts) (; q, waterlevel, clay, silt, sand, sagg, lagg, gravel) = model.boundary_conditions (; dm_clay, dm_silt, dm_sand, dm_sagg, dm_lagg, dm_gravel) = model.parameters (; total, suspended, bed) = model.variables - zeros = zeros(Float, length(q)) + zeros = fill(0.0, length(q)) # Conversion from load [ton] to concentration for rivers [mg/L] - toconc .= ifelse.(q .> 0.0, 1e6 ./ (q .* ts), zeros) + toconc = ifelse.(q .> 0.0, 1e6 ./ (q .* ts), zeros) # Differentiation of bed and suspended load using Rouse number for suspension # threshold diameter between bed load and mixed load using Rouse number - dbedf .= + dbedf = 1e3 .* (2.5 .* 3600 .* 0.41 ./ 411 .* (9.81 .* waterlevel .* geometry.slope) .^ 0.5) .^ 0.5 # threshold diameter between suspended load and mixed load using Rouse number - dsuspf .= + dsuspf = 1e3 .* (1.2 .* 3600 .* 0.41 ./ 411 .* (9.81 .* waterlevel .* geometry.slope) .^ 0.5) .^ 0.5 # Rouse with diameter - SSclay .= - ifelse.(dm_clay .<= dsuspf, clay, ifelse.(dm_clay .<= dbedf, clay ./ 2, zeros)) - SSsilt .= - ifelse.(dm_silt .<= dsuspf, silt, ifelse.(dm_silt .<= dbedf, silt ./ 2, zeros)) - SSsand .= - ifelse.(dm_sand .<= dsuspf, sand, ifelse.(dm_sand .<= dbedf, sand ./ 2, zeros)) - SSsagg .= - ifelse.(dm_sagg .<= dsuspf, sagg, ifelse.(dm_sagg .<= dbedf, sagg ./ 2, zeros)) - SSlagg .= - ifelse.(dm_lagg .<= dsuspf, lagg, ifelse.(dm_lagg .<= dbedf, lagg ./ 2, zeros)) - SSgrav .= + SSclay = ifelse.(dm_clay .<= dsuspf, clay, ifelse.(dm_clay .<= dbedf, clay ./ 2, zeros)) + SSsilt = ifelse.(dm_silt .<= dsuspf, silt, ifelse.(dm_silt .<= dbedf, silt ./ 2, zeros)) + SSsand = ifelse.(dm_sand .<= dsuspf, sand, ifelse.(dm_sand .<= dbedf, sand ./ 2, zeros)) + SSsagg = ifelse.(dm_sagg .<= dsuspf, sagg, ifelse.(dm_sagg .<= dbedf, sagg ./ 2, zeros)) + SSlagg = ifelse.(dm_lagg .<= dsuspf, lagg, ifelse.(dm_lagg .<= dbedf, lagg ./ 2, zeros)) + SSgrav = ifelse.( dm_gravel .<= dsuspf, gravel, ifelse.(dm_gravel .<= dbedf, gravel ./ 2, zeros), ) - SS .= SSclay .+ SSsilt .+ SSsagg .+ SSsand .+ SSlagg .+ SSgrav - Bedload .= (clay .+ silt .+ sagg .+ sand .+ lagg .+ gravel) .- SS + SS = SSclay .+ SSsilt .+ SSsagg .+ SSsand .+ SSlagg .+ SSgrav + Bedload = (clay .+ silt .+ sagg .+ sand .+ lagg .+ gravel) .- SS - suspended .= SS .* toconc - bed .= Bedload .* toconc - total .= (clay .+ silt .+ sagg .+ sand .+ lagg .+ gravel) .* toconc + @. suspended = SS .* toconc + @. bed = Bedload .* toconc + @. total = (clay .+ silt .+ sagg .+ sand .+ lagg .+ gravel) .* toconc end \ No newline at end of file diff --git a/src/sediment_transport/transport_capacity.jl b/src/sediment_transport/transport_capacity.jl index c0d4f999c..1931e4258 100644 --- a/src/sediment_transport/transport_capacity.jl +++ b/src/sediment_transport/transport_capacity.jl @@ -23,6 +23,23 @@ function transport_capacity_bc(n) return bc end +function update_boundary_conditions!( + model::AbstractTransportCapacityModel, + hydrometeo_forcing::HydrometeoForcing, + model_type::Symbol, +) + (; q, waterlevel) = model.boundary_conditions + (; q_land, waterlevel_land, q_river, waterlevel_river) = hydrometeo_forcing + + if model_type == :land + @. q = q_land + @. waterlevel = waterlevel_land + elseif model_type == :river + @. q = q_river + @. waterlevel = waterlevel_river + end +end + ##################### Overland Flow ##################### # Govers parameters for transport capacity models @@ -329,13 +346,13 @@ end function update!( model::TransportCapacityYalinDifferentiationModel, - width, + geometry::LandGeometry, waterbodies, rivers, ts, ) (; q, waterlevel) = model.boundary_conditions - (; slope, density, dm_clay, dm_silt, dm_sand, dm_sagg, dm_lagg) = model.parameters + (; density, dm_clay, dm_silt, dm_sand, dm_sagg, dm_lagg) = model.parameters (; amount, clay, silt, sand, sagg, lagg) = model.variables n = length(q) @@ -348,15 +365,15 @@ function update!( dm_sand[i], dm_sagg[i], dm_lagg[i], - slope[i], + geometry.slope[i], ) clay[i] = transport_capacity_yalin_differentiation( q[i], waterlevel[i], density[i], dm_clay[i], - slope[i], - width[i], + geometry.slope[i], + geometry.width[i], waterbodies[i], rivers[i], dtot, @@ -367,8 +384,8 @@ function update!( waterlevel[i], density[i], dm_silt[i], - slope[i], - width[i], + geometry.slope[i], + geometry.width[i], waterbodies[i], rivers[i], dtot, @@ -379,8 +396,8 @@ function update!( waterlevel[i], density[i], dm_sand[i], - slope[i], - width[i], + geometry.slope[i], + geometry.width[i], waterbodies[i], rivers[i], dtot, @@ -391,8 +408,8 @@ function update!( waterlevel[i], density[i], dm_sagg[i], - slope[i], - width[i], + geometry.slope[i], + geometry.width[i], waterbodies[i], rivers[i], dtot, @@ -403,8 +420,8 @@ function update!( waterlevel[i], density[i], dm_lagg[i], - slope[i], - width[i], + geometry.slope[i], + geometry.width[i], waterbodies[i], rivers[i], dtot, diff --git a/src/states.jl b/src/states.jl index 7cb8f47e7..7af020475 100644 --- a/src/states.jl +++ b/src/states.jl @@ -37,6 +37,30 @@ function get_vertical_states(model_type::AbstractString; snow = false, glacier = return vertical_states end +function get_sediment_states() + states = ( + :leftover_clay, + :leftover_silt, + :leftover_sand, + :leftover_sagg, + :leftover_lagg, + :leftover_gravel, + :store_clay, + :store_silt, + :store_sand, + :store_sagg, + :store_lagg, + :store_gravel, + :clay, + :silt, + :sand, + :sagg, + :lagg, + :gravel, + ) + return states +end + """ add_to_required_states(required_states::Tuple, key_entry::Tuple, states::Tuple) add_to_required_states(required_states::Tuple, key_entry::Tuple, states::Nothing) @@ -117,26 +141,7 @@ function extract_required_states(config::Config) # River states if model_type == "sediment" - river_states = ( - :leftover_clay, - :leftover_silt, - :leftover_sand, - :leftover_sagg, - :leftover_lagg, - :leftover_gravel, - :store_clay, - :store_silt, - :store_sand, - :store_sagg, - :store_lagg, - :store_gravel, - :clay, - :silt, - :sand, - :sagg, - :lagg, - :gravel, - ) + river_states = get_sediment_states() elseif model_type == "sbm" || model_type == "sbm_gwf" river_states = (:q, :h, :h_av) else @@ -169,8 +174,16 @@ function extract_required_states(config::Config) required_states = add_to_required_states(required_states, (:lateral, :land), land_states) # Add river states to dict - required_states = - add_to_required_states(required_states, (:lateral, :river), river_states) + if model_type == "sediment" + required_states = add_to_required_states( + required_states, + (:lateral, :river, :sediment_flux, :variables), + river_states, + ) + else + required_states = + add_to_required_states(required_states, (:lateral, :river), river_states) + end # Add floodplain states to dict required_states = add_to_required_states( required_states, diff --git a/test/run_sediment.jl b/test/run_sediment.jl index d43eba054..139fa03a1 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -42,6 +42,7 @@ end @testset "second timestep sediment model (lateral)" begin land = model.lateral.land + river = model.lateral.river @test land.transport_capacity.parameters.dm_sand[1] == 200.0f0 @test land.transport_capacity.parameters.dm_lagg[1] == 500.0f0 @@ -52,16 +53,31 @@ end @test mean(land.transport_capacity.variables.clay) ≈ 1.0992655197016734f6 @test mean(land.to_river.variables.amount) ≈ 0.07624135182616738f0 - @test mean(land.to_river.variables.clay) ≈ 0.002285341387958068f0 - @test mean(land.to_river.variables.sand) ≈ 0.02575351604673049f0 + @test sum(land.to_river.variables.clay) ≈ 114.42704329506047f0 + @test sum(land.to_river.variables.sand) ≈ 1289.4785484597958f0 @test mean(land.sediment_flux.variables.clay) ≈ 0.006578791733506439f0 - @test mean(lat.river.concentrations.variables.suspended) ≈ 0.8259993252994058f0 - @test mean(lat.river.sediment_flux.boundary_conditions.erosion_land_clay) ≈ - 0.01980468760667709f0 - @test lat.river.hydrometeo_forcing.waterlevel_river[network.river.order[end]] ≈ + @test mean(river.hydrometeo_forcing.q_river) ≈ 0.6975180562953642f0 + @test river.hydrometeo_forcing.waterlevel_river[network.river.order[end]] ≈ 0.006103649735450745f0 - @test lat.river.sediment_flux.varibales.clay[5649] ≈ 2.359031898208781f-9 + @test mean(river.geometry.width) ≈ 22.628250814095523f0 + + @test mean(river.transport_capacity.boundary_conditions.q) ≈ 0.6975180562953642f0 + @test mean(river.transport_capacity.variables.amount) ≈ 0.4458019733090582f0 + @test mean(river.potential_erosion.variables.bed) ≈ 307.18492138827116f0 + + @test sum(river.sediment_flux.boundary_conditions.erosion_land_clay) ≈ + 114.42704329506047f0 + @test sum(river.sediment_flux.boundary_conditions.erosion_land_sand) ≈ + 1289.4785484597958f0 + @test mean(river.sediment_flux.boundary_conditions.transport_capacity) ≈ + 0.4458019733090582f0 + @test mean(river.sediment_flux.variables.amount) ≈ 0.4333483865969662f0 + @test mean(river.sediment_flux.variables.erosion) ≈ 0.019077695621351014f0 + @test mean(river.sediment_flux.variables.deposition) ≈ 0.6941274181387916f0 + @test river.sediment_flux.variables.clay[5649] ≈ 2.840979764480952f-9 + + @test mean(river.concentrations.variables.suspended) ≈ 0.8260083257660087f0 end # @testset "Exchange and grid location sediment" begin diff --git a/test/sediment_config.toml b/test/sediment_config.toml index 29a5eea21..de64a3188 100644 --- a/test/sediment_config.toml +++ b/test/sediment_config.toml @@ -174,7 +174,7 @@ landtransportmethod = "yalinpart" # Overland flow transport capacity method: ["y rainerosmethod = "answers" # Rainfall erosion equation: ["answers", "eurosem"] reinit = true rivtransportmethod = "bagnold" # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] -runrivermodel = false +runrivermodel = true type = "sediment" [output] @@ -213,9 +213,12 @@ waterlevel_river = "h_riv" [output.lateral.river.sediment_flux.variables] leftover_clay = "clayload" clay = "outclay" +erosion = "erosion_riv" +deposition = "deposition_riv" [output.lateral.river.sediment_flux.boundary_conditions] erosion_land_clay = "inlandclayriv" +transport_capacity = "TCsed_riv" [csv] path = "output-moselle-sediment.csv" From e9322938824a80c72c76d69033904976fbfe1ec6 Mon Sep 17 00:00:00 2001 From: hboisgon Date: Wed, 25 Sep 2024 18:43:36 +0800 Subject: [PATCH 13/42] renaming of functions --- src/Wflow.jl | 4 +- src/erosion.jl | 41 ++-- src/erosion/overland_flow_erosion.jl | 68 ++++-- src/erosion/rainfall_erosion.jl | 123 ++++++---- src/erosion/river_erosion.jl | 45 ++-- src/erosion/soil_erosion.jl | 76 ++++--- src/forcing.jl | 4 +- src/geometry.jl | 4 +- src/sediment_flux.jl | 49 ++-- src/sediment_transport/land_to_river.jl | 85 ++++--- .../overland_flow_transport.jl | 134 ++++++----- src/sediment_transport/river_transport.jl | 210 ++++++++++-------- src/sediment_transport/transport_capacity.jl | 191 ++++++++-------- 13 files changed, 607 insertions(+), 427 deletions(-) diff --git a/src/Wflow.jl b/src/Wflow.jl index a7ee89f8a..9df1cc191 100644 --- a/src/Wflow.jl +++ b/src/Wflow.jl @@ -116,8 +116,6 @@ include("sbm.jl") #include("sediment.jl") include("reservoir_lake.jl") include("sbm_model.jl") -include("sediment_model.jl") -include("erosion.jl") include("erosion/erosion_process.jl") include("erosion/rainfall_erosion.jl") include("erosion/overland_flow_erosion.jl") @@ -129,7 +127,9 @@ include("sediment_transport/transport_capacity.jl") include("sediment_transport/overland_flow_transport.jl") include("sediment_transport/land_to_river.jl") include("sediment_transport/river_transport.jl") +include("erosion.jl") include("sediment_flux.jl") +include("sediment_model.jl") include("vertical_process.jl") include("groundwater/connectivity.jl") include("groundwater/aquifer.jl") diff --git a/src/erosion.jl b/src/erosion.jl index 696f7b79e..b91db0c83 100644 --- a/src/erosion.jl +++ b/src/erosion.jl @@ -1,21 +1,23 @@ -@get_units @with_kw struct SoilLoss{RE, OLE, SE, T} +@get_units @with_kw struct SoilLoss{RE, OFE, SE, T} hydrometeo_forcing::HydrometeoForcing | "-" geometry::LandGeometry | "-" rainfall_erosion::RE | "-" - overland_flow_erosion::OLE | "-" + overland_flow_erosion::OFE | "-" soil_erosion::SE | "-" end function initialize_soil_loss(nc, config, inds) n = length(inds) - hydrometeo_forcing = initialize_hydrometeo_forcing(n) - geometry = initialize_land_geometry(nc, config, inds) + + hydrometeo_forcing = HydrometeoForcing(n) + geometry = LandGeometry(nc, config, inds) + # Rainfall erosion rainfallerosionmodel = get(config.model, "rainfall_erosion", "answers")::String if rainfallerosionmodel == "answers" - rainfall_erosion_model = initialize_answers_rainfall_erosion_model(nc, config, inds) + rainfall_erosion_model = RainfallErosionAnswersModel(nc, config, inds) elseif rainfallerosionmodel == "eurosem" - rainfall_erosion_model = initialize_eurosem_rainfall_erosion_model(nc, config, inds) + rainfall_erosion_model = RainfallErosionEurosemModel(nc, config, inds) else error("Unknown rainfall erosion model: $rainfallerosionmodel") end @@ -23,15 +25,14 @@ function initialize_soil_loss(nc, config, inds) # Overland flow erosion overlandflowerosionmodel = get(config.model, "overland_flow_erosion", "answers")::String if overlandflowerosionmodel == "answers" - overland_flow_erosion_model = - initialize_answers_overland_flow_erosion_model(nc, config, inds) + overland_flow_erosion_model = OverlandFlowErosionAnswersModel(nc, config, inds) else error("Unknown overland flow erosion model: $overlandflowerosionmodel") # overland_flow_erosion_model = NoOverlandFlowErosionModel() end # Total soil erosion and particle differentiation - soil_erosion_model = initialize_soil_erosion_model(nc, config, inds) + soil_erosion_model = SoilErosionModel(nc, config, inds) soil_loss = SoilLoss{ typeof(rainfall_erosion_model), @@ -49,19 +50,25 @@ function initialize_soil_loss(nc, config, inds) end function update!(model::SoilLoss, dt) + (; + hydrometeo_forcing, + geometry, + rainfall_erosion, + overland_flow_erosion, + soil_erosion, + ) = model # Convert dt to integer ts = tosecond(dt) #TODO add interception/canopygapfraction calculation here for eurosem #need SBM refactor + # Rainfall erosion - update!(model.rainfall_erosion, model.hydrometeo_forcing, model.geometry, ts) + update_boundary_conditions!(rainfall_erosion, hydrometeo_forcing) + update!(rainfall_erosion, geometry, ts) # Overland flow erosion - update!(model.overland_flow_erosion, model.hydrometeo_forcing, model.geometry, ts) + update_boundary_conditions!(overland_flow_erosion, hydrometeo_forcing) + update!(overland_flow_erosion, geometry, ts) # Total soil erosion and particle differentiation - re = get_rainfall_erosion(model.rainfall_erosion) - ole = get_overland_flow_erosion(model.overland_flow_erosion) - (; rainfall_erosion, overland_flow_erosion) = model.soil_erosion.boundary_conditions - @. rainfall_erosion = re - @. overland_flow_erosion = ole - update!(model.soil_erosion) + update_boundary_conditions!(soil_erosion, rainfall_erosion, overland_flow_erosion) + update!(soil_erosion) end diff --git a/src/erosion/overland_flow_erosion.jl b/src/erosion/overland_flow_erosion.jl index 567c36757..b12805308 100644 --- a/src/erosion/overland_flow_erosion.jl +++ b/src/erosion/overland_flow_erosion.jl @@ -1,16 +1,24 @@ -abstract type AbstractOverlandFlowErosionModel end +abstract type AbstractOverlandFlowErosionModel{T} end -struct NoOverlandFlowErosionModel <: AbstractOverlandFlowErosionModel end +struct NoOverlandFlowErosionModel{T} <: AbstractOverlandFlowErosionModel{T} end ## Overland flow structs and functions -@get_units @with_kw struct OverlandFlowErosionModelVars{T} +@get_units @with_kw struct OverlandFlowErosionVariables{T} # Total soil erosion from overland flow amount::Vector{T} | "t dt-1" end -function overland_flow_erosion_model_vars(n) - vars = OverlandFlowErosionModelVars(; amount = fill(mv, n)) - return vars +function OverlandFlowErosionVariables(n; amount::Vector{T} = fill(mv, n)) where {T} + return OverlandFlowErosionVariables{T}(; amount = amount) +end + +@get_units @with_kw struct OverlandFlowErosionBC{T} + # Overland flow [m3 s-1] + q::Vector{T} +end + +function OverlandFlowErosionBC(n; q::Vector{T} = fill(mv, n)) where {T} + return OverlandFlowErosionBC{T}(; q = q) end # ANSWERS specific structs and functions for rainfall erosion @@ -23,13 +31,7 @@ end answers_k::Vector{T} | "-" end -@get_units @with_kw struct OverlandFlowErosionAnswersModel{T} <: - AbstractOverlandFlowErosionModel - parameters::OverlandFlowErosionAnswersParameters{T} | "-" - variables::OverlandFlowErosionModelVars{T} | "-" -end - -function initialize_answers_params_overland_flow(nc, config, inds) +function OverlandFlowErosionAnswersParameters(nc, config, inds) usle_k = ncread( nc, config, @@ -62,28 +64,50 @@ function initialize_answers_params_overland_flow(nc, config, inds) return answers_parameters end -function initialize_answers_overland_flow_erosion_model(nc, config, inds) +@with_kw struct OverlandFlowErosionAnswersModel{T} <: AbstractOverlandFlowErosionModel{T} + boundary_conditions::OverlandFlowErosionBC{T} + parameters::OverlandFlowErosionAnswersParameters{T} + variables::OverlandFlowErosionVariables{T} +end + +function OverlandFlowErosionAnswersModel(nc, config, inds) n = length(inds) - vars = overland_flow_erosion_model_vars(n) - params = initialize_answers_params_overland_flow(nc, config, inds) - model = OverlandFlowErosionAnswersModel(; parameters = params, variables = vars) + vars = OverlandFlowErosionVariables(n) + params = OverlandFlowErosionAnswersParameters(nc, config, inds) + bc = OverlandFlowErosionBC(n) + model = OverlandFlowErosionAnswersModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) return model end -function update!( +function update_boundary_conditions!( model::OverlandFlowErosionAnswersModel, hydrometeo_forcing::HydrometeoForcing, - geometry::LandGeometry, - ts, ) + (; q) = model.boundary_conditions (; q_land) = hydrometeo_forcing + @. q = q_land +end + +function update_boundary_conditions!( + model::NoOverlandFlowErosionModel, + hydrometeo_forcing::HydrometeoForcing, +) + return nothing +end + +function update!(model::OverlandFlowErosionAnswersModel, geometry::LandGeometry, ts) + (; q) = model.boundary_conditions (; usle_k, usle_c, answers_k) = model.parameters (; amount) = model.variables - n = length(q_land) + n = length(q) threaded_foreach(1:n; basesize = 1000) do i amount[i] = overland_flow_erosion_answers( - q_land[i], + q[i], usle_k[i], usle_c[i], answers_k[i], diff --git a/src/erosion/rainfall_erosion.jl b/src/erosion/rainfall_erosion.jl index d6e16e950..32c0de725 100644 --- a/src/erosion/rainfall_erosion.jl +++ b/src/erosion/rainfall_erosion.jl @@ -1,27 +1,38 @@ -abstract type AbstractRainfallErosionModel end +abstract type AbstractRainfallErosionModel{T} end -struct NoRainfallErosionModel <: AbstractRainfallErosionModel end +struct NoRainfallErosionModel{T} <: AbstractRainfallErosionModel{T} end ## General rainfall erosion functions and structs -@get_units @with_kw struct RainfallErosionModelVars{T} +@get_units @with_kw struct RainfallErosionModelVariables{T} # Total soil erosion from rainfall (splash) amount::Vector{T} | "t dt-1" end -function rainfall_erosion_model_vars(n) - vars = RainfallErosionModelVars(; amount = fill(mv, n)) - return vars +function RainfallErosionModelVariables(n; amount::Vector{T} = fill(mv, n)) where {T} + return RainfallErosionModelVariables{T}(; amount = amount) end ## EUROSEM specific structs and functions for rainfall erosion @get_units @with_kw struct RainfallErosionEurosemBC{T} + # precipitation + precipitation::Vector{T} | "mm dt-1" # Interception interception::Vector{T} | "mm dt-1" + # Waterlevel on land + waterlevel::Vector{T} | "m" end -function rainfall_erosion_eurosem_bc(n) - bc = RainfallErosionEurosemBC(; interception = fill(mv, n)) - return bc +function RainfallErosionEurosemBC( + n; + precipitation::Vector{T} = fill(mv, n), + interception::Vector{T} = fill(0.0, n), + waterlevel::Vector{T} = fill(mv, n), +) where {T} + return RainfallErosionEurosemBC{T}(; + precipitation = precipitation, + interception = interception, + waterlevel = waterlevel, + ) end @get_units @with_kw struct RainfallErosionEurosemParameters{T} @@ -37,13 +48,7 @@ end soilcover_fraction::Vector{T} | "-" end -@get_units @with_kw struct RainfallErosionEurosemModel{T} <: AbstractRainfallErosionModel - boundary_conditions::RainfallErosionEurosemBC{T} | "-" - parameters::RainfallErosionEurosemParameters{T} | "-" - variables::RainfallErosionModelVars{T} | "-" -end - -function initialize_eurosem_params(nc, config, inds) +function RainfallErosionEurosemParameters(nc, config, inds) soil_detachability = ncread( nc, config, @@ -94,11 +99,17 @@ function initialize_eurosem_params(nc, config, inds) return eurosem_parameters end -function initialize_eurosem_rainfall_erosion_model(nc, config, inds) +@with_kw struct RainfallErosionEurosemModel{T} <: AbstractRainfallErosionModel{T} + boundary_conditions::RainfallErosionEurosemBC{T} + parameters::RainfallErosionEurosemParameters{T} + variables::RainfallErosionModelVariables{T} +end + +function RainfallErosionEurosemModel(nc, config, inds) n = length(inds) - vars = rainfall_erosion_model_vars(n) - params = initialize_eurosem_params(nc, config, inds) - bc = rainfall_erosion_eurosem_bc(n) + vars = RainfallErosionModelVariables(n) + params = RainfallErosionEurosemParameters(nc, config, inds) + bc = RainfallErosionEurosemBC(n) model = RainfallErosionEurosemModel(; boundary_conditions = bc, parameters = params, @@ -107,14 +118,17 @@ function initialize_eurosem_rainfall_erosion_model(nc, config, inds) return model end -function update!( +function update_boundary_conditions!( model::RainfallErosionEurosemModel, hydrometeo_forcing::HydrometeoForcing, - geometry::LandGeometry, - ts, ) - (; precipitation, waterlevel_land) = hydrometeo_forcing - (; interception) = model.boundary_conditions + (; precipitation, waterlevel) = model.boundary_conditions + @. precipitation = hydrometeo_forcing.precipitation + @. waterlevel = model.boundary_conditions.waterlevel_land +end + +function update!(model::RainfallErosionEurosemModel, geometry::LandGeometry, ts) + (; precipitation, interception, waterlevel) = model.boundary_conditions (; soil_detachability, eurosem_exponent, @@ -124,12 +138,12 @@ function update!( ) = model.parameters (; amount) = model.variables - n = length(precip) + n = length(precipitation) threaded_foreach(1:n; basesize = 1000) do i amount[i] = rainfall_erosion_eurosem( precipitation[i], interception[i], - waterlevel_land[i], + waterlevel[i], soil_detachability[i], eurosem_exponent[i], canopyheight[i], @@ -141,7 +155,16 @@ function update!( end end -# ANSWERS specific structs and functions for rainfall erosion +### ANSWERS specific structs and functions for rainfall erosion +@get_units @with_kw struct RainfallErosionAnswersBC{T} + # precipitation + precipitation::Vector{T} | "mm dt-1" +end + +function RainfallErosionAnswersBC(n; precipitation::Vector{T} = fill(mv, n)) where {T} + return RainfallErosionAnswersBC{T}(; precipitation = precipitation) +end + @get_units @with_kw struct RainfallErosionAnswersParameters{T} # Soil erodibility factor usle_k::Vector{T} | "-" @@ -149,12 +172,7 @@ end usle_c::Vector{T} | "-" end -@get_units @with_kw struct RainfallErosionAnswersModel{T} <: AbstractRainfallErosionModel - parameters::RainfallErosionAnswersParameters{T} | "-" - variables::RainfallErosionModelVars{T} | "-" -end - -function initialize_answers_params_rainfall(nc, config, inds) +function RainfallErosionAnswersParameters(nc, config, inds) usle_k = ncread( nc, config, @@ -176,21 +194,35 @@ function initialize_answers_params_rainfall(nc, config, inds) return answers_parameters end -function initialize_answers_rainfall_erosion_model(nc, config, inds) +@with_kw struct RainfallErosionAnswersModel{T} <: AbstractRainfallErosionModel{T} + boundary_conditions::RainfallErosionAnswersBC{T} + parameters::RainfallErosionAnswersParameters{T} + variables::RainfallErosionModelVariables{T} +end + +function RainfallErosionAnswersModel(nc, config, inds) n = length(inds) - vars = rainfall_erosion_model_vars(n) - params = initialize_answers_params_rainfall(nc, config, inds) - model = RainfallErosionAnswersModel(; parameters = params, variables = vars) + bc = RainfallErosionAnswersBC(n) + vars = RainfallErosionModelVariables(n) + params = RainfallErosionAnswersParameters(nc, config, inds) + model = RainfallErosionAnswersModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) return model end -function update!( +function update_boundary_conditions!( model::RainfallErosionAnswersModel, hydrometeo_forcing::HydrometeoForcing, - geometry::LandGeometry, - ts, ) - (; precipitation) = hydrometeo_forcing + (; precipitation) = model.boundary_conditions + @. precipitation = hydrometeo_forcing.precipitation +end + +function update!(model::RainfallErosionAnswersModel, geometry::LandGeometry, ts) + (; precipitation) = model.boundary_conditions (; usle_k, usle_c) = model.parameters (; amount) = model.variables @@ -206,6 +238,13 @@ function update!( end end +function update_boundary_conditions!( + model::NoRainfallErosionModel, + hydrometeo_forcing::HydrometeoForcing, +) + return nothing +end + function update!(model::NoRainfallErosionModel) return nothing end diff --git a/src/erosion/river_erosion.jl b/src/erosion/river_erosion.jl index d7d0a52bc..2e2bfc586 100644 --- a/src/erosion/river_erosion.jl +++ b/src/erosion/river_erosion.jl @@ -1,16 +1,19 @@ -abstract type AbstractRiverErosionModel end +abstract type AbstractRiverErosionModel{T} end ## Potential direct river erosion structs and functions -@get_units @with_kw struct RiverErosionModelVars{T} +@get_units @with_kw struct RiverErosionModelVariables{T} # Potential river bed erosion bed::Vector{T} | "t dt-1" # Potential river bank erosion bank::Vector{T} | "t dt-1" end -function river_erosion_model_vars(n) - vars = RiverErosionModelVars(; bed = fill(mv, n), bank = fill(mv, n)) - return vars +function RiverErosionModelVariables( + n; + bed::Vector{T} = fill(mv, n), + bank::Vector{T} = fill(mv, n), +) where {T} + return RiverErosionModelVariables{T}(; bed = bed, bank = bank) end @get_units @with_kw struct RiverErosionBC{T} @@ -18,9 +21,8 @@ end waterlevel::Vector{T} | "t dt-1" end -function river_erosion_bc(n) - bc = RiverErosionBC(; waterlevel = fill(mv, n)) - return bc +function RiverErosionBC(n; waterlevel::Vector{T} = fill(mv, n)) where {T} + return RiverErosionBC{T}(; waterlevel = waterlevel) end # Parameters for the Julian Torres river erosion model @@ -29,13 +31,13 @@ end d50::Vector{T} | "mm" end -@get_units @with_kw struct RiverErosionJulianTorresModel{T} <: AbstractRiverErosionModel - boundary_conditions::RiverErosionBC{T} | "-" - parameters::RiverErosionParameters{T} | "-" - variables::RiverErosionModelVars{T} | "-" +@with_kw struct RiverErosionJulianTorresModel{T} <: AbstractRiverErosionModel{T} + boundary_conditions::RiverErosionBC{T} + parameters::RiverErosionParameters{T} + variables::RiverErosionModelVariables{T} end -function initialize_river_erosion_params(nc, config, inds) +function RiverErosionParameters(nc, config, inds) d50 = ncread( nc, config, @@ -49,11 +51,11 @@ function initialize_river_erosion_params(nc, config, inds) return river_parameters end -function initialize_river_erosion_julian_torres_model(nc, config, inds) +function RiverErosionJulianTorresModel(nc, config, inds) n = length(inds) - vars = river_erosion_model_vars(n) - params = initialize_river_erosion_params(nc, config, inds) - bc = river_erosion_bc(n) + vars = RiverErosionModelVariables(n) + params = RiverErosionParameters(nc, config, inds) + bc = RiverErosionBC(n) model = RiverErosionJulianTorresModel(; boundary_conditions = bc, parameters = params, @@ -62,6 +64,15 @@ function initialize_river_erosion_julian_torres_model(nc, config, inds) return model end +function update_boundary_conditions!( + model::RiverErosionJulianTorresModel, + hydrometeo_forcing::HydrometeoForcing, +) + (; waterlevel) = model.boundary_conditions + (; waterlevel_river) = hydrometeo_forcing + @. waterlevel = waterlevel_river +end + function update!(model::RiverErosionJulianTorresModel, geometry::RiverGeometry, ts) (; waterlevel) = model.boundary_conditions (; d50) = model.parameters diff --git a/src/erosion/soil_erosion.jl b/src/erosion/soil_erosion.jl index affd4caf0..66814d41c 100644 --- a/src/erosion/soil_erosion.jl +++ b/src/erosion/soil_erosion.jl @@ -1,7 +1,7 @@ -abstract type AbstractSoilErosionModel end +abstract type AbstractSoilErosionModel{T} end ## Total soil erosion and differentiation structs and functions -@get_units @with_kw struct SoilErosionModelVars{T} +@get_units @with_kw struct SoilErosionModelVariables{T} # Total soil erosion amount::Vector{T} | "t dt-1" # Total clay erosion @@ -16,16 +16,23 @@ abstract type AbstractSoilErosionModel end lagg::Vector{T} | "t dt-1" end -function soil_erosion_model_vars(n) - vars = SoilErosionModelVars(; - amount = fill(mv, n), - clay = fill(mv, n), - silt = fill(mv, n), - sand = fill(mv, n), - sagg = fill(mv, n), - lagg = fill(mv, n), +function SoilErosionModelVariables( + n; + amount::Vector{T} = fill(mv, n), + clay::Vector{T} = fill(mv, n), + silt::Vector{T} = fill(mv, n), + sand::Vector{T} = fill(mv, n), + sagg::Vector{T} = fill(mv, n), + lagg::Vector{T} = fill(mv, n), +) where {T} + return SoilErosionModelVariables{T}(; + amount = amount, + clay = clay, + silt = silt, + sand = sand, + sagg = sagg, + lagg = lagg, ) - return vars end @get_units @with_kw struct SoilErosionBC{T} @@ -35,10 +42,15 @@ end overland_flow_erosion::Vector{T} | "m dt-1" end -function soil_erosion_bc(n) - bc = - SoilErosionBC(; rainfall_erosion = fill(mv, n), overland_flow_erosion = fill(mv, n)) - return bc +function SoilErosionBC( + n; + rainfall_erosion::Vector{T} = fill(mv, n), + overland_flow_erosion::Vector{T} = fill(mv, n), +) where {T} + return SoilErosionBC{T}(; + rainfall_erosion = rainfall_erosion, + overland_flow_erosion = overland_flow_erosion, + ) end # Parameters for particle differentiation @@ -55,13 +67,7 @@ end lagg_fraction::Vector{T} | "-" end -@get_units @with_kw struct SoilErosionModel{T} <: AbstractSoilErosionModel - boundary_conditions::SoilErosionBC{T} | "-" - parameters::SoilErosionParameters{T} | "-" - variables::SoilErosionModelVars{T} | "-" -end - -function initialize_soil_erosion_params(nc, config, inds) +function SoilErosionParameters(nc, config, inds) clay_fraction = ncread( nc, config, @@ -119,16 +125,34 @@ function initialize_soil_erosion_params(nc, config, inds) return soil_parameters end -function initialize_soil_erosion_model(nc, config, inds) +@with_kw struct SoilErosionModel{T} <: AbstractSoilErosionModel{T} + boundary_conditions::SoilErosionBC{T} + parameters::SoilErosionParameters{T} + variables::SoilErosionModelVariables{T} +end + +function SoilErosionModel(nc, config, inds) n = length(inds) - vars = soil_erosion_model_vars(n) - params = initialize_soil_erosion_params(nc, config, inds) - bc = soil_erosion_bc(n) + vars = SoilErosionModelVariables(n) + params = SoilErosionParameters(nc, config, inds) + bc = SoilErosionBC(n) model = SoilErosionModel(; boundary_conditions = bc, parameters = params, variables = vars) return model end +function update_boundary_conditions!( + model::SoilErosionModel, + rainfall_erosion::AbstractRainfallErosionModel, + overland_flow_erosion::AbstractOverlandFlowErosionModel, +) + re = get_rainfall_erosion(rainfall_erosion) + ole = get_overland_flow_erosion(overland_flow_erosion) + (; rainfall_erosion, overland_flow_erosion) = model.boundary_conditions + @. rainfall_erosion = re + @. overland_flow_erosion = ole +end + function update!(model::SoilErosionModel) (; rainfall_erosion, overland_flow_erosion) = model.boundary_conditions (; clay_fraction, silt_fraction, sand_fraction, sagg_fraction, lagg_fraction) = diff --git a/src/forcing.jl b/src/forcing.jl index c48ea7142..53665f8d1 100644 --- a/src/forcing.jl +++ b/src/forcing.jl @@ -8,7 +8,7 @@ temperature::Vector{T} | "°C" end -function initialize_atmospheric_forcing(n) +function AtmosphericForcing(n) atmospheric_forcing = AtmosphericForcing(; precipitation = fill(mv, n), potential_evaporation = fill(mv, n), @@ -30,7 +30,7 @@ end q_river::Vector{T} | "m3 s-1" end -function initialize_hydrometeo_forcing(n) +function HydrometeoForcing(n) hydrometeo_forcing = HydrometeoForcing(; precipitation = fill(mv, n), waterlevel_land = fill(mv, n), diff --git a/src/geometry.jl b/src/geometry.jl index 4695e8d4e..32614fad2 100644 --- a/src/geometry.jl +++ b/src/geometry.jl @@ -8,7 +8,7 @@ slope::Vector{T} | "-" end -function initialize_land_geometry(nc, config, inds) +function LandGeometry(nc, config, inds) # read x, y coordinates and calculate cell length [m] y_nc = read_y_axis(nc) x_nc = read_x_axis(nc) @@ -44,7 +44,7 @@ end slope::Vector{T} | "-" end -function initialize_river_geometry(nc, config, inds) +function RiverGeometry(nc, config, inds) riverwidth = ncread( nc, config, diff --git a/src/sediment_flux.jl b/src/sediment_flux.jl index fc4c6feaa..0f4ceb647 100644 --- a/src/sediment_flux.jl +++ b/src/sediment_flux.jl @@ -11,8 +11,8 @@ end function initialize_overland_flow_sediment(nc, config, inds, waterbodies, rivers) n = length(inds) - hydrometeo_forcing = initialize_hydrometeo_forcing(n) - geometry = initialize_land_geometry(nc, config, inds) + hydrometeo_forcing = HydrometeoForcing(n) + geometry = LandGeometry(nc, config, inds) # Check what transport capacity equation will be used do_river = get(config.model, "runrivermodel", false)::Bool # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] @@ -20,23 +20,21 @@ function initialize_overland_flow_sediment(nc, config, inds, waterbodies, rivers if do_river || landtransportmethod == "yalinpart" transport_capacity_model = - initialize_transport_capacity_yalin_diff_model(nc, config, inds) + TransportCapacityYalinDifferentiationModel(nc, config, inds) elseif landtransportmethod == "govers" - transport_capacity_model = - initialize_transport_capacity_govers_model(nc, config, inds) + transport_capacity_model = TransportCapacityGoversModel(nc, config, inds) elseif landtransportmethod == "yalin" - transport_capacity_model = - initialize_transport_capacity_yalin_model(nc, config, inds) + transport_capacity_model = TransportCapacityYalinModel(nc, config, inds) else error("Unknown land transport method: $landtransportmethod") end if do_river || landtransportmethod == "yalinpart" - sediment_flux_model = initialize_sediment_land_transport_differentiation_model(inds) - to_river_model = initialize_sediment_to_river_differentiation_model(inds) + sediment_flux_model = SedimentLandTransportDifferentiationModel(inds) + to_river_model = SedimentToRiverDifferentiationModel(inds) else - sediment_flux_model = initialize_sediment_land_transport_model(inds) - to_river_model = initialize_sediment_to_river_model(inds) + sediment_flux_model = SedimentLandTransportModel(inds) + to_river_model = SedimentToRiverModel(inds) end overland_flow_sediment = OverlandFlowSediment{ @@ -92,39 +90,34 @@ end function initialize_river_flow_sediment(nc, config, inds, waterbodies) n = length(inds) - hydrometeo_forcing = initialize_hydrometeo_forcing(n) - geometry = initialize_river_geometry(nc, config, inds) + hydrometeo_forcing = HydrometeoForcing(n) + geometry = RiverGeometry(nc, config, inds) # Check what transport capacity equation will be used # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] transport_method = get(config.model, "rivtransportmethod", "bagnold")::String if transport_method == "bagnold" - transport_capacity_model = - initialize_transport_capacity_bagnold_model(nc, config, inds) + transport_capacity_model = TransportCapacityBagnoldModel(nc, config, inds) elseif transport_method == "engelund" - transport_capacity_model = - initialize_transport_capacity_engelund_model(nc, config, inds) + transport_capacity_model = TransportCapacityEngelundModel(nc, config, inds) elseif transport_method == "yang" - transport_capacity_model = - initialize_transport_capacity_yang_model(nc, config, inds) + transport_capacity_model = TransportCapacityYangModel(nc, config, inds) elseif transport_method == "kodatie" - transport_capacity_model = - initialize_transport_capacity_kodatie_model(nc, config, inds) + transport_capacity_model = TransportCapacityKodatieModel(nc, config, inds) elseif transport_method == "molinas" - transport_capacity_model = - initialize_transport_capacity_molinas_model(nc, config, inds) + transport_capacity_model = TransportCapacityMolinasModel(nc, config, inds) else error("Unknown river transport method: $transport_method") end # Potential river erosion - potential_erosion_model = initialize_river_erosion_julian_torres_model(nc, config, inds) + potential_erosion_model = RiverErosionJulianTorresModel(nc, config, inds) # Sediment flux in river / mass balance - sediment_flux_model = initialize_sediment_river_transport_model(nc, config, inds) + sediment_flux_model = SedimentRiverTransportModel(nc, config, inds) # Concentrations - concentrations_model = initialize_sediment_concentrations_river_model(nc, config, inds) + concentrations_model = SedimentConcentrationsRiverModel(nc, config, inds) river_sediment = RiverSediment{ typeof(transport_capacity_model), @@ -159,9 +152,7 @@ function update!( update!(model.transport_capacity, model.geometry, ts) # Potential maximum river erosion - (; waterlevel) = model.potential_erosion.boundary_conditions - (; waterlevel_river) = model.hydrometeo_forcing - @. waterlevel = waterlevel_river + update_boundary_conditions!(model.potential_erosion, model.hydrometeo_forcing) update!(model.potential_erosion, model.geometry, ts) # River transport diff --git a/src/sediment_transport/land_to_river.jl b/src/sediment_transport/land_to_river.jl index 5ece4f514..8f196f9d5 100644 --- a/src/sediment_transport/land_to_river.jl +++ b/src/sediment_transport/land_to_river.jl @@ -1,14 +1,13 @@ -abstract type AbstractSedimentToRiverModel end +abstract type AbstractSedimentToRiverModel{T} end ## Total sediment transport in overland flow structs and functions -@get_units @with_kw struct SedimentToRiverVars{T} +@get_units @with_kw struct SedimentToRiverVariables{T} # Total sediment reaching the river amount::Vector{T} | "t dt-1" end -function sediment_to_river_vars(n) - vars = SedimentToRiverVars(; amount = fill(mv, n)) - return vars +function SedimentToRiverVariables(n; amount::Vector{T} = fill(mv, n)) where {T} + return SedimentToRiverVariables{T}(; amount = amount) end @get_units @with_kw struct SedimentToRiverBC{T} @@ -16,20 +15,19 @@ end deposition::Vector{T} | "t dt-1" end -function sediment_to_river_bc(n) - bc = SedimentToRiverBC(; deposition = fill(mv, n)) - return bc +function SedimentToRiverBC(n; deposition::Vector{T} = fill(mv, n)) where {T} + return SedimentToRiverBC{T}(; deposition = deposition) end -@get_units @with_kw struct SedimentToRiverModel{T} <: AbstractSedimentToRiverModel - boundary_conditions::SedimentToRiverBC{T} | "-" - variables::SedimentToRiverVars{T} | "-" +@with_kw struct SedimentToRiverModel{T} <: AbstractSedimentToRiverModel{T} + boundary_conditions::SedimentToRiverBC{T} + variables::SedimentToRiverVariables{T} end -function initialize_sediment_to_river_model(inds) +function SedimentToRiverModel(inds) n = length(inds) - vars = sediment_to_river_vars(n) - bc = sediment_to_river_bc(n) + vars = SedimentToRiverVariables(n) + bc = SedimentToRiverBC(n) model = SedimentToRiverModel(; boundary_conditions = bc, variables = vars) return model end @@ -51,7 +49,7 @@ function update!(model::SedimentToRiverModel, rivers) end ## Different particles reaching the river structs and functions -@get_units @with_kw struct SedimentToRiverDifferentiationVars{T} +@get_units @with_kw struct SedimentToRiverDifferentiationVariables{T} # Total sediment flux amount::Vector{T} | "t dt-1" # Clay flux @@ -66,16 +64,23 @@ end lagg::Vector{T} | "t dt-1" end -function sediment_to_river_differentiation_vars(n) - vars = SedimentToRiverDifferentiationVars(; - amount = fill(mv, n), - clay = fill(mv, n), - silt = fill(mv, n), - sand = fill(mv, n), - sagg = fill(mv, n), - lagg = fill(mv, n), +function SedimentToRiverDifferentiationVariables( + n; + amount::Vector{T} = fill(mv, n), + clay::Vector{T} = fill(mv, n), + silt::Vector{T} = fill(mv, n), + sand::Vector{T} = fill(mv, n), + sagg::Vector{T} = fill(mv, n), + lagg::Vector{T} = fill(mv, n), +) where {T} + return SedimentToRiverDifferentiationVariables{T}(; + amount = amount, + clay = clay, + silt = silt, + sand = sand, + sagg = sagg, + lagg = lagg, ) - return vars end @get_units @with_kw struct SedimentToRiverDifferentiationBC{T} @@ -91,27 +96,33 @@ end deposition_lagg::Vector{T} | "t dt-1" end -function sediment_to_river_differentiation_bc(n) - bc = SedimentToRiverDifferentiationBC(; - deposition_clay = fill(mv, n), - deposition_silt = fill(mv, n), - deposition_sand = fill(mv, n), - deposition_sagg = fill(mv, n), - deposition_lagg = fill(mv, n), +function SedimentToRiverDifferentiationBC( + n; + deposition_clay::Vector{T} = fill(mv, n), + deposition_silt::Vector{T} = fill(mv, n), + deposition_sand::Vector{T} = fill(mv, n), + deposition_sagg::Vector{T} = fill(mv, n), + deposition_lagg::Vector{T} = fill(mv, n), +) where {T} + return SedimentToRiverDifferentiationBC{T}(; + deposition_clay = deposition_clay, + deposition_silt = deposition_silt, + deposition_sand = deposition_sand, + deposition_sagg = deposition_sagg, + deposition_lagg = deposition_lagg, ) - return bc end @get_units @with_kw struct SedimentToRiverDifferentiationModel{T} <: - AbstractSedimentToRiverModel + AbstractSedimentToRiverModel{T} boundary_conditions::SedimentToRiverDifferentiationBC{T} | "-" - variables::SedimentToRiverDifferentiationVars{T} | "-" + variables::SedimentToRiverDifferentiationVariables{T} | "-" end -function initialize_sediment_to_river_differentiation_model(inds) +function SedimentToRiverDifferentiationModel(inds) n = length(inds) - vars = sediment_to_river_differentiation_vars(n) - bc = sediment_to_river_differentiation_bc(n) + vars = SedimentToRiverDifferentiationVariables(n) + bc = SedimentToRiverDifferentiationBC(n) model = SedimentToRiverDifferentiationModel(; boundary_conditions = bc, variables = vars) return model diff --git a/src/sediment_transport/overland_flow_transport.jl b/src/sediment_transport/overland_flow_transport.jl index 66eca1bde..442e62140 100644 --- a/src/sediment_transport/overland_flow_transport.jl +++ b/src/sediment_transport/overland_flow_transport.jl @@ -1,15 +1,18 @@ -abstract type AbstractSedimentLandTransportModel end +abstract type AbstractSedimentLandTransportModel{T} end ## Total sediment transport in overland flow structs and functions -@get_units @with_kw struct SedimentLandTransportVars{T} +@get_units @with_kw struct SedimentLandTransportVariables{T} # Total sediment flux amount::Vector{T} | "t dt-1" deposition::Vector{T} | "t dt-1" end -function sediment_land_transport_vars(n) - vars = SedimentLandTransportVars(; amount = fill(mv, n), deposition = fill(mv, n)) - return vars +function SedimentLandTransportVariables( + n; + amount::Vector{T} = fill(mv, n), + deposition::Vector{T} = fill(mv, n), +) where {T} + return SedimentLandTransportVariables{T}(; amount = amount, deposition = deposition) end @get_units @with_kw struct SedimentLandTransportBC{T} @@ -19,21 +22,26 @@ end transport_capacity::Vector{T} | "t dt-1" end -function sediment_land_transport_bc(n) - bc = SedimentLandTransportBC(; erosion = fill(mv, n), transport_capacity = fill(mv, n)) - return bc +function SedimentLandTransportBC( + n; + erosion::Vector{T} = fill(mv, n), + transport_capacity::Vector{T} = fill(mv, n), +) where {T} + return SedimentLandTransportBC{T}(; + erosion = erosion, + transport_capacity = transport_capacity, + ) end -@get_units @with_kw struct SedimentLandTransportModel{T} <: - AbstractSedimentLandTransportModel - boundary_conditions::SedimentLandTransportBC{T} | "-" - variables::SedimentLandTransportVars{T} | "-" +@with_kw struct SedimentLandTransportModel{T} <: AbstractSedimentLandTransportModel{T} + boundary_conditions::SedimentLandTransportBC{T} + variables::SedimentLandTransportVariables{T} end -function initialize_sediment_land_transport_model(inds) +function SedimentLandTransportModel(inds) n = length(inds) - vars = sediment_land_transport_vars(n) - bc = sediment_land_transport_bc(n) + vars = SedimentLandTransportVariables(n) + bc = SedimentLandTransportBC(n) model = SedimentLandTransportModel(; boundary_conditions = bc, variables = vars) return model end @@ -60,7 +68,7 @@ function update!(model::SedimentLandTransportModel, network) end ## Total transport capacity with particle differentiation structs and functions -@get_units @with_kw struct SedimentLandTransportDifferentiationVars{T} +@get_units @with_kw struct SedimentLandTransportDifferentiationVariables{T} # Total sediment flux amount::Vector{T} | "t dt-1" # Deposition @@ -87,22 +95,35 @@ end deposition_lagg::Vector{T} | "t dt-1" end -function sediment_land_transport_differentiation_vars(n) - vars = SedimentLandTransportDifferentiationVars(; - amount = fill(mv, n), - deposition = fill(mv, n), - clay = fill(mv, n), - deposition_clay = fill(mv, n), - silt = fill(mv, n), - deposition_silt = fill(mv, n), - sand = fill(mv, n), - deposition_sand = fill(mv, n), - sagg = fill(mv, n), - deposition_sagg = fill(mv, n), - lagg = fill(mv, n), - deposition_lagg = fill(mv, n), +function SedimentLandTransportDifferentiationVariables( + n; + amount::Vector{T} = fill(mv, n), + deposition::Vector{T} = fill(mv, n), + clay::Vector{T} = fill(mv, n), + deposition_clay::Vector{T} = fill(mv, n), + silt::Vector{T} = fill(mv, n), + deposition_silt::Vector{T} = fill(mv, n), + sand::Vector{T} = fill(mv, n), + deposition_sand::Vector{T} = fill(mv, n), + sagg::Vector{T} = fill(mv, n), + deposition_sagg::Vector{T} = fill(mv, n), + lagg::Vector{T} = fill(mv, n), + deposition_lagg::Vector{T} = fill(mv, n), +) where {T} + return SedimentLandTransportDifferentiationVariables{T}(; + amount = amount, + deposition = deposition, + clay = clay, + deposition_clay = deposition_clay, + silt = silt, + deposition_silt = deposition_silt, + sand = sand, + deposition_sand = deposition_sand, + sagg = sagg, + deposition_sagg = deposition_sagg, + lagg = lagg, + deposition_lagg = deposition_lagg, ) - return vars end @get_units @with_kw struct SedimentLandTransportDifferentiationBC{T} @@ -128,32 +149,43 @@ end transport_capacity_lagg::Vector{T} | "t dt-1" end -function sediment_land_transport_differentiation_bc(n) - bc = SedimentLandTransportDifferentiationBC(; - erosion_clay = fill(mv, n), - erosion_silt = fill(mv, n), - erosion_sand = fill(mv, n), - erosion_sagg = fill(mv, n), - erosion_lagg = fill(mv, n), - transport_capacity_clay = fill(mv, n), - transport_capacity_silt = fill(mv, n), - transport_capacity_sand = fill(mv, n), - transport_capacity_sagg = fill(mv, n), - transport_capacity_lagg = fill(mv, n), +function SedimentLandTransportDifferentiationBC( + n; + erosion_clay::Vector{T} = fill(mv, n), + erosion_silt::Vector{T} = fill(mv, n), + erosion_sand::Vector{T} = fill(mv, n), + erosion_sagg::Vector{T} = fill(mv, n), + erosion_lagg::Vector{T} = fill(mv, n), + transport_capacity_clay::Vector{T} = fill(mv, n), + transport_capacity_silt::Vector{T} = fill(mv, n), + transport_capacity_sand::Vector{T} = fill(mv, n), + transport_capacity_sagg::Vector{T} = fill(mv, n), + transport_capacity_lagg::Vector{T} = fill(mv, n), +) where {T} + return SedimentLandTransportDifferentiationBC{T}(; + erosion_clay = erosion_clay, + erosion_silt = erosion_silt, + erosion_sand = erosion_sand, + erosion_sagg = erosion_sagg, + erosion_lagg = erosion_lagg, + transport_capacity_clay = transport_capacity_clay, + transport_capacity_silt = transport_capacity_silt, + transport_capacity_sand = transport_capacity_sand, + transport_capacity_sagg = transport_capacity_sagg, + transport_capacity_lagg = transport_capacity_lagg, ) - return bc end -@get_units @with_kw struct SedimentLandTransportDifferentiationModel{T} <: - AbstractSedimentLandTransportModel - boundary_conditions::SedimentLandTransportDifferentiationBC{T} | "-" - variables::SedimentLandTransportDifferentiationVars{T} | "-" +@with_kw struct SedimentLandTransportDifferentiationModel{T} <: + AbstractSedimentLandTransportModel{T} + boundary_conditions::SedimentLandTransportDifferentiationBC{T} + variables::SedimentLandTransportDifferentiationVariables{T} end -function initialize_sediment_land_transport_differentiation_model(inds) +function SedimentLandTransportDifferentiationModel(inds) n = length(inds) - vars = sediment_land_transport_differentiation_vars(n) - bc = sediment_land_transport_differentiation_bc(n) + vars = SedimentLandTransportDifferentiationVariables(n) + bc = SedimentLandTransportDifferentiationBC(n) model = SedimentLandTransportDifferentiationModel(; boundary_conditions = bc, variables = vars, diff --git a/src/sediment_transport/river_transport.jl b/src/sediment_transport/river_transport.jl index a658bb497..74b11bd5b 100644 --- a/src/sediment_transport/river_transport.jl +++ b/src/sediment_transport/river_transport.jl @@ -1,7 +1,7 @@ -abstract type AbstractSedimentRiverTransportModel end +abstract type AbstractSedimentRiverTransportModel{T} end ## Total sediment transport in overland flow structs and functions -@get_units @with_kw struct SedimentRiverTransportVars{T} +@get_units @with_kw struct SedimentRiverTransportVariables{T} # Sediment flux [ton] amount::Vector{T} | "t dt-1" clay::Vector{T} | "t dt-1" @@ -30,31 +30,53 @@ abstract type AbstractSedimentRiverTransportModel end store_gravel::Vector{T} | "t dt-1" end -function sediment_river_transport_vars(n) - vars = SedimentRiverTransportVars(; - amount = fill(mv, n), - clay = fill(0.0, n), - silt = fill(0.0, n), - sand = fill(0.0, n), - sagg = fill(0.0, n), - lagg = fill(0.0, n), - gravel = fill(0.0, n), - deposition = fill(mv, n), - erosion = fill(mv, n), - leftover_clay = fill(0.0, n), - leftover_silt = fill(0.0, n), - leftover_sand = fill(0.0, n), - leftover_sagg = fill(0.0, n), - leftover_lagg = fill(0.0, n), - leftover_gravel = fill(0.0, n), - store_clay = fill(0.0, n), - store_silt = fill(0.0, n), - store_sand = fill(0.0, n), - store_sagg = fill(0.0, n), - store_lagg = fill(0.0, n), - store_gravel = fill(0.0, n), +function SedimentRiverTransportVariables( + n; + amount::Vector{T} = fill(mv, n), + clay::Vector{T} = fill(0.0, n), + silt::Vector{T} = fill(0.0, n), + sand::Vector{T} = fill(0.0, n), + sagg::Vector{T} = fill(0.0, n), + lagg::Vector{T} = fill(0.0, n), + gravel::Vector{T} = fill(0.0, n), + deposition::Vector{T} = fill(mv, n), + erosion::Vector{T} = fill(mv, n), + leftover_clay::Vector{T} = fill(0.0, n), + leftover_silt::Vector{T} = fill(0.0, n), + leftover_sand::Vector{T} = fill(0.0, n), + leftover_sagg::Vector{T} = fill(0.0, n), + leftover_lagg::Vector{T} = fill(0.0, n), + leftover_gravel::Vector{T} = fill(0.0, n), + store_clay::Vector{T} = fill(0.0, n), + store_silt::Vector{T} = fill(0.0, n), + store_sand::Vector{T} = fill(0.0, n), + store_sagg::Vector{T} = fill(0.0, n), + store_lagg::Vector{T} = fill(0.0, n), + store_gravel::Vector{T} = fill(0.0, n), +) where {T} + return SedimentRiverTransportVariables{T}(; + amount = amount, + clay = clay, + silt = silt, + sand = sand, + sagg = sagg, + lagg = lagg, + gravel = gravel, + deposition = deposition, + erosion = erosion, + leftover_clay = leftover_clay, + leftover_silt = leftover_silt, + leftover_sand = leftover_sand, + leftover_sagg = leftover_sagg, + leftover_lagg = leftover_lagg, + leftover_gravel = leftover_gravel, + store_clay = store_clay, + store_silt = store_silt, + store_sand = store_sand, + store_sagg = store_sagg, + store_lagg = store_lagg, + store_gravel = store_gravel, ) - return vars end @get_units @with_kw struct SedimentRiverTransportBC{T} @@ -75,20 +97,31 @@ end potential_erosion_river_bank::Vector{T} | "t dt-1" end -function sediment_river_transport_bc(n) - bc = SedimentRiverTransportBC(; - waterlevel = fill(mv, n), - q = fill(mv, n), - transport_capacity = fill(mv, n), - erosion_land_clay = fill(mv, n), - erosion_land_silt = fill(mv, n), - erosion_land_sand = fill(mv, n), - erosion_land_sagg = fill(mv, n), - erosion_land_lagg = fill(mv, n), - potential_erosion_river_bed = fill(mv, n), - potential_erosion_river_bank = fill(mv, n), +function SedimentRiverTransportBC( + n; + waterlevel::Vector{T} = fill(mv, n), + q::Vector{T} = fill(mv, n), + transport_capacity::Vector{T} = fill(mv, n), + erosion_land_clay::Vector{T} = fill(mv, n), + erosion_land_silt::Vector{T} = fill(mv, n), + erosion_land_sand::Vector{T} = fill(mv, n), + erosion_land_sagg::Vector{T} = fill(mv, n), + erosion_land_lagg::Vector{T} = fill(mv, n), + potential_erosion_river_bed::Vector{T} = fill(mv, n), + potential_erosion_river_bank::Vector{T} = fill(mv, n), +) where {T} + return SedimentRiverTransportBC{T}(; + waterlevel = waterlevel, + q = q, + transport_capacity = transport_capacity, + erosion_land_clay = erosion_land_clay, + erosion_land_silt = erosion_land_silt, + erosion_land_sand = erosion_land_sand, + erosion_land_sagg = erosion_land_sagg, + erosion_land_lagg = erosion_land_lagg, + potential_erosion_river_bed = potential_erosion_river_bed, + potential_erosion_river_bank = potential_erosion_river_bank, ) - return bc end # Parameters for river transport @@ -121,7 +154,7 @@ end waterbodies_trapping_efficiency::Vector{T} | "-" end -function initialize_sediment_river_transport_params(nc, config, inds) +function SedimentRiverTransportParameters(nc, config, inds) n = length(inds) clay_fraction = ncread( nc, @@ -291,18 +324,17 @@ function initialize_sediment_river_transport_params(nc, config, inds) return river_parameters end -@get_units @with_kw struct SedimentRiverTransportModel{T} <: - AbstractSedimentRiverTransportModel - boundary_conditions::SedimentRiverTransportBC{T} | "-" - parameters::SedimentRiverTransportParameters{T} | "-" - variables::SedimentRiverTransportVars{T} | "-" +@with_kw struct SedimentRiverTransportModel{T} <: AbstractSedimentRiverTransportModel{T} + boundary_conditions::SedimentRiverTransportBC{T} + parameters::SedimentRiverTransportParameters{T} + variables::SedimentRiverTransportVariables{T} end -function initialize_sediment_river_transport_model(nc, config, inds) +function SedimentRiverTransportModel(nc, config, inds) n = length(inds) - vars = sediment_river_transport_vars(n) - params = initialize_sediment_river_transport_params(nc, config, inds) - bc = sediment_river_transport_bc(n) + vars = SedimentRiverTransportVariables(n) + params = SedimentRiverTransportParameters(nc, config, inds) + bc = SedimentRiverTransportBC(n) model = SedimentRiverTransportModel(; boundary_conditions = bc, parameters = params, @@ -545,15 +577,6 @@ function update!( erosion_sagg + erosion_lagg + erosion_gravel - #if erosion[v] > transport_capacity[v] - # println("Erosion exceeds transport capacity for node $v") - #end - if v == 5642 - println("Erosion at node 5642: ", erosion[v]) - println("Transport capacity at node 5642: ", transport_capacity[v]) - println("Sediment stored at node 5642: ", store_sediment) - println("Sediemnt need: ", sediment_need) - end ### Deposition / settling ### # Different deposition if waterbody outlet or river @@ -747,10 +770,10 @@ function update!( end end -abstract type AbstractSedimentConcentrationsRiverModel end +abstract type AbstractSedimentConcentrationsRiverModel{T} end ## Total sediment transport in overland flow structs and functions -@get_units @with_kw struct SedimentConcentrationsRiverVars{T} +@get_units @with_kw struct SedimentConcentrationsRiverVariables{T} # Total sediment concentration in the river total::Vector{T} | "g m-3" # suspended sediemnt concentration in the river @@ -759,13 +782,17 @@ abstract type AbstractSedimentConcentrationsRiverModel end bed::Vector{T} | "g m-3" end -function sediment_concentrations_river_vars(n) - vars = SedimentConcentrationsRiverVars(; - total = fill(mv, n), - suspended = fill(mv, n), - bed = fill(mv, n), +function SedimentConcentrationsRiverVariables( + n; + total::Vector{T} = fill(mv, n), + suspended::Vector{T} = fill(mv, n), + bed::Vector{T} = fill(mv, n), +) where {T} + return SedimentConcentrationsRiverVariables{T}(; + total = total, + suspended = suspended, + bed = bed, ) - return vars end @get_units @with_kw struct SedimentConcentrationsRiverBC{T} @@ -786,18 +813,27 @@ end gravel::Vector{T} | "g m-3" end -function sediment_concentrations_river_bc(n) - bc = SedimentConcentrationsRiverBC(; - q = fill(mv, n), - waterlevel = fill(mv, n), - clay = fill(mv, n), - silt = fill(mv, n), - sand = fill(mv, n), - sagg = fill(mv, n), - lagg = fill(mv, n), - gravel = fill(mv, n), +function SedimentConcentrationsRiverBC( + n; + q::Vector{T} = fill(mv, n), + waterlevel::Vector{T} = fill(mv, n), + clay::Vector{T} = fill(mv, n), + silt::Vector{T} = fill(mv, n), + sand::Vector{T} = fill(mv, n), + sagg::Vector{T} = fill(mv, n), + lagg::Vector{T} = fill(mv, n), + gravel::Vector{T} = fill(mv, n), +) where {T} + return SedimentConcentrationsRiverBC{T}(; + q = q, + waterlevel = waterlevel, + clay = clay, + silt = silt, + sand = sand, + sagg = sagg, + lagg = lagg, + gravel = gravel, ) - return bc end # Common parameters for transport capacity models @@ -816,7 +852,7 @@ end dm_gravel::Vector{T} | "µm" end -function initialize_sediment_concentrations_river_params(nc, config, inds) +function SedimentConcentrationsRiverParameters(nc, config, inds) dm_clay = ncread( nc, config, @@ -877,18 +913,18 @@ function initialize_sediment_concentrations_river_params(nc, config, inds) return conc_parameters end -@get_units @with_kw struct SedimentConcentrationsRiverModel{T} <: - AbstractSedimentConcentrationsRiverModel - boundary_conditions::SedimentConcentrationsRiverBC{T} | "-" - parameters::SedimentConcentrationsRiverParameters{T} | "-" - variables::SedimentConcentrationsRiverVars{T} | "-" +@with_kw struct SedimentConcentrationsRiverModel{T} <: + AbstractSedimentConcentrationsRiverModel{T} + boundary_conditions::SedimentConcentrationsRiverBC{T} + parameters::SedimentConcentrationsRiverParameters{T} + variables::SedimentConcentrationsRiverVariables{T} end -function initialize_sediment_concentrations_river_model(nc, config, inds) +function SedimentConcentrationsRiverModel(nc, config, inds) n = length(inds) - vars = sediment_concentrations_river_vars(n) - params = initialize_sediment_concentrations_river_params(nc, config, inds) - bc = sediment_concentrations_river_bc(n) + vars = SedimentConcentrationsRiverVariables(n) + params = SedimentConcentrationsRiverParameters(nc, config, inds) + bc = SedimentConcentrationsRiverBC(n) model = SedimentConcentrationsRiverModel(; boundary_conditions = bc, parameters = params, diff --git a/src/sediment_transport/transport_capacity.jl b/src/sediment_transport/transport_capacity.jl index 1931e4258..58eaf29e1 100644 --- a/src/sediment_transport/transport_capacity.jl +++ b/src/sediment_transport/transport_capacity.jl @@ -1,14 +1,13 @@ -abstract type AbstractTransportCapacityModel end +abstract type AbstractTransportCapacityModel{T} end ## Total sediment transport capacity structs and functions -@get_units @with_kw struct TransportCapacityModelVars{T} +@get_units @with_kw struct TransportCapacityModelVariables{T} # Total sediment transport capacity amount::Vector{T} | "t dt-1" end -function transport_capacity_model_vars(n) - vars = TransportCapacityModelVars(; amount = fill(mv, n)) - return vars +function TransportCapacityModelVariables(n; amount::Vector{T} = fill(mv, n)) where {T} + return TransportCapacityModelVariables{T}(; amount = amount) end @get_units @with_kw struct TransportCapacityBC{T} @@ -18,9 +17,12 @@ end waterlevel::Vector{T} | "m" end -function transport_capacity_bc(n) - bc = TransportCapacityBC(; q = fill(mv, n), waterlevel = fill(mv, n)) - return bc +function TransportCapacityBC( + n; + q::Vector{T} = fill(mv, n), + waterlevel::Vector{T} = fill(mv, n), +) where {T} + return TransportCapacityBC{T}(; q = q, waterlevel = waterlevel) end function update_boundary_conditions!( @@ -54,7 +56,7 @@ end n_govers::Vector{T} | "-" end -function initialize_transport_capacity_govers_params(nc, config, inds) +function TransportCapacityGoversParameters(nc, config, inds) slope = ncread( nc, config, @@ -97,17 +99,17 @@ function initialize_transport_capacity_govers_params(nc, config, inds) return tc_parameters end -@get_units @with_kw struct TransportCapacityGoversModel{T} <: AbstractTransportCapacityModel - boundary_conditions::TransportCapacityBC{T} | "-" - parameters::TransportCapacityGoversParameters{T} | "-" - variables::TransportCapacityModelVars{T} | "-" +@with_kw struct TransportCapacityGoversModel{T} <: AbstractTransportCapacityModel{T} + boundary_conditions::TransportCapacityBC{T} + parameters::TransportCapacityGoversParameters{T} + variables::TransportCapacityModelVariables{T} end -function initialize_transport_capacity_govers_model(nc, config, inds) +function TransportCapacityGoversModel(nc, config, inds) n = length(inds) - vars = transport_capacity_model_vars(n) - params = initialize_transport_capacity_govers_params(nc, config, inds) - bc = transport_capacity_bc(n) + vars = TransportCapacityModelVariables(n) + params = TransportCapacityGoversParameters(nc, config, inds) + bc = TransportCapacityBC(n) model = TransportCapacityGoversModel(; boundary_conditions = bc, parameters = params, @@ -148,7 +150,7 @@ end d50::Vector{T} | "mm" end -function initialize_transport_capacity_yalin_params(nc, config, inds) +function TransportCapacityYalinParameters(nc, config, inds) slope = ncread( nc, config, @@ -179,17 +181,17 @@ function initialize_transport_capacity_yalin_params(nc, config, inds) return tc_parameters end -@get_units @with_kw struct TransportCapacityYalinModel{T} <: AbstractTransportCapacityModel - boundary_conditions::TransportCapacityBC{T} | "-" - parameters::TransportCapacityYalinParameters{T} | "-" - variables::TransportCapacityModelVars{T} | "-" +@with_kw struct TransportCapacityYalinModel{T} <: AbstractTransportCapacityModel{T} + boundary_conditions::TransportCapacityBC{T} + parameters::TransportCapacityYalinParameters{T} + variables::TransportCapacityModelVariables{T} end -function initialize_transport_capacity_yalin_model(nc, config, inds) +function TransportCapacityYalinModel(nc, config, inds) n = length(inds) - vars = transport_capacity_model_vars(n) - params = initialize_transport_capacity_yalin_params(nc, config, inds) - bc = transport_capacity_bc(n) + vars = TransportCapacityModelVariables(n) + params = TransportCapacityYalinParameters(nc, config, inds) + bc = TransportCapacityBC(n) model = TransportCapacityYalinModel(; boundary_conditions = bc, parameters = params, @@ -220,7 +222,7 @@ function update!(model::TransportCapacityYalinModel, width, waterbodies, rivers, end ## Total transport capacity with particle differentiation structs and functions -@get_units @with_kw struct TransportCapacityYalinDifferentiationModelVars{T} +@get_units @with_kw struct TransportCapacityYalinDifferentiationModelVariables{T} # Total sediment transport capacity amount::Vector{T} | "t dt-1" # Transport capacity clay @@ -235,16 +237,23 @@ end lagg::Vector{T} | "t dt-1" end -function transport_capacity_yalin_differentiation_model_vars(n) - vars = TransportCapacityYalinDifferentiationModelVars(; - amount = fill(mv, n), - clay = fill(mv, n), - silt = fill(mv, n), - sand = fill(mv, n), - sagg = fill(mv, n), - lagg = fill(mv, n), +function TransportCapacityYalinDifferentiationModelVariables( + n; + amount::Vector{T} = fill(mv, n), + clay::Vector{T} = fill(mv, n), + silt::Vector{T} = fill(mv, n), + sand::Vector{T} = fill(mv, n), + sagg::Vector{T} = fill(mv, n), + lagg::Vector{T} = fill(mv, n), +) where {T} + return TransportCapacityYalinDifferentiationModelVariables{T}(; + amount = amount, + clay = clay, + silt = silt, + sand = sand, + sagg = sagg, + lagg = lagg, ) - return vars end # Common parameters for transport capacity models @@ -263,7 +272,7 @@ end dm_lagg::Vector{T} | "µm" end -function initialize_transport_capacity_yalin_diff_params(nc, config, inds) +function TransportCapacityYalinDifferentiationParameters(nc, config, inds) density = ncread( nc, config, @@ -324,18 +333,18 @@ function initialize_transport_capacity_yalin_diff_params(nc, config, inds) return tc_parameters end -@get_units @with_kw struct TransportCapacityYalinDifferentiationModel{T} <: - AbstractTransportCapacityModel - boundary_conditions::TransportCapacityBC{T} | "-" - parameters::TransportCapacityYalinDifferentiationParameters{T} | "-" - variables::TransportCapacityYalinDifferentiationModelVars{T} | "-" +@with_kw struct TransportCapacityYalinDifferentiationModel{T} <: + AbstractTransportCapacityModel{T} + boundary_conditions::TransportCapacityBC{T} + parameters::TransportCapacityYalinDifferentiationParameters{T} + variables::TransportCapacityYalinDifferentiationModelVariables{T} end -function initialize_transport_capacity_yalin_diff_model(nc, config, inds) +function TransportCapacityYalinDifferentiationModel(nc, config, inds) n = length(inds) - vars = transport_capacity_yalin_differentiation_model_vars(n) - params = initialize_transport_capacity_yalin_diff_params(nc, config, inds) - bc = transport_capacity_bc(n) + vars = TransportCapacityYalinDifferentiationModelVariables(n) + params = TransportCapacityYalinDifferentiationParameters(nc, config, inds) + bc = TransportCapacityBC(n) model = TransportCapacityYalinDifferentiationModel(; boundary_conditions = bc, parameters = params, @@ -439,7 +448,7 @@ end d50::Vector{T} | "mm" end -function initialize_transport_capacity_river_params(nc, config, inds) +function TransportCapacityRiverParameters(nc, config, inds) density = ncread( nc, config, @@ -469,7 +478,7 @@ end e_bagnold::Vector{T} | "-" end -function initialize_transport_capacity_bagnold_params(nc, config, inds) +function TransportCapacityBagnoldParameters(nc, config, inds) c_bagnold = ncread( nc, config, @@ -492,18 +501,17 @@ function initialize_transport_capacity_bagnold_params(nc, config, inds) return tc_parameters end -@get_units @with_kw struct TransportCapacityBagnoldModel{T} <: - AbstractTransportCapacityModel - boundary_conditions::TransportCapacityBC{T} | "-" - parameters::TransportCapacityBagnoldParameters{T} | "-" - variables::TransportCapacityModelVars{T} | "-" +@with_kw struct TransportCapacityBagnoldModel{T} <: AbstractTransportCapacityModel{T} + boundary_conditions::TransportCapacityBC{T} + parameters::TransportCapacityBagnoldParameters{T} + variables::TransportCapacityModelVariables{T} end -function initialize_transport_capacity_bagnold_model(nc, config, inds) +function TransportCapacityBagnoldModel(nc, config, inds) n = length(inds) - vars = transport_capacity_model_vars(n) - params = initialize_transport_capacity_bagnold_params(nc, config, inds) - bc = transport_capacity_bc(n) + vars = TransportCapacityModelVariables(n) + params = TransportCapacityBagnoldParameters(nc, config, inds) + bc = TransportCapacityBC(n) model = TransportCapacityBagnoldModel(; boundary_conditions = bc, parameters = params, @@ -534,18 +542,17 @@ function update!(model::TransportCapacityBagnoldModel, geometry::RiverGeometry, end # Engelund and Hansen parameters for transport capacity models -@get_units @with_kw struct TransportCapacityEngelundModel{T} <: - AbstractTransportCapacityModel - boundary_conditions::TransportCapacityBC{T} | "-" - parameters::TransportCapacityRiverParameters{T} | "-" - variables::TransportCapacityModelVars{T} | "-" +@with_kw struct TransportCapacityEngelundModel{T} <: AbstractTransportCapacityModel{T} + boundary_conditions::TransportCapacityBC{T} + parameters::TransportCapacityRiverParameters{T} + variables::TransportCapacityModelVariables{T} end -function initialize_transport_capacity_engelund_model(nc, config, inds) +function TransportCapacityEngelundModel(nc, config, inds) n = length(inds) - vars = transport_capacity_model_vars(n) - params = initialize_transport_capacity_river_params(nc, config, inds) - bc = transport_capacity_bc(n) + vars = TransportCapacityModelVariables(n) + params = TransportCapacityRiverParameters(nc, config, inds) + bc = TransportCapacityBC(n) model = TransportCapacityEngelundModel(; boundary_conditions = bc, parameters = params, @@ -586,7 +593,7 @@ end d_kodatie::Vector{T} | "-" end -function initialize_transport_capacity_kodatie_params(nc, config, inds) +function TransportCapacityKodatieParameters(nc, config, inds) a_kodatie = ncread( nc, config, @@ -629,18 +636,17 @@ function initialize_transport_capacity_kodatie_params(nc, config, inds) return tc_parameters end -@get_units @with_kw struct TransportCapacityKodatieModel{T} <: - AbstractTransportCapacityModel - boundary_conditions::TransportCapacityBC{T} | "-" - parameters::TransportCapacityKodatieParameters{T} | "-" - variables::TransportCapacityModelVars{T} | "-" +@with_kw struct TransportCapacityKodatieModel{T} <: AbstractTransportCapacityModel{T} + boundary_conditions::TransportCapacityBC{T} + parameters::TransportCapacityKodatieParameters{T} + variables::TransportCapacityModelVariables{T} end -function initialize_transport_capacity_kodatie_model(nc, config, inds) +function TransportCapacityKodatieModel(nc, config, inds) n = length(inds) - vars = transport_capacity_model_vars(n) - params = initialize_transport_capacity_kodatie_params(nc, config, inds) - bc = transport_capacity_bc(n) + vars = TransportCapacityModelVariables(n) + params = TransportCapacityKodatieParameters(nc, config, inds) + bc = TransportCapacityBC(n) model = TransportCapacityKodatieModel(; boundary_conditions = bc, parameters = params, @@ -672,17 +678,17 @@ function update!(model::TransportCapacityKodatieModel, geometry::RiverGeometry, end # Yang parameters for transport capacity models -@get_units @with_kw struct TransportCapacityYangModel{T} <: AbstractTransportCapacityModel - boundary_conditions::TransportCapacityBC{T} | "-" - parameters::TransportCapacityRiverParameters{T} | "-" - variables::TransportCapacityModelVars{T} | "-" +@with_kw struct TransportCapacityYangModel{T} <: AbstractTransportCapacityModel{T} + boundary_conditions::TransportCapacityBC{T} + parameters::TransportCapacityRiverParameters{T} + variables::TransportCapacityModelVariables{T} end -function initialize_transport_capacity_yang_model(nc, config, inds) +function TransportCapacityYangModel(nc, config, inds) n = length(inds) - vars = transport_capacity_model_vars(n) - params = initialize_transport_capacity_river_params(nc, config, inds) - bc = transport_capacity_bc(n) + vars = TransportCapacityModelVariables(n) + params = TransportCapacityRiverParameters(nc, config, inds) + bc = TransportCapacityBC(n) model = TransportCapacityYangModel(; boundary_conditions = bc, parameters = params, @@ -712,18 +718,17 @@ function update!(model::TransportCapacityYangModel, geometry::RiverGeometry, ts) end # Molinas and Wu parameters for transport capacity models -@get_units @with_kw struct TransportCapacityMolinasModel{T} <: - AbstractTransportCapacityModel - boundary_conditions::TransportCapacityBC{T} | "-" - parameters::TransportCapacityRiverParameters{T} | "-" - variables::TransportCapacityModelVars{T} | "-" +@with_kw struct TransportCapacityMolinasModel{T} <: AbstractTransportCapacityModel{T} + boundary_conditions::TransportCapacityBC{T} + parameters::TransportCapacityRiverParameters{T} + variables::TransportCapacityModelVariables{T} end -function initialize_transport_capacity_molinas_model(nc, config, inds) +function TransportCapacityMolinasModel(nc, config, inds) n = length(inds) - vars = transport_capacity_model_vars(n) - params = initialize_transport_capacity_river_params(nc, config, inds) - bc = transport_capacity_bc(n) + vars = TransportCapacityModelVariables(n) + params = TransportCapacityRiverParameters(nc, config, inds) + bc = TransportCapacityBC(n) model = TransportCapacityMolinasModel(; boundary_conditions = bc, parameters = params, From 55b48e957e419552ecaed37bb96081e2aecfa891 Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Mon, 1 Jul 2024 10:29:36 +0200 Subject: [PATCH 14/42] 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> --- docs/changelog.qmd | 11 +++++++- docs/developments/julia_structs.qmd | 6 ++--- docs/user_guide/toml_file.qmd | 42 +++++++++++++---------------- src/flow.jl | 37 +++++++++---------------- src/states.jl | 23 +++++++++++++++- src/utils.jl | 25 +++++------------ test/runtests.jl | 2 -- 7 files changed, 73 insertions(+), 73 deletions(-) diff --git a/docs/changelog.qmd b/docs/changelog.qmd index c5ad6760f..ac32d9866 100644 --- a/docs/changelog.qmd +++ b/docs/changelog.qmd @@ -7,7 +7,16 @@ 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 +## v1.0.0 + +### Fixed + +### Changed +- Removed vertical concepts `HBV` and `FLEXTopo`. + +### Added + +## [unreleased] ### Fixed - Initialization of `LateralSSF` variables `ssf` and `ssfmax` with vertical hydraulic diff --git a/docs/developments/julia_structs.qmd b/docs/developments/julia_structs.qmd index c4827cffb..dba35eec2 100644 --- a/docs/developments/julia_structs.qmd +++ b/docs/developments/julia_structs.qmd @@ -21,9 +21,9 @@ 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 vertical concept. +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 vertical concept. Below an example how lateral concepts are mapped for the SBM model through a `NamedTuple`: diff --git a/docs/user_guide/toml_file.qmd b/docs/user_guide/toml_file.qmd index d67f46b7a..e57bc80aa 100644 --- a/docs/user_guide/toml_file.qmd +++ b/docs/user_guide/toml_file.qmd @@ -234,25 +234,23 @@ h = "h_land" ``` ### Scalar data -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](#output-csv-section) 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](#output-csv-section), 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](./fews.qmd). +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). If multiple layers 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. ```toml [netcdf] @@ -301,10 +299,8 @@ extract data for certain locations (e.g. `gauges`) or areas (e.g. `subcatchment` 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. +internal layer index (see also example below) should be specified. If multiple layers 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. diff --git a/src/flow.jl b/src/flow.jl index dab39db52..0ac70441d 100644 --- a/src/flow.jl +++ b/src/flow.jl @@ -1161,11 +1161,9 @@ end function stable_timestep(sw::ShallowWaterLand{T})::T where {T} dt_min = 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 + @fastmath @inbounds dt = + sw.rivercells[i] == 0 ? + sw.alpha * min(sw.xl[i], sw.yl[i]) / sqrt(sw.g * sw.h[i]) : T(Inf) dt_min = min(dt, dt_min) end dt_min = isinf(dt_min) ? T(10.0) : dt_min @@ -1614,14 +1612,13 @@ function initialize_floodplain_1d( end """ - set_river_inwater!(model::Model, ssf_toriver) + set_river_inwater(model, ssf_toriver) -Set `inwater` of the lateral river component for a `Model`. `ssf_toriver` is the subsurface +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, ssf_toriver) - (; lateral, vertical, network, config) = model - (; net_runoff_river) = vertical.runoff.variables +function set_river_inwater(model, ssf_toriver) + @unpack lateral, vertical, network = model inds = network.index_river do_water_demand = haskey(config.model, "water_demand") if do_water_demand @@ -1648,7 +1645,7 @@ function set_river_inwater!(model::Model, ssf_toriver) 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. """ @@ -1681,21 +1678,13 @@ end Set `inwater` of the lateral land component for the `SbmModel` type. """ -function set_land_inwater!( +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 = - (net_runoff + vertical.allocation.variables.nonirri_returnflow) * - network.land.area * - 0.001 / lateral.land.dt - else - @. lateral.land.inwater = (net_runoff * network.land.area * 0.001) / lateral.land.dt - end - return nothing + @unpack lateral, vertical, network = model + lateral.land.inwater .= + (vertical.net_runoff .* network.land.xl .* network.land.yl .* 0.001) ./ + lateral.land.dt end # Computation of inflow from the lateral components `land` and `subsurface` to water bodies diff --git a/src/states.jl b/src/states.jl index 4cd991654..0987d0984 100644 --- a/src/states.jl +++ b/src/states.jl @@ -6,7 +6,28 @@ required states (internal names as symbols). """ function get_snow_states(model_type::AbstractString) if model_type == "sbm" || model_type == "sbm_gwf" - states = (:snow_storage, :snow_water) + if snow && glacier + vertical_states = ( + :satwaterdepth, + :snow, + :tsoil, + :ustorelayerdepth, + :snowwater, + :canopystorage, + :glacierstore, + ) + elseif snow + vertical_states = ( + :satwaterdepth, + :snow, + :tsoil, + :ustorelayerdepth, + :snowwater, + :canopystorage, + ) + else + vertical_states = (:satwaterdepth, :ustorelayerdepth, :canopystorage) + end elseif model_type == "sediment" states = () else diff --git a/src/utils.jl b/src/utils.jl index 40c514ec1..80ddfbb85 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -863,25 +863,12 @@ function initialize_lateralssf!(model::LateralSSF, kh_profile::KhExponentialCons 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] +"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] kh_max = 0.0 for j in 1:nlayers[i] kh_max += kv_profile.kv[i][j] * act_thickl[i][j] diff --git a/test/runtests.jl b/test/runtests.jl index 6981af08d..0f45dde45 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -83,8 +83,6 @@ with_logger(NullLogger()) do include("vertical_process.jl") include("reservoir_lake.jl") include("run_sbm.jl") - include("run_sbm_piave.jl") - include("run_sbm_gwf_piave.jl") include("run_sbm_gwf.jl") include("run.jl") include("groundwater.jl") From e1adf7761052881356e1b581956bb0ed248f8c7b Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Tue, 2 Jul 2024 10:03:37 +0200 Subject: [PATCH 15/42] 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 --- docs/changelog.qmd | 3 +++ test/reservoir_lake.jl | 9 +++++++++ test/run_sbm.jl | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/docs/changelog.qmd b/docs/changelog.qmd index ac32d9866..e561a8271 100644 --- a/docs/changelog.qmd +++ b/docs/changelog.qmd @@ -13,6 +13,9 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Changed - Removed vertical concepts `HBV` and `FLEXTopo`. +- Removed metadata macros `exchange`, `grid_type` and `grid_location`. The macro `grid_type` + is not required because this functionality is already part of `BMI`. The macros `exchange` + and `grid_location` are replaced by functions resulting in cleaner code. ### Added diff --git a/test/reservoir_lake.jl b/test/reservoir_lake.jl index 261021927..5e61f99d8 100644 --- a/test/reservoir_lake.jl +++ b/test/reservoir_lake.jl @@ -62,6 +62,15 @@ lake = Wflow.Lake{Float64}(; @test lake.actevap[1] ≈ 3.2 end +@testset "Exchange and grid location lake" begin + @test Wflow.exchange(lake, :dt) == 0 + @test Wflow.exchange(lake, :storage) == 1 + @test Wflow.exchange(lake, :outflow) == 1 + @test Wflow.grid_location(lake, :dt) == "none" + @test Wflow.grid_location(lake, :storage) == "node" + @test Wflow.grid_location(lake, :outflow) == "node" +end + datadir = joinpath(@__DIR__, "data") sh = [ Wflow.read_sh_csv(joinpath(datadir, "input", "lake_sh_1.csv")), diff --git a/test/run_sbm.jl b/test/run_sbm.jl index 2d03bed96..654a2aa9f 100644 --- a/test/run_sbm.jl +++ b/test/run_sbm.jl @@ -139,6 +139,42 @@ end @test res.evaporation[1] ≈ 0.5400000810623169f0 end +@testset "Exchange and grid location SBM" begin + sbm = model.vertical + @test Wflow.exchange(sbm, :n) == 0 + @test Wflow.exchange(sbm, :rootingdepth) == 1 + @test Wflow.grid_location(sbm, :dt) == "none" + @test Wflow.grid_location(sbm, :rootingdepth) == "node" + @test Wflow.grid_location(sbm, :runoff) == "node" +end + +@testset "Exchange and grid location subsurface flow" begin + ssf = model.lateral.subsurface + @test Wflow.exchange(ssf, :dt) == 0 + @test Wflow.exchange(ssf, :ssf) == 1 + @test Wflow.exchange(ssf, :slope) == 1 + @test Wflow.grid_location(ssf, :dt) == "none" + @test Wflow.grid_location(ssf, :ssf) == "node" + @test Wflow.grid_location(ssf, :slope) == "node" +end + +@testset "Exchange and grid location kinematic wave" begin + land = model.lateral.land + @test Wflow.exchange(land, :dt) == 0 + @test Wflow.exchange(land, :q_av) == 1 + @test Wflow.exchange(land, :inwater) == 1 + @test Wflow.grid_location(land, :dt) == "none" + @test Wflow.grid_location(land, :q_av) == "node" + @test Wflow.grid_location(land, :inwater) == "node" + river = model.lateral.river + @test Wflow.exchange(river, :dt) == 0 + @test Wflow.exchange(river, :q_av) == 1 + @test Wflow.exchange(river, :inwater) == 1 + @test Wflow.grid_location(river, :dt) == "none" + @test Wflow.grid_location(river, :q_av) == "node" + @test Wflow.grid_location(river, :inwater) == "node" +end + # set these variables for comparison in "changed dynamic parameters" precip = copy(model.vertical.atmospheric_forcing.precipitation) evap = copy(model.vertical.atmospheric_forcing.potential_evaporation) From 4181a6de476e1ec5efdb0d6d7a74b1e727d871ab Mon Sep 17 00:00:00 2001 From: Carlos Fernando Baptista Date: Mon, 15 Jul 2024 12:56:19 +0200 Subject: [PATCH 16/42] 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 --- src/bmi.jl | 2 +- src/flow.jl | 2 +- src/sediment.jl | 50 +++---------------------------------------------- test/run_sbm.jl | 1 + 4 files changed, 6 insertions(+), 49 deletions(-) diff --git a/src/bmi.jl b/src/bmi.jl index 4063baae7..42f833428 100644 --- a/src/bmi.jl +++ b/src/bmi.jl @@ -420,4 +420,4 @@ function get_start_unix_time(model::Model) end exchange(t::Vector) = true -exchange(t) = false \ No newline at end of file +exchange(t) = false diff --git a/src/flow.jl b/src/flow.jl index 0ac70441d..067a78ba2 100644 --- a/src/flow.jl +++ b/src/flow.jl @@ -1682,7 +1682,7 @@ function set_land_inwater( model::Model{N, L, V, R, W, T}, ) where {N, L, V, R, W, T <: SbmModel} @unpack lateral, vertical, network = model - lateral.land.inwater .= + return lateral.land.inwater .= (vertical.net_runoff .* network.land.xl .* network.land.yl .* 0.001) ./ lateral.land.dt end diff --git a/src/sediment.jl b/src/sediment.jl index ec5c8f8d1..3bdee4b87 100644 --- a/src/sediment.jl +++ b/src/sediment.jl @@ -99,53 +99,6 @@ 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 n = length(inds) @@ -1616,5 +1569,8 @@ function update!(rs::RiverSediment, network, config) rs.SSconc[v] = SS * toconc rs.Bedconc[v] = Bed * toconc end +<<<<<<< HEAD return nothing +======= +>>>>>>> d21c93089 (Refactor/style guide (#437)) end diff --git a/test/run_sbm.jl b/test/run_sbm.jl index 654a2aa9f..c00583d8b 100644 --- a/test/run_sbm.jl +++ b/test/run_sbm.jl @@ -112,6 +112,7 @@ end @test ssf[network.land.order[1]] ≈ 718.2802566393531f0 @test ssf[network.land.order[end - 100]] ≈ 2337.771227118579f0 @test ssf[network.land.order[end]] ≈ 288.19428729403984f0 + @test sum(ssf) ≈ 6.370399148012509f7 end @testset "overland flow" begin From 992671a402f2da983ecd639407e8eab20b769fab Mon Sep 17 00:00:00 2001 From: hboisgon Date: Mon, 9 Sep 2024 17:45:53 +0800 Subject: [PATCH 17/42] first commit refactor soil erosion --- src/erosion.jl | 56 +++++++ src/erosion/erosion_process.jl | 204 +++++++++++++++++++++++++ src/erosion/overland_flow_erosion.jl | 118 +++++++++++++++ src/erosion/rainfall_erosion.jl | 218 +++++++++++++++++++++++++++ src/erosion/soil_erosion.jl | 156 +++++++++++++++++++ test/sediment_config_refactor.toml | 213 ++++++++++++++++++++++++++ 6 files changed, 965 insertions(+) create mode 100644 src/erosion.jl create mode 100644 src/erosion/erosion_process.jl create mode 100644 src/erosion/overland_flow_erosion.jl create mode 100644 src/erosion/rainfall_erosion.jl create mode 100644 src/erosion/soil_erosion.jl create mode 100644 test/sediment_config_refactor.toml diff --git a/src/erosion.jl b/src/erosion.jl new file mode 100644 index 000000000..a1dd759e2 --- /dev/null +++ b/src/erosion.jl @@ -0,0 +1,56 @@ +@get_units @with_kw struct SoilErosion{RE, OLE, TE} + rainfall_erosion::RE | "-" + overland_flow_erosion::OLE | "-" + total_erosion::TE | "-" +end + +function initialize_soil_erosion(nc, config, inds) + + # Rainfall erosion + rainfallerosionmodel = get(config.model, "rainfall_erosion", "answers")::String + if rainfallerosionmodel == "answers" + rainfall_erosion_model = initialize_rainfall_erosion_answers(nc, config, inds) + elseif rainfallerosionmodel == "eurosem" + rainfall_erosion_model = initialize_eurosem_rainfall_erosion_model(nc, config, inds) + else + error("Unknown rainfall erosion model: $rainfallerosionmodel") + end + + # Overland flow erosion + overlandflowerosionmodel = get(config.model, "overland_flow_erosion", "answers")::String + if overlandflowerosionmodel == "answers" + overland_flow_erosion_model = + initialize_answers_overland_flow_erosion_model(nc, config, inds) + else + error("Unknown overland flow erosion model: $overlandflowerosionmodel") + # overland_flow_erosion_model = NoOverlandFlowErosionModel() + end + + # Total soil erosion and particle differentiation + total_erosion_model = initialize_soil_erosion_model(nc, config, inds) + + soil_erosion = SoilErosion{ + typeof(rainfall_erosion_model), + typeof(overland_flow_erosion_model), + typeof(total_erosion_model), + }(; + rainfall_erosion = rainfall_erosion_model, + overland_flow_erosion = overland_flow_erosion_model, + total_erosion = total_erosion_model, + ) + return soil_erosion +end + +function update!(model::SoilErosion, area, dt) + #TODO add interception/canopygapfraction calculation here for eurosem + #need SBM refactor + # Rainfall erosion + update!(model.rainfall_erosion, area, dt) + # Overland flow erosion + update!(model.overland_flow_erosion, area, dt) + # Total soil erosion and particle differentiation + (; rainfall_erosion, overland_flow_erosion) = model.total_erosion.boundary_conditions + @. rainfall_erosion = get_rainfall_erosion(model.rainfall_erosion) + @. overland_flow_erosion = get_overland_flow_erosion(model.overland_flow_erosion) + update!(model.total_erosion) +end diff --git a/src/erosion/erosion_process.jl b/src/erosion/erosion_process.jl new file mode 100644 index 000000000..c55bde0e8 --- /dev/null +++ b/src/erosion/erosion_process.jl @@ -0,0 +1,204 @@ +""" + rainfall_erosion_eurosem( + precip, + interception, + waterlevel, + soil_detachability, + eurosem_exponent, + canopyheight, + canopygapfraction, + soilcover_fraction, + area, + dt, + ) + +Rainfall erosion model based on EUROSEM. + +# Arguments +- `precip` (precipitation [mm Δt⁻¹]) +- `interception` (interception [mm Δt⁻¹]) +- `waterlevel` (water level [m]) +- `soil_detachability` (soil detachability [-]) +- `eurosem_exponent` (EUROSEM exponent [-]) +- `canopyheight` (canopy height [m]) +- `canopygapfraction` (canopy gap fraction [-]) +- `soilcover_fraction` (soil cover fraction [-]) +- `area` (area [m2]) +- `dt` (timestep [seconds]) + +# Output +- `rainfall_erosion` (soil loss [t Δt⁻¹]) +""" +function rainfall_erosion_eurosem( + precip, + interception, + waterlevel, + soil_detachability, + eurosem_exponent, + canopyheight, + canopygapfraction, + soilcover_fraction, + area, + dt, +) + # calculate rainfall intensity [mm/h] + rintnsty = precip / (dt / 3600) + # Kinetic energy of direct throughfall [J/m2/mm] + # kedir = max(11.87 + 8.73 * log10(max(0.0001, rintnsty)),0.0) #basis used in USLE + kedir = max(8.95 + 8.44 * log10(max(0.0001, rintnsty)), 0.0) #variant used in most distributed mdoels + # Kinetic energy of leaf drainage [J/m2/mm] + pheff = 0.5 * canopyheight + keleaf = max((15.8 * pheff^0.5) - 5.87, 0.0) + + #Depths of rainfall (total, leaf drianage, direct) [mm] + rdtot = precip + rdleaf = rdtot * 0.1 * canopygapfraction #stemflow + rddir = max(rdtot - rdleaf - interception, 0.0) #throughfall + + #Total kinetic energy by rainfall [J/m2] + ketot = (rddir * kedir + rdleaf * keleaf) * 0.001 + # Rainfall / splash erosion [g/m2] + sedspl = soil_detachability * ketot * exp(-eurosem_exponent * waterlevel) + sedspl = sedspl * area * 1e-6 # ton/cell + + # Remove the impervious area + sedspl = sedspl * (1.0 - soilcover_fraction) + return rainfall_erosion +end + +""" + rainfall_erosion_answers( + precip, + usle_k, + usle_c, + area, + dt, + ) + +Rainfall erosion model based on ANSWERS. + +# Arguments +- `precip` (precipitation [mm Δt⁻¹]) +- `usle_k` (USLE soil erodibility [t ha-1 mm-1]) +- `usle_c` (USLE cover and management factor [-]) +- `soilcover_fraction` (soil cover fraction [-]) +- `area` (area [m2]) +- `dt` (timestep [seconds]) + +# Output +- `rainfall_erosion` (soil loss [t Δt⁻¹]) +""" +function rainfall_erosion_answers(precip, usle_k, usle_c, area, dt) + # calculate rainfall intensity [mm/min] + rintnsty = precip / (ts / 60) + # splash erosion [kg/min] + sedspl = 0.108 * usle_c * usle_k * area * rintnsty^2 + # [ton/timestep] + sedspl = sedspl * (dt / 60) * 1e-3 + return rainfall_erosion +end + +""" + overland_flow_erosion_answers( + overland_flow, + waterlevel, + usle_k, + usle_c, + answers_k, + slope, + soilcover_fraction, + area, + dt, + ) + +Overland flow erosion model based on ANSWERS. + +# Arguments +- `overland_flow` (overland flow [m3 s-1]) +- `waterlevel` (water level [m]) +- `usle_k` (USLE soil erodibility [t ha-1 mm-1]) +- `usle_c` (USLE cover and management factor [-]) +- `answers_k` (ANSWERS overland flow factor [-]) +- `slope` (slope [-]) +- `area` (area [m2]) +- `dt` (timestep [seconds]) + +# Output +- `overland_flow_erosion` (soil loss [t Δt⁻¹]) +""" +function overland_flow_erosion_answers( + overland_flow, + usle_k, + usle_c, + answers_k, + slope, + area, + dt, +) + # Overland flow rate [m2/min] + qr_land = overland_flow * 60 / ((area) / 2) + # Sine of the slope + sinslope = sin(atan(slope)) + + # Overland flow erosion [kg/min] + # For a wide range of slope, it is better to use the sine of slope rather than tangeant + erosion = answers_k * usle_c * usle_k * area * sinslope * qr_land + # [ton/timestep] + erosion = erosion * (dt / 60) * 1e-3 + return erosion +end + +""" + total_soil_erosion( + rainfall_erosion, + overland_flow_erosion, + clay_fraction, + silt_fraction, + sand_fraction, + sagg_fraction, + lagg_fraction, + ) + +Calculate total soil erosion and particle differentiation. + +# Arguments +- `rainfall_erosion` (soil loss from rainfall erosion [t Δt⁻¹]) +- `overland_flow_erosion` (soil loss from overland flow erosion [t Δt⁻¹]) +- `clay_fraction` (clay fraction [-]) +- `silt_fraction` (silt fraction [-]) +- `sand_fraction` (sand fraction [-]) +- `sagg_fraction` (small aggregates fraction [-]) +- `lagg_fraction` (large aggregates fraction [-]) + +# Output +- `soil_erosion` (total soil loss [t Δt⁻¹]) +- `clay_erosion` (clay loss [t Δt⁻¹]) +- `silt_erosion` (silt loss [t Δt⁻¹]) +- `sand_erosion` (sand loss [t Δt⁻¹]) +- `sagg_erosion` (small aggregates loss [t Δt⁻¹]) +- `lagg_erosion` (large aggregates loss [t Δt⁻¹]) +""" +function total_soil_erosion( + rainfall_erosion, + overland_flow_erosion, + clay_fraction, + silt_fraction, + sand_fraction, + sagg_fraction, + lagg_fraction, +) + # Total soil erosion + soil_erosion = rainfall_erosion + overland_flow_erosion + # Particle differentiation + clay_erosion = soil_erosion * clay_fraction + silt_erosion = soil_erosion * silt_fraction + sand_erosion = soil_erosion * sand_fraction + sagg_erosion = soil_erosion * sagg_fraction + lagg_erosion = soil_erosion * lagg_fraction + return soil_erosion, + clay_erosion, + silt_erosion, + sand_erosion, + sagg_erosion, + lagg_erosion +end \ No newline at end of file diff --git a/src/erosion/overland_flow_erosion.jl b/src/erosion/overland_flow_erosion.jl new file mode 100644 index 000000000..237da6474 --- /dev/null +++ b/src/erosion/overland_flow_erosion.jl @@ -0,0 +1,118 @@ +abstract type AbstractOverlandFlowErosionModel end + +struct NoOverlandFlowErosionModel <: AbstractOverlandFlowErosionModel end + +## Overland flow structs and functions +@get_units @with_kw struct OverlandFlowErosionModelVars{T} + # Total soil erosion from overland flow + overland_flow_erosion::Vector{T} | "t dt-1" +end + +function overland_flow_erosion_model_vars(n) + vars = OverlandFlowErosionModelVars(; overland_flow_erosion = fill(mv, n)) + return vars +end + +@get_units @with_kw struct OverlandFlowErosionBC{T} + # Overland flow + overland_flow::Vector{T} | "m dt-1" +end + +function overland_flow_erosion_bc(n) + bc = OverlandFlowErosionBC(; overland_flow = fill(mv, n)) + return bc +end + +# ANSWERS specific structs and functions for rainfall erosion +@get_units @with_kw struct OverlandFlowErosionAnswersParameters{T} + # Soil erodibility factor + usle_k::Vector{T} | "-" + # Crop management factor + usle_c::Vector{T} | "-" + # Answers overland flow factor + answers_k::Vector{T} | "-" + # slope + slope::Vector{T} | "-" +end + +@get_units @with_kw struct OverlandFlowErosionAnswersModel{T} <: + AbstractOverlandFlowErosionModel + boundary_conditions::OverlandFlowErosionBC{T} | "-" + parameters::OverlandFlowErosionAnswersParameters{T} | "-" + variables::OverlandFlowErosionModelVars{T} | "-" +end + +function initialize_answers_params_overland_flow(nc, config, inds) + usle_k = ncread( + nc, + config, + "vertical.soil_erosion.parameters.usle_k"; + sel = inds, + defaults = 0.1, + type = Float, + ) + usle_c = ncread( + nc, + config, + "vertical.soil_erosion.parameters.usle_c"; + sel = inds, + defaults = 0.01, + type = Float, + ) + answers_k = ncread( + nc, + config, + "vertical.soil_erosion.parameters.answers_k"; + sel = inds, + defaults = 0.9, + type = Float, + ) + slope = ncread(nc, config, "vertical.slope"; sel = inds, defaults = 0.01, type = Float) + answers_parameters = OverlandFlowErosionAnswersParameters(; + usle_k = usle_k, + usle_c = usle_c, + answers_k = answers_k, + slope = slope, + ) + return answers_parameters +end + +function initialize_answers_overland_flow_erosion_model(nc, config, inds) + n = length(inds) + vars = overland_flow_erosion_model_vars(n) + params = initialize_answers_params_overland_flow(nc, config, inds) + bc = overland_flow_erosion_bc(n) + model = OverlandFlowErosionAnswersModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::OverlandFlowErosionAnswersModel, area, dt) + (; overland_flow) = model.boundary_conditions + (; usle_k, usle_c, answers_k, slope) = model.parameters + (; overland_flow_erosion) = model.variables + + n = length(overland_flow) + threaded_foreach(1:n; basesize = 1000) do i + overland_flow_erosion[i] = overland_flow_erosion_answers( + overland_flow[i], + usle_k[i], + usle_c[i], + answers_k[i], + slope[i], + area[i], + dt, + ) + end +end + +function update!(model::NoOverlandFlowErosionModel) + return nothing +end + +get_overland_flow_erosion(model::NoOverlandFlowErosionModel) = 0.0 +get_overland_flow_erosion(model::OverlandFlowErosionAnswersModel) = + model.variables.overland_flow_erosion \ No newline at end of file diff --git a/src/erosion/rainfall_erosion.jl b/src/erosion/rainfall_erosion.jl new file mode 100644 index 000000000..674f79fe9 --- /dev/null +++ b/src/erosion/rainfall_erosion.jl @@ -0,0 +1,218 @@ +abstract type AbstractRainfallErosionModel end + +struct NoRainfallErosionModel <: RainfallErosionModel end + +## General rainfall erosion functions and structs +@get_units @with_kw struct RainfallErosionModelVars{T} + # Total soil erosion from rainfall (splash) + rainfall_erosion::Vector{T} | "t dt-1" +end + +function rainfall_erosion_model_vars(n) + vars = RainfallErosionModelVars(; rainfall_erosion = fill(mv, n)) + return vars +end + +@get_units @with_kw struct RainfallErosionBC{T} + # Precipitation + precip::Vector{T} | "mm dt-1" +end + +function rainfall_erosion_bc(n) + bc = RainfallErosionBC(; precip = fill(mv, n)) + return bc +end + +## EUROSEM specific structs and functions for rainfall erosion +@get_units @with_kw struct RainfallErosionEurosemBC{T} + # Precipitation + precip::Vector{T} | "mm dt-1" + # Interception + interception::Vector{T} | "mm dt-1" + # Water level + waterlevel::Vector{T} | "m" +end + +function rainfall_erosion_eurosem_bc(n) + bc = RainfallErosionEurosemBC(; + precip = fill(mv, n), + interception = fill(mv, n), + waterlevel = fill(mv, n), + ) + return bc +end + +@get_units @with_kw struct RainfallErosionEurosemParameters{T} + # Soil detachability factor + soil_detachability::Vector{T} | "g J-1" + # Exponent EUROSEM + eurosem_exponent::Vector{T} | "-" + # Canopy height + canopyheight::Vector{T} | "m" + # Canopy gap fraction + canopygapfraction::Vector{T} | "-" + # Fraction of the soil that is covered (eg paved, snow, etc) + soilcover_fraction::Vector{T} | "-" +end + +@get_units @with_kw struct RainfallErosionEurosemModel{T} <: AbstractRainfallErosionModel + boundary_conditions::RainfallErosionEurosemBC{T} | "-" + parameters::RainfallErosionEurosemParameters{T} | "-" + variables::RainfallErosionModelVars{T} | "-" +end + +function initialize_eurosem_params(nc, config, inds) + n = length(inds) + soil_detachability = ncread( + nc, + config, + "vertical.soil_erosion.parameters.soil_detachability"; + sel = inds, + defaults = 0.6, + type = Float, + ) + eurosem_exponent = ncread( + nc, + config, + "vertical.soil_erosion.parameters.eurosem_exponent"; + sel = inds, + defaults = 2.0, + type = Float, + ) + canopyheight = ncread( + nc, + config, + "vertical.soil_erosion.parameters.canopyheight"; + sel = inds, + defaults = 0.5, + type = Float, + ) + canopygapfraction = ncread( + nc, + config, + "vertical.soil_erosion.parameters.canopygapfraction"; + sel = inds, + defaults = 0.1, + type = Float, + ) + soilcover_fraction = + ncread(nc, config, "vertical.pathfrac"; sel = inds, defaults = 0.01, type = Float) + eurosem_parameters = RainfallErosionEurosemParameters(; + soil_detachability = soil_detachability, + eurosem_exponent = eurosem_exponent, + canopyheight = canopyheight, + canopygapfraction = canopygapfraction, + soilcover_fraction = soilcover_fraction, + ) + return eurosem_parameters +end + +function initialize_eurosem_rainfall_erosion_model(nc, config, inds) + n = length(inds) + vars = rainfall_erosion_model_vars(n) + params = initialize_eurosem_params(nc, config, inds) + bc = rainfall_erosion_eurosem_bc(n) + model = RainfallErosionEurosemModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::RainfallErosionEurosemModel, area, dt) + (; precip, interception, waterlevel) = model.boundary_conditions + (; + soil_detachability, + eurosem_exponent, + canopyheight, + canopygapfraction, + soilcover_fraction, + ) = model.parameters + (; rainfall_erosion) = model.variables + + n = length(precip) + threaded_foreach(1:n; basesize = 1000) do i + rainfall_erosion[i] = rainfall_erosion_eurosem( + precip[i], + interception[i], + waterlevel[i], + soil_detachability[i], + eurosem_exponent[i], + canopyheight[i], + canopygapfraction[i], + soilcover_fraction[i], + area[i], + dt, + ) + end +end + +# ANSWERS specific structs and functions for rainfall erosion +@get_units @with_kw struct RainfallErosionAnswersParameters{T} + # Soil erodibility factor + usle_k::Vector{T} | "-" + # Crop management factor + usle_c::Vector{T} | "-" +end + +@get_units @with_kw struct RainfallErosionAnswersModel{T} <: AbstractRainfallErosionModel + boundary_conditions::RainfallErosionBC{T} | "-" + parameters::RainfallErosionAnswersParameters{T} | "-" + variables::RainfallErosionModelVars{T} | "-" +end + +function initialize_answers_params_rainfall(nc, config, inds) + usle_k = ncread( + nc, + config, + "vertical.soil_erosion.parameters.usle_k"; + sel = inds, + defaults = 0.1, + type = Float, + ) + usle_c = ncread( + nc, + config, + "vertical.soil_erosion.parameters.usle_c"; + sel = inds, + defaults = 0.01, + type = Float, + ) + answers_parameters = + RainfallErosionAnswersParameters(; usle_k = usle_k, usle_c = usle_c) + return answers_parameters +end + +function initialize_answers_rainfall_erosion_model(nc, config, inds) + n = length(inds) + vars = rainfall_erosion_model_vars(n) + params = initialize_answers_params_rainfall(nc, config, inds) + bc = rainfall_erosion_bc(n) + model = RainfallErosionAnswersModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::RainfallErosionAnswersModel, area, dt) + (; precip) = model.boundary_conditions + (; usle_k, usle_c) = model.parameters + (; rainfall_erosion) = model.variables + + n = length(precip) + threaded_foreach(1:n; basesize = 1000) do i + rainfall_erosion[i] = + rainfall_erosion_answers(precip[i], usle_k[i], usle_c[i], area[i], dt) + end +end + +function update!(model::NoRainfallErosionModel) + return nothing +end + +get_rainfall_erosion(model::NoRainfallErosionModel) = 0.0 +get_rainfall_erosion(model::RainfallErosionEurosemModel) = model.variables.rainfall_erosion +get_rainfall_erosion(model::RainfallErosionAnswersModel) = model.variables.rainfall_erosion \ No newline at end of file diff --git a/src/erosion/soil_erosion.jl b/src/erosion/soil_erosion.jl new file mode 100644 index 000000000..9e33aafa9 --- /dev/null +++ b/src/erosion/soil_erosion.jl @@ -0,0 +1,156 @@ +abstract type AbstractSoilErosionModel end + +## Total soil erosion and differentiation structs and functions +@get_units @with_kw struct SoilErosionModelVars{T} + # Total soil erosion + soil_erosion::Vector{T} | "t dt-1" + # Total clay erosion + clay_erosion::Vector{T} | "t dt-1" + # Total silt erosion + silt_erosion::Vector{T} | "t dt-1" + # Total sand erosion + sand_erosion::Vector{T} | "t dt-1" + # Total small aggregates erosion + sagg_erosion::Vector{T} | "t dt-1" + # Total large aggregates erosion + lagg_erosion::Vector{T} | "t dt-1" +end + +function soil_erosion_model_vars(n) + vars = SoilErosionModelVars(; + soil_erosion = fill(mv, n), + clay_erosion = fill(mv, n), + silt_erosion = fill(mv, n), + sand_erosion = fill(mv, n), + sagg_erosion = fill(mv, n), + lagg_erosion = fill(mv, n), + ) + return vars +end + +@get_units @with_kw struct SoilErosionBC{T} + # Rainfall erosion + rainfall_erosion::Vector{T} | "t dt-1" + # Overland flow erosion + overland_flow_erosion::Vector{T} | "m dt-1" +end + +function soil_erosion_bc(n) + bc = + SoilErosionBC(; rainfall_erosion = fill(mv, n), overland_flow_erosion = fill(mv, n)) + return bc +end + +# Parameters for particle differentiation +@get_units @with_kw struct SoilErosionParameters{T} + # Soil content clay + clay_fraction::Vector{T} | "-" + # Soil content silt + silt_fraction::Vector{T} | "-" + # Soil content sand + sand_fraction::Vector{T} | "-" + # Soil content small aggregates + sagg_fraction::Vector{T} | "-" + # Soil content large aggregates + lagg_fraction::Vector{T} | "-" +end + +@get_units @with_kw struct SoilErosionModel{T} <: AbstractSoilErosionModel + boundary_conditions::SoilErosionBC{T} | "-" + parameters::SoilErosionParameters{T} | "-" + variables::SoilErosionModelVars{T} | "-" +end + +function initialize_soil_erosion_params(nc, config, inds) + clay_fraction = ncread( + nc, + config, + "vertical.soil_erosion.parameters.clay_fraction"; + sel = inds, + defaults = 0.4, + type = Float, + ) + silt_fraction = ncread( + nc, + config, + "vertical.soil_erosion.parameters.silt_fraction"; + sel = inds, + defaults = 0.3, + type = Float, + ) + sand_fraction = ncread( + nc, + config, + "vertical.soil_erosion.parameters.sand_fraction"; + sel = inds, + defaults = 0.3, + type = Float, + ) + sagg_fraction = ncread( + nc, + config, + "vertical.soil_erosion.parameters.sagg_fraction"; + sel = inds, + defaults = 0.0, + type = Float, + ) + lagg_fraction = ncread( + nc, + config, + "vertical.soil_erosion.parameters.lagg_fraction"; + sel = inds, + defaults = 0.0, + type = Float, + ) + # Check that soil fractions sum to 1 + soil_fractions = + clay_fraction + silt_fraction + sand_fraction + sagg_fraction + lagg_fraction + if any(abs.(soil_fractions .- 1.0) .> 1e-6) + error("Particle fractions in the soil must sum to 1") + end + soil_parameters = SoilErosionParameters(; + clay_fraction = clay_fraction, + silt_fraction = silt_fraction, + sand_fraction = sand_fraction, + sagg_fraction = sagg_fraction, + lagg_fraction = lagg_fraction, + ) + + return soil_parameters +end + +function initialize_soil_erosion_model(nc, config, inds) + n = length(inds) + vars = soil_erosion_model_vars(n) + params = initialize_soil_erosion_params(nc, config, inds) + bc = soil_erosion_bc(n) + model = + SoilErosionModel(; boundary_conditions = bc, parameters = params, variables = vars) + return model +end + +function update!(model::SoilErosionModel) + (; rainfall_erosion, overland_flow_erosion) = model.boundary_conditions + (; clay_fraction, silt_fraction, sand_fraction, sagg_fraction, lagg_fraction) = + model.parameters + (; soil_erosion, clay_erosion, silt_erosion, sand_erosion, sagg_erosion, lagg_erosion) = + model.variables + + n = length(rainfall_erosion) + threaded_foreach(1:n; basesize = 1000) do i + soil_erosion[i], + clay_erosion[i], + silt_erosion[i], + sand_erosion[i], + sagg_erosion[i], + lagg_erosion[i] = total_soil_erosion( + rainfall_erosion[i], + overland_flow_erosion[i], + clay_fraction[i], + silt_fraction[i], + sand_fraction[i], + sagg_fraction[i], + lagg_fraction[i], + ) + end +end \ No newline at end of file diff --git a/test/sediment_config_refactor.toml b/test/sediment_config_refactor.toml new file mode 100644 index 000000000..1844b208a --- /dev/null +++ b/test/sediment_config_refactor.toml @@ -0,0 +1,213 @@ +# 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-01-03T00:00:00 +starttime = 1999-12-31T00:00:00 +time_units = "days since 1900-01-01 00:00:00" +timestepsecs = 86400 +dir_input = "data/input" +dir_output = "data/output" + +[state] +path_input = "instates-moselle-sed.nc" +path_output = "outstates-moselle-sed.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.lateral.river] +clayload = "clayload" +claystore = "claystore" +gravload = "gravload" +gravstore = "gravstore" +laggload = "laggload" +laggstore = "laggstore" +outclay = "outclay" +outgrav = "outgrav" +outlagg = "outlagg" +outsagg = "outsagg" +outsand = "outsand" +outsilt = "outsilt" +saggload = "saggload" +saggstore = "saggstore" +sandload = "sandload" +sandstore = "sandstore" +siltload = "siltload" +siltstore = "siltstore" + +[input] +path_forcing = "forcing-moselle-sed.nc" +path_static = "staticmaps-moselle-sed.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.h_land", + "vertical.interception", + "vertical.precipitation", + "vertical.q_land", + "lateral.river.h_riv", + "lateral.river.q_riv", +] + +cyclic = ["vertical.leaf_area_index"] + +[input.vertical] +pathfrac = "PathFrac" +slope = "Slope" +## boundary_conditions to be moved +precipitation = "P" +q_land = "runL" +interception = "int" +## interception to be merged with sbm +#kext = "Kext" +#leaf_area_index = "LAI" # cyclic +#specific_leaf = "Sl" +#storage_wood = "Swood" +## Land transport part +h_land = "levKinL" +rivcell = "wflow_river" +# Reservoir +resareas = "wflow_reservoirareas" +# Lake +lakeareas = "wflow_lakeareas" + +[input.vertical.soil_erosion.parameters] +soil_detachability = "ErosK" +eurosem_exponent = "eros_spl_EUROSEM" +canopyheight = "CanopyHeight" +canopygapfraction = "CanopyGapFraction" +usle_k = "USLE_K" +usle_c = "USLE_C" +answers_k = "eros_ov" +clay_fraction = "PercentClay" +silt_fraction = "PercentSilt" +sand_fraction = "PercentSand" +sagg_fraction = "PercentSagg" +lagg_fraction = "PercentLagg" + +[input.lateral.land] +slope = "Slope" + +[input.lateral.river] +h_riv = "h" +q_riv = "q" +cbagnold = "c_Bagnold" +d50 = "D50_River" +d50engelund = "D50_River" +ebagnold = "exp_Bagnold" +fclayriv = "ClayF_River" +fgravriv = "GravelF_River" +fsandriv = "SandF_River" +fsiltriv = "SiltF_River" +length = "wflow_riverlength" +slope = "RiverSlope" +width = "wflow_riverwidth" +# Reservoir +resarea = "ResSimpleArea" +restrapeff = "ResTrapEff" +resareas = "wflow_reservoirareas" +reslocs = "wflow_reservoirlocs" +# Lake +lakearea = "LakeArea" +lakeareas = "wflow_lakeareas" +lakelocs = "wflow_lakelocs" + +[model] +dolake = false +doreservoir = true +landtransportmethod = "yalinpart" # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] +rainerosmethod = "answers" # Rainfall erosion equation: ["answers", "eurosem"] +reinit = true +rivtransportmethod = "bagnold" # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] +runrivermodel = true +type = "sediment" + +[output] +path = "output-moselle-sed.nc" + +[output.vertical] +TCclay = "TCclay" +TCsed = "TCsed" +erosclay = "erosclay" +pathfrac = "pathfrac" +precipitation = "prec" +sedov = "sedov" +sedspl = "sedspl" +soilloss = "soilloss" + +[output.lateral.land] +inlandclay = "inlandclay" +inlandsed = "inlandsed" +olclay = "olclay" +olsed = "olsed" + +[output.lateral.river] +Bedconc = "Bedconc" +SSconc = "SSconc" +Sedconc = "Sedconc" +clayload = "clayload" +h_riv = "h_riv" +inlandclay = "inlandclayriv" +outclay = "outclay" +width = "widthriv" + +[csv] +path = "output-moselle-sediment.csv" + +[[csv.column]] +coordinate.x = 6.931 +coordinate.y = 48.085 +header = "SL" +parameter = "vertical.soilloss" + +[[csv.column]] +coordinate.x = 6.931 +coordinate.y = 48.085 +header = "SSPL" +parameter = "vertical.sedspl" + +[[csv.column]] +coordinate.x = 6.931 +coordinate.y = 48.085 +header = "SOV" +parameter = "vertical.sedov" + +[[csv.column]] +coordinate.x = 6.931 +coordinate.y = 48.085 +header = "P" +parameter = "vertical.precipitation" + +[[csv.column]] +coordinate.x = 6.931 +coordinate.y = 48.085 +header = "ql" +parameter = "vertical.q_land" + +[[csv.column]] +coordinate.x = 6.931 +coordinate.y = 48.085 +header = "TCsed" +parameter = "vertical.TCsed" + +[[csv.column]] +coordinate.x = 6.931 +coordinate.y = 48.085 +header = "TCclay" +parameter = "vertical.TCclay" + +[[csv.column]] +coordinate.x = 6.931 +coordinate.y = 48.085 +header = "inlandsed" +parameter = "lateral.land.inlandsed" From 39d938c7efcdbecc21ce7852573ceaa658a8cb3a Mon Sep 17 00:00:00 2001 From: hboisgon Date: Tue, 17 Sep 2024 13:39:50 +0800 Subject: [PATCH 18/42] small updates and bugfixes until test comparison --- src/Wflow.jl | 5 + src/erosion.jl | 46 +++++--- src/erosion/erosion_process.jl | 34 +++--- src/erosion/overland_flow_erosion.jl | 57 ++++------ src/erosion/rainfall_erosion.jl | 97 ++++++++-------- src/erosion/soil_erosion.jl | 36 +++--- src/forcing.jl | 28 ++++- src/sediment.jl | 4 - src/sediment_model.jl | 80 +++++++------- test/run_sediment.jl | 64 +++++------ test/sediment_config.toml | 159 +++++++++++++++------------ 11 files changed, 317 insertions(+), 293 deletions(-) diff --git a/src/Wflow.jl b/src/Wflow.jl index 233a96fe0..6081b4ad3 100644 --- a/src/Wflow.jl +++ b/src/Wflow.jl @@ -158,6 +158,11 @@ include("sediment.jl") include("reservoir_lake.jl") include("sbm_model.jl") include("sediment_model.jl") +include("erosion.jl") +include("erosion/erosion_process.jl") +include("erosion/rainfall_erosion.jl") +include("erosion/overland_flow_erosion.jl") +include("erosion/soil_erosion.jl") include("groundwater/connectivity.jl") include("groundwater/aquifer.jl") include("groundwater/boundary_conditions.jl") diff --git a/src/erosion.jl b/src/erosion.jl index a1dd759e2..8b328bd9f 100644 --- a/src/erosion.jl +++ b/src/erosion.jl @@ -1,15 +1,18 @@ -@get_units @with_kw struct SoilErosion{RE, OLE, TE} +@get_units @with_kw struct SoilLoss{RE, OLE, SE, T} + hydrometeo_forcing::HydrometeoForcing | "-" rainfall_erosion::RE | "-" overland_flow_erosion::OLE | "-" - total_erosion::TE | "-" + soil_erosion::SE | "-" + area::Vector{T} | "m²" end -function initialize_soil_erosion(nc, config, inds) - +function initialize_soil_loss(nc, config, inds, area, slope) + n = length(inds) + hydrometeo_forcing = initialize_hydrometeo_forcing(n) # Rainfall erosion rainfallerosionmodel = get(config.model, "rainfall_erosion", "answers")::String if rainfallerosionmodel == "answers" - rainfall_erosion_model = initialize_rainfall_erosion_answers(nc, config, inds) + rainfall_erosion_model = initialize_answers_rainfall_erosion_model(nc, config, inds) elseif rainfallerosionmodel == "eurosem" rainfall_erosion_model = initialize_eurosem_rainfall_erosion_model(nc, config, inds) else @@ -20,37 +23,44 @@ function initialize_soil_erosion(nc, config, inds) overlandflowerosionmodel = get(config.model, "overland_flow_erosion", "answers")::String if overlandflowerosionmodel == "answers" overland_flow_erosion_model = - initialize_answers_overland_flow_erosion_model(nc, config, inds) + initialize_answers_overland_flow_erosion_model(nc, config, inds, slope) else error("Unknown overland flow erosion model: $overlandflowerosionmodel") # overland_flow_erosion_model = NoOverlandFlowErosionModel() end # Total soil erosion and particle differentiation - total_erosion_model = initialize_soil_erosion_model(nc, config, inds) + soil_erosion_model = initialize_soil_erosion_model(nc, config, inds) - soil_erosion = SoilErosion{ + soil_loss = SoilLoss{ typeof(rainfall_erosion_model), typeof(overland_flow_erosion_model), - typeof(total_erosion_model), + typeof(soil_erosion_model), + Float, }(; + hydrometeo_forcing = hydrometeo_forcing, rainfall_erosion = rainfall_erosion_model, overland_flow_erosion = overland_flow_erosion_model, - total_erosion = total_erosion_model, + soil_erosion = soil_erosion_model, + area = area, ) - return soil_erosion + return soil_loss end -function update!(model::SoilErosion, area, dt) +function update!(model::SoilLoss, dt) + # Convert dt to integer + ts = tosecond(dt) #TODO add interception/canopygapfraction calculation here for eurosem #need SBM refactor # Rainfall erosion - update!(model.rainfall_erosion, area, dt) + update!(model.rainfall_erosion, model.hydrometeo_forcing, model.area, ts) # Overland flow erosion - update!(model.overland_flow_erosion, area, dt) + update!(model.overland_flow_erosion, model.hydrometeo_forcing, model.area, ts) # Total soil erosion and particle differentiation - (; rainfall_erosion, overland_flow_erosion) = model.total_erosion.boundary_conditions - @. rainfall_erosion = get_rainfall_erosion(model.rainfall_erosion) - @. overland_flow_erosion = get_overland_flow_erosion(model.overland_flow_erosion) - update!(model.total_erosion) + re = get_rainfall_erosion(model.rainfall_erosion) + ole = get_overland_flow_erosion(model.overland_flow_erosion) + (; rainfall_erosion, overland_flow_erosion) = model.soil_erosion.boundary_conditions + @. rainfall_erosion = re + @. overland_flow_erosion = ole + update!(model.soil_erosion) end diff --git a/src/erosion/erosion_process.jl b/src/erosion/erosion_process.jl index c55bde0e8..00a3d5415 100644 --- a/src/erosion/erosion_process.jl +++ b/src/erosion/erosion_process.jl @@ -9,7 +9,7 @@ canopygapfraction, soilcover_fraction, area, - dt, + ts, ) Rainfall erosion model based on EUROSEM. @@ -24,7 +24,7 @@ Rainfall erosion model based on EUROSEM. - `canopygapfraction` (canopy gap fraction [-]) - `soilcover_fraction` (soil cover fraction [-]) - `area` (area [m2]) -- `dt` (timestep [seconds]) +- `ts` (timestep [seconds]) # Output - `rainfall_erosion` (soil loss [t Δt⁻¹]) @@ -39,10 +39,10 @@ function rainfall_erosion_eurosem( canopygapfraction, soilcover_fraction, area, - dt, + ts, ) # calculate rainfall intensity [mm/h] - rintnsty = precip / (dt / 3600) + rintnsty = precip / (ts / 3600) # Kinetic energy of direct throughfall [J/m2/mm] # kedir = max(11.87 + 8.73 * log10(max(0.0001, rintnsty)),0.0) #basis used in USLE kedir = max(8.95 + 8.44 * log10(max(0.0001, rintnsty)), 0.0) #variant used in most distributed mdoels @@ -58,11 +58,11 @@ function rainfall_erosion_eurosem( #Total kinetic energy by rainfall [J/m2] ketot = (rddir * kedir + rdleaf * keleaf) * 0.001 # Rainfall / splash erosion [g/m2] - sedspl = soil_detachability * ketot * exp(-eurosem_exponent * waterlevel) - sedspl = sedspl * area * 1e-6 # ton/cell + rainfall_erosion = soil_detachability * ketot * exp(-eurosem_exponent * waterlevel) + rainfall_erosion = rainfall_erosion * area * 1e-6 # ton/cell # Remove the impervious area - sedspl = sedspl * (1.0 - soilcover_fraction) + rainfall_erosion = rainfall_erosion * (1.0 - soilcover_fraction) return rainfall_erosion end @@ -72,7 +72,7 @@ end usle_k, usle_c, area, - dt, + ts, ) Rainfall erosion model based on ANSWERS. @@ -83,18 +83,18 @@ Rainfall erosion model based on ANSWERS. - `usle_c` (USLE cover and management factor [-]) - `soilcover_fraction` (soil cover fraction [-]) - `area` (area [m2]) -- `dt` (timestep [seconds]) +- `ts` (timestep [seconds]) # Output - `rainfall_erosion` (soil loss [t Δt⁻¹]) """ -function rainfall_erosion_answers(precip, usle_k, usle_c, area, dt) +function rainfall_erosion_answers(precip, usle_k, usle_c, area, ts) # calculate rainfall intensity [mm/min] rintnsty = precip / (ts / 60) # splash erosion [kg/min] - sedspl = 0.108 * usle_c * usle_k * area * rintnsty^2 + rainfall_erosion = 0.108 * usle_c * usle_k * area * rintnsty^2 # [ton/timestep] - sedspl = sedspl * (dt / 60) * 1e-3 + rainfall_erosion = rainfall_erosion * (ts / 60) * 1e-3 return rainfall_erosion end @@ -108,7 +108,7 @@ end slope, soilcover_fraction, area, - dt, + ts, ) Overland flow erosion model based on ANSWERS. @@ -121,7 +121,7 @@ Overland flow erosion model based on ANSWERS. - `answers_k` (ANSWERS overland flow factor [-]) - `slope` (slope [-]) - `area` (area [m2]) -- `dt` (timestep [seconds]) +- `ts` (timestep [seconds]) # Output - `overland_flow_erosion` (soil loss [t Δt⁻¹]) @@ -133,10 +133,10 @@ function overland_flow_erosion_answers( answers_k, slope, area, - dt, + ts, ) # Overland flow rate [m2/min] - qr_land = overland_flow * 60 / ((area) / 2) + qr_land = overland_flow * 60 / (area .^ 0.5) # Sine of the slope sinslope = sin(atan(slope)) @@ -144,7 +144,7 @@ function overland_flow_erosion_answers( # For a wide range of slope, it is better to use the sine of slope rather than tangeant erosion = answers_k * usle_c * usle_k * area * sinslope * qr_land # [ton/timestep] - erosion = erosion * (dt / 60) * 1e-3 + erosion = erosion * (ts / 60) * 1e-3 return erosion end diff --git a/src/erosion/overland_flow_erosion.jl b/src/erosion/overland_flow_erosion.jl index 237da6474..43a4da684 100644 --- a/src/erosion/overland_flow_erosion.jl +++ b/src/erosion/overland_flow_erosion.jl @@ -5,24 +5,14 @@ struct NoOverlandFlowErosionModel <: AbstractOverlandFlowErosionModel end ## Overland flow structs and functions @get_units @with_kw struct OverlandFlowErosionModelVars{T} # Total soil erosion from overland flow - overland_flow_erosion::Vector{T} | "t dt-1" + amount::Vector{T} | "t dt-1" end function overland_flow_erosion_model_vars(n) - vars = OverlandFlowErosionModelVars(; overland_flow_erosion = fill(mv, n)) + vars = OverlandFlowErosionModelVars(; amount = fill(mv, n)) return vars end -@get_units @with_kw struct OverlandFlowErosionBC{T} - # Overland flow - overland_flow::Vector{T} | "m dt-1" -end - -function overland_flow_erosion_bc(n) - bc = OverlandFlowErosionBC(; overland_flow = fill(mv, n)) - return bc -end - # ANSWERS specific structs and functions for rainfall erosion @get_units @with_kw struct OverlandFlowErosionAnswersParameters{T} # Soil erodibility factor @@ -37,16 +27,15 @@ end @get_units @with_kw struct OverlandFlowErosionAnswersModel{T} <: AbstractOverlandFlowErosionModel - boundary_conditions::OverlandFlowErosionBC{T} | "-" parameters::OverlandFlowErosionAnswersParameters{T} | "-" variables::OverlandFlowErosionModelVars{T} | "-" end -function initialize_answers_params_overland_flow(nc, config, inds) +function initialize_answers_params_overland_flow(nc, config, inds, slope) usle_k = ncread( nc, config, - "vertical.soil_erosion.parameters.usle_k"; + "vertical.overland_flow_erosion.parameters.usle_k"; sel = inds, defaults = 0.1, type = Float, @@ -54,7 +43,7 @@ function initialize_answers_params_overland_flow(nc, config, inds) usle_c = ncread( nc, config, - "vertical.soil_erosion.parameters.usle_c"; + "vertical.overland_flow_erosion.parameters.usle_c"; sel = inds, defaults = 0.01, type = Float, @@ -62,12 +51,11 @@ function initialize_answers_params_overland_flow(nc, config, inds) answers_k = ncread( nc, config, - "vertical.soil_erosion.parameters.answers_k"; + "vertical.overland_flow_erosion.parameters.answers_k"; sel = inds, defaults = 0.9, type = Float, ) - slope = ncread(nc, config, "vertical.slope"; sel = inds, defaults = 0.01, type = Float) answers_parameters = OverlandFlowErosionAnswersParameters(; usle_k = usle_k, usle_c = usle_c, @@ -77,34 +65,34 @@ function initialize_answers_params_overland_flow(nc, config, inds) return answers_parameters end -function initialize_answers_overland_flow_erosion_model(nc, config, inds) +function initialize_answers_overland_flow_erosion_model(nc, config, inds, slope) n = length(inds) vars = overland_flow_erosion_model_vars(n) - params = initialize_answers_params_overland_flow(nc, config, inds) - bc = overland_flow_erosion_bc(n) - model = OverlandFlowErosionAnswersModel(; - boundary_conditions = bc, - parameters = params, - variables = vars, - ) + params = initialize_answers_params_overland_flow(nc, config, inds, slope) + model = OverlandFlowErosionAnswersModel(; parameters = params, variables = vars) return model end -function update!(model::OverlandFlowErosionAnswersModel, area, dt) - (; overland_flow) = model.boundary_conditions +function update!( + model::OverlandFlowErosionAnswersModel, + hydrometeo_forcing::HydrometeoForcing, + area, + ts, +) + (; q_land) = hydrometeo_forcing (; usle_k, usle_c, answers_k, slope) = model.parameters - (; overland_flow_erosion) = model.variables + (; amount) = model.variables - n = length(overland_flow) + n = length(q_land) threaded_foreach(1:n; basesize = 1000) do i - overland_flow_erosion[i] = overland_flow_erosion_answers( - overland_flow[i], + amount[i] = overland_flow_erosion_answers( + q_land[i], usle_k[i], usle_c[i], answers_k[i], slope[i], area[i], - dt, + ts, ) end end @@ -114,5 +102,4 @@ function update!(model::NoOverlandFlowErosionModel) end get_overland_flow_erosion(model::NoOverlandFlowErosionModel) = 0.0 -get_overland_flow_erosion(model::OverlandFlowErosionAnswersModel) = - model.variables.overland_flow_erosion \ No newline at end of file +get_overland_flow_erosion(model::OverlandFlowErosionAnswersModel) = model.variables.amount \ No newline at end of file diff --git a/src/erosion/rainfall_erosion.jl b/src/erosion/rainfall_erosion.jl index 674f79fe9..b7643aa53 100644 --- a/src/erosion/rainfall_erosion.jl +++ b/src/erosion/rainfall_erosion.jl @@ -1,44 +1,26 @@ abstract type AbstractRainfallErosionModel end -struct NoRainfallErosionModel <: RainfallErosionModel end +struct NoRainfallErosionModel <: AbstractRainfallErosionModel end ## General rainfall erosion functions and structs @get_units @with_kw struct RainfallErosionModelVars{T} # Total soil erosion from rainfall (splash) - rainfall_erosion::Vector{T} | "t dt-1" + amount::Vector{T} | "t dt-1" end function rainfall_erosion_model_vars(n) - vars = RainfallErosionModelVars(; rainfall_erosion = fill(mv, n)) + vars = RainfallErosionModelVars(; amount = fill(mv, n)) return vars end -@get_units @with_kw struct RainfallErosionBC{T} - # Precipitation - precip::Vector{T} | "mm dt-1" -end - -function rainfall_erosion_bc(n) - bc = RainfallErosionBC(; precip = fill(mv, n)) - return bc -end - ## EUROSEM specific structs and functions for rainfall erosion @get_units @with_kw struct RainfallErosionEurosemBC{T} - # Precipitation - precip::Vector{T} | "mm dt-1" # Interception interception::Vector{T} | "mm dt-1" - # Water level - waterlevel::Vector{T} | "m" end function rainfall_erosion_eurosem_bc(n) - bc = RainfallErosionEurosemBC(; - precip = fill(mv, n), - interception = fill(mv, n), - waterlevel = fill(mv, n), - ) + bc = RainfallErosionEurosemBC(; interception = fill(mv, n)) return bc end @@ -62,11 +44,10 @@ end end function initialize_eurosem_params(nc, config, inds) - n = length(inds) soil_detachability = ncread( nc, config, - "vertical.soil_erosion.parameters.soil_detachability"; + "vertical.rainfall_erosion.parameters.soil_detachability"; sel = inds, defaults = 0.6, type = Float, @@ -74,7 +55,7 @@ function initialize_eurosem_params(nc, config, inds) eurosem_exponent = ncread( nc, config, - "vertical.soil_erosion.parameters.eurosem_exponent"; + "vertical.rainfall_erosion.parameters.eurosem_exponent"; sel = inds, defaults = 2.0, type = Float, @@ -82,7 +63,7 @@ function initialize_eurosem_params(nc, config, inds) canopyheight = ncread( nc, config, - "vertical.soil_erosion.parameters.canopyheight"; + "vertical.rainfall_erosion.parameters.canopyheight"; sel = inds, defaults = 0.5, type = Float, @@ -90,13 +71,19 @@ function initialize_eurosem_params(nc, config, inds) canopygapfraction = ncread( nc, config, - "vertical.soil_erosion.parameters.canopygapfraction"; + "vertical.rainfall_erosion.parameters.canopygapfraction"; sel = inds, defaults = 0.1, type = Float, ) - soilcover_fraction = - ncread(nc, config, "vertical.pathfrac"; sel = inds, defaults = 0.01, type = Float) + soilcover_fraction = ncread( + nc, + config, + "vertical.rainfall_erosion.parameters.pathfrac"; + sel = inds, + defaults = 0.01, + type = Float, + ) eurosem_parameters = RainfallErosionEurosemParameters(; soil_detachability = soil_detachability, eurosem_exponent = eurosem_exponent, @@ -120,8 +107,14 @@ function initialize_eurosem_rainfall_erosion_model(nc, config, inds) return model end -function update!(model::RainfallErosionEurosemModel, area, dt) - (; precip, interception, waterlevel) = model.boundary_conditions +function update!( + model::RainfallErosionEurosemModel, + hydrometeo_forcing::HydrometeoForcing, + area, + ts, +) + (; precipitation, waterlevel_land) = hydrometeo_forcing + (; interception) = model.boundary_conditions (; soil_detachability, eurosem_exponent, @@ -129,21 +122,21 @@ function update!(model::RainfallErosionEurosemModel, area, dt) canopygapfraction, soilcover_fraction, ) = model.parameters - (; rainfall_erosion) = model.variables + (; amount) = model.variables n = length(precip) threaded_foreach(1:n; basesize = 1000) do i - rainfall_erosion[i] = rainfall_erosion_eurosem( - precip[i], + amount[i] = rainfall_erosion_eurosem( + precipitation[i], interception[i], - waterlevel[i], + waterlevel_land[i], soil_detachability[i], eurosem_exponent[i], canopyheight[i], canopygapfraction[i], soilcover_fraction[i], area[i], - dt, + ts, ) end end @@ -157,7 +150,6 @@ end end @get_units @with_kw struct RainfallErosionAnswersModel{T} <: AbstractRainfallErosionModel - boundary_conditions::RainfallErosionBC{T} | "-" parameters::RainfallErosionAnswersParameters{T} | "-" variables::RainfallErosionModelVars{T} | "-" end @@ -166,7 +158,7 @@ function initialize_answers_params_rainfall(nc, config, inds) usle_k = ncread( nc, config, - "vertical.soil_erosion.parameters.usle_k"; + "vertical.rainfall_erosion.parameters.usle_k"; sel = inds, defaults = 0.1, type = Float, @@ -174,7 +166,7 @@ function initialize_answers_params_rainfall(nc, config, inds) usle_c = ncread( nc, config, - "vertical.soil_erosion.parameters.usle_c"; + "vertical.rainfall_erosion.parameters.usle_c"; sel = inds, defaults = 0.01, type = Float, @@ -188,24 +180,24 @@ function initialize_answers_rainfall_erosion_model(nc, config, inds) n = length(inds) vars = rainfall_erosion_model_vars(n) params = initialize_answers_params_rainfall(nc, config, inds) - bc = rainfall_erosion_bc(n) - model = RainfallErosionAnswersModel(; - boundary_conditions = bc, - parameters = params, - variables = vars, - ) + model = RainfallErosionAnswersModel(; parameters = params, variables = vars) return model end -function update!(model::RainfallErosionAnswersModel, area, dt) - (; precip) = model.boundary_conditions +function update!( + model::RainfallErosionAnswersModel, + hydrometeo_forcing::HydrometeoForcing, + area, + ts, +) + (; precipitation) = hydrometeo_forcing (; usle_k, usle_c) = model.parameters - (; rainfall_erosion) = model.variables + (; amount) = model.variables - n = length(precip) + n = length(precipitation) threaded_foreach(1:n; basesize = 1000) do i - rainfall_erosion[i] = - rainfall_erosion_answers(precip[i], usle_k[i], usle_c[i], area[i], dt) + amount[i] = + rainfall_erosion_answers(precipitation[i], usle_k[i], usle_c[i], area[i], ts) end end @@ -214,5 +206,4 @@ function update!(model::NoRainfallErosionModel) end get_rainfall_erosion(model::NoRainfallErosionModel) = 0.0 -get_rainfall_erosion(model::RainfallErosionEurosemModel) = model.variables.rainfall_erosion -get_rainfall_erosion(model::RainfallErosionAnswersModel) = model.variables.rainfall_erosion \ No newline at end of file +get_rainfall_erosion(model::AbstractRainfallErosionModel) = model.variables.amount \ No newline at end of file diff --git a/src/erosion/soil_erosion.jl b/src/erosion/soil_erosion.jl index 9e33aafa9..affd4caf0 100644 --- a/src/erosion/soil_erosion.jl +++ b/src/erosion/soil_erosion.jl @@ -3,27 +3,27 @@ abstract type AbstractSoilErosionModel end ## Total soil erosion and differentiation structs and functions @get_units @with_kw struct SoilErosionModelVars{T} # Total soil erosion - soil_erosion::Vector{T} | "t dt-1" + amount::Vector{T} | "t dt-1" # Total clay erosion - clay_erosion::Vector{T} | "t dt-1" + clay::Vector{T} | "t dt-1" # Total silt erosion - silt_erosion::Vector{T} | "t dt-1" + silt::Vector{T} | "t dt-1" # Total sand erosion - sand_erosion::Vector{T} | "t dt-1" + sand::Vector{T} | "t dt-1" # Total small aggregates erosion - sagg_erosion::Vector{T} | "t dt-1" + sagg::Vector{T} | "t dt-1" # Total large aggregates erosion - lagg_erosion::Vector{T} | "t dt-1" + lagg::Vector{T} | "t dt-1" end function soil_erosion_model_vars(n) vars = SoilErosionModelVars(; - soil_erosion = fill(mv, n), - clay_erosion = fill(mv, n), - silt_erosion = fill(mv, n), - sand_erosion = fill(mv, n), - sagg_erosion = fill(mv, n), - lagg_erosion = fill(mv, n), + amount = fill(mv, n), + clay = fill(mv, n), + silt = fill(mv, n), + sand = fill(mv, n), + sagg = fill(mv, n), + lagg = fill(mv, n), ) return vars end @@ -105,7 +105,7 @@ function initialize_soil_erosion_params(nc, config, inds) # Check that soil fractions sum to 1 soil_fractions = clay_fraction + silt_fraction + sand_fraction + sagg_fraction + lagg_fraction - if any(abs.(soil_fractions .- 1.0) .> 1e-6) + if any(abs.(soil_fractions .- 1.0) .> 1e-3) error("Particle fractions in the soil must sum to 1") end soil_parameters = SoilErosionParameters(; @@ -133,17 +133,11 @@ function update!(model::SoilErosionModel) (; rainfall_erosion, overland_flow_erosion) = model.boundary_conditions (; clay_fraction, silt_fraction, sand_fraction, sagg_fraction, lagg_fraction) = model.parameters - (; soil_erosion, clay_erosion, silt_erosion, sand_erosion, sagg_erosion, lagg_erosion) = - model.variables + (; amount, clay, silt, sand, sagg, lagg) = model.variables n = length(rainfall_erosion) threaded_foreach(1:n; basesize = 1000) do i - soil_erosion[i], - clay_erosion[i], - silt_erosion[i], - sand_erosion[i], - sagg_erosion[i], - lagg_erosion[i] = total_soil_erosion( + amount[i], clay[i], silt[i], sand[i], sagg[i], lagg[i] = total_soil_erosion( rainfall_erosion[i], overland_flow_erosion[i], clay_fraction[i], diff --git a/src/forcing.jl b/src/forcing.jl index 294aa2908..0f90bff1d 100644 --- a/src/forcing.jl +++ b/src/forcing.jl @@ -15,9 +15,29 @@ function AtmosphericForcing( potential_evaporation::Vector{T} = fill(mv, n), temperature::Vector{T} = fill(mv, n), ) where {T} - return AtmosphericForcing{T}(; - precipitation, - potential_evaporation, - temperature, + return AtmosphericForcing{T}(; precipitation, potential_evaporation, temperature) +end + +@get_units @with_kw struct HydrometeoForcing{T} + # Precipitation [mm Δt⁻¹] + precipitation::Vector{T} + # Overland flow depth [m] + waterlevel_land::Vector{T} | "m" + # Overland flow discharge [m3 s-1] + q_land::Vector{T} | "m3 s-1" + # River depth [m] + waterlevel_river::Vector{T} | "m" + # River discharge [m3 s-1] + q_river::Vector{T} | "m3 s-1" +end + +function initialize_hydrometeo_forcing(n) + hydrometeo_forcing = HydrometeoForcing(; + precipitation = fill(mv, n), + waterlevel_land = fill(mv, n), + q_land = fill(mv, n), + waterlevel_river = fill(mv, n), + q_river = fill(mv, n), ) + return hydrometeo_forcing end \ No newline at end of file diff --git a/src/sediment.jl b/src/sediment.jl index 3bdee4b87..c5083afa5 100644 --- a/src/sediment.jl +++ b/src/sediment.jl @@ -1569,8 +1569,4 @@ function update!(rs::RiverSediment, network, config) rs.SSconc[v] = SS * toconc rs.Bedconc[v] = Bed * toconc end -<<<<<<< HEAD - return nothing -======= ->>>>>>> d21c93089 (Refactor/style guide (#437)) end diff --git a/src/sediment_model.jl b/src/sediment_model.jl index cf2b78eaf..b44f2604a 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -13,18 +13,15 @@ function initialize_sediment_model(config::Config) reader = prepare_reader(config) clock = Clock(config, reader) - dt = clock.dt do_river = get(config.model, "runrivermodel", false)::Bool nc = NCDataset(static_path) - dims = dimnames(nc[param(config, "input.subcatchment")]) 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) - modelsize_2d = size(subcatch_2d) river_2d = ncread(nc, config, "river_location"; optional = false, type = Bool, fill = false) @@ -37,7 +34,6 @@ function initialize_sediment_model(config::Config) riverlength = riverlength_2d[inds] inds_riv, rev_inds_riv = active_indices(river_2d, 0) - nriv = length(inds_riv) # Needed to update the forcing reservoir = () @@ -52,8 +48,12 @@ function initialize_sediment_model(config::Config) sizeinmetres = get(config.model, "sizeinmetres", false)::Bool xl, yl = cell_lengths(y, cellength, sizeinmetres) riverfrac = river_fraction(river, riverlength, riverwidth, xl, yl) + area = xl .* yl + landslope = + ncread(nc, config, "vertical.slope"; optional = false, sel = inds, type = Float) + clamp!(landslope, 0.00001, Inf) - eros = initialize_landsed(nc, config, river, riverfrac, xl, yl, inds) + soilloss = initialize_soil_loss(nc, config, inds, area, landslope) ldd_2d = ncread(nc, config, "ldd"; optional = false, allow_missing = true) ldd = ldd_2d[inds] @@ -111,7 +111,7 @@ function initialize_sediment_model(config::Config) rs = initialize_riversed(nc, config, riverwidth, riverlength, inds_riv) - modelmap = (vertical = eros, lateral = (land = ols, river = rs)) + modelmap = (vertical = soilloss, lateral = (land = ols, river = rs)) indices_reverse = ( land = rev_inds, river = rev_inds_riv, @@ -140,7 +140,7 @@ function initialize_sediment_model(config::Config) config, (; land, river, reservoir, lake, index_river, frac_toriver), (land = ols, river = rs), - eros, + soilloss, clock, reader, writer, @@ -154,39 +154,41 @@ 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} - (; lateral, vertical, network, config) = model - - update_until_ols!(vertical, config) - update_until_oltransport!(vertical, config) - - lateral.land.soilloss .= vertical.soilloss - lateral.land.erosclay .= vertical.erosclay - lateral.land.erossilt .= vertical.erossilt - lateral.land.erossand .= vertical.erossand - lateral.land.erossagg .= vertical.erossagg - lateral.land.eroslagg .= vertical.eroslagg - - lateral.land.TCsed .= vertical.TCsed - lateral.land.TCclay .= vertical.TCclay - lateral.land.TCsilt .= vertical.TCsilt - lateral.land.TCsand .= vertical.TCsand - lateral.land.TCsagg .= vertical.TCsagg - lateral.land.TClagg .= vertical.TClagg - - update!(lateral.land, network.land, config) - - do_river = get(config.model, "runrivermodel", false)::Bool - - if do_river - inds_riv = network.index_river - lateral.river.inlandclay .= lateral.land.inlandclay[inds_riv] - lateral.river.inlandsilt .= lateral.land.inlandsilt[inds_riv] - lateral.river.inlandsand .= lateral.land.inlandsand[inds_riv] - lateral.river.inlandsagg .= lateral.land.inlandsagg[inds_riv] - lateral.river.inlandlagg .= lateral.land.inlandlagg[inds_riv] + (; lateral, vertical, network, config, clock) = model + dt = clock.dt - update!(lateral.river, network.river, config) - end + # Soil erosion + update!(vertical, dt) + # update_until_oltransport(vertical, config) + + # lateral.land.soilloss .= vertical.soilloss + # lateral.land.erosclay .= vertical.erosclay + # lateral.land.erossilt .= vertical.erossilt + # lateral.land.erossand .= vertical.erossand + # lateral.land.erossagg .= vertical.erossagg + # lateral.land.eroslagg .= vertical.eroslagg + + # lateral.land.TCsed .= vertical.TCsed + # lateral.land.TCclay .= vertical.TCclay + # lateral.land.TCsilt .= vertical.TCsilt + # lateral.land.TCsand .= vertical.TCsand + # lateral.land.TCsagg .= vertical.TCsagg + # lateral.land.TClagg .= vertical.TClagg + + # update(lateral.land, network.land, config) + + # do_river = get(config.model, "runrivermodel", false)::Bool + + # if do_river + # inds_riv = network.index_river + # lateral.river.inlandclay .= lateral.land.inlandclay[inds_riv] + # lateral.river.inlandsilt .= lateral.land.inlandsilt[inds_riv] + # lateral.river.inlandsand .= lateral.land.inlandsand[inds_riv] + # lateral.river.inlandsagg .= lateral.land.inlandsagg[inds_riv] + # lateral.river.inlandlagg .= lateral.land.inlandlagg[inds_riv] + + # update(lateral.river, network.river, config) + # end return nothing end diff --git a/test/run_sediment.jl b/test/run_sediment.jl index 58c0096bc..699808bef 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -11,45 +11,47 @@ Wflow.run_timestep!(model) @testset "first timestep sediment model (vertical)" begin eros = model.vertical - @test eros.erosov[1] ≈ 0.9f0 + @test eros.hydrometeo_forcing.q_land[1] ≈ 0.0f0 + @test eros.overland_flow_erosion.parameters.usle_k[1] ≈ 0.026510488241910934 + @test eros.overland_flow_erosion.variables.amount[1] ≈ 0.9f0 @test model.clock.iteration == 1 - @test mean(eros.leaf_area_index) ≈ 1.7120018886212223f0 - @test eros.dmsand[1] == 200.0f0 - @test eros.dmlagg[1] == 500.0f0 - @test mean(eros.interception) ≈ 0.4767846753916875f0 - @test mean(eros.soilloss) ≈ 0.008596682196335555f0 + #@test mean(eros.leaf_area_index) ≈ 1.7120018886212223f0 + #@test eros.dmsand[1] == 200.0f0 + #@test eros.dmlagg[1] == 500.0f0 + #@test mean(eros.interception) ≈ 0.4767846753916875f0 + @test mean(eros.soil_erosion.variables.amount) ≈ 0.008596682196335555f0 end -# run the second timestep -Wflow.run_timestep!(model) +# # run the second timestep +# model = Wflow.run_timestep(model) -@testset "second timestep sediment model (vertical)" begin - eros = model.vertical +# @testset "second timestep sediment model (vertical)" begin +# eros = model.vertical - @test mean(eros.soilloss) ≈ 0.07601393657280235f0 - @test mean(eros.erosclay) ≈ 0.0022388720384961766f0 - @test mean(eros.erossand) ≈ 0.02572046244407882f0 - @test mean(eros.eroslagg) ≈ 0.022126541806118796f0 - @test mean(eros.TCsed) == 0.0 - @test mean(eros.TCsilt) ≈ 1.0988158364353527f6 - @test mean(eros.TCsand) ≈ 1.0987090622888755f6 - @test mean(eros.TCclay) ≈ 1.0992655197016734f6 - @test mean(eros.TCsilt) ≈ 1.0988158364353527f6 -end +# @test mean(eros.soil_erosion.variables.amount) ≈ 0.07601393657280235f0 +# @test mean(eros.soil_erosion.variables.clay) ≈ 0.0022388720384961766f0 +# @test mean(eros.soil_erosion.variables.sand) ≈ 0.02572046244407882f0 +# @test mean(eros.soil_erosion.variables.lagg) ≈ 0.022126541806118796f0 +# #@test mean(eros.TCsed) == 0.0 +# #@test mean(eros.TCsilt) ≈ 1.0988158364353527f6 +# #@test mean(eros.TCsand) ≈ 1.0987090622888755f6 +# #@test mean(eros.TCclay) ≈ 1.0992655197016734f6 +# #@test mean(eros.TCsilt) ≈ 1.0988158364353527f6 +# end -@testset "second timestep sediment model (lateral)" begin - lat = model.lateral +# @testset "second timestep sediment model (lateral)" begin +# lat = model.lateral - @test mean(lat.land.inlandsed) ≈ 0.07463801685030906f0 - @test mean(lat.land.inlandclay) ≈ 0.0022367786781657497f0 - @test mean(lat.land.inlandsand) ≈ 0.02519222037812127f0 - @test mean(lat.land.olclay) ≈ 0.006443036462118322f0 +# @test mean(lat.land.inlandsed) ≈ 0.07463801685030906f0 +# @test mean(lat.land.inlandclay) ≈ 0.0022367786781657497f0 +# @test mean(lat.land.inlandsand) ≈ 0.02519222037812127f0 +# @test mean(lat.land.olclay) ≈ 0.006443036462118322f0 - @test mean(lat.river.SSconc) ≈ 0.8259993252994058f0 - @test mean(lat.river.inlandclay) ≈ 0.01980468760667709f0 - @test lat.river.h_riv[network.river.order[end]] ≈ 0.006103649735450745f0 - @test lat.river.outclay[5649] ≈ 2.359031898208781f-9 -end +# @test mean(lat.river.SSconc) ≈ 0.8259993252994058f0 +# @test mean(lat.river.inlandclay) ≈ 0.01980468760667709f0 +# @test lat.river.h_riv[network.river.order[end]] ≈ 0.006103649735450745f0 +# @test lat.river.outclay[5649] ≈ 2.359031898208781f-9 +# end @testset "Exchange and grid location sediment" begin @test Wflow.exchange(model.vertical.n) == false diff --git a/test/sediment_config.toml b/test/sediment_config.toml index 23c9fd4b5..946f0786f 100644 --- a/test/sediment_config.toml +++ b/test/sediment_config.toml @@ -51,42 +51,57 @@ 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.h_land", - "vertical.interception", - "vertical.precipitation", - "vertical.q_land", + "vertical.hydrometeo_forcing.waterlevel_land", + #"vertical.interception", + "vertical.hydrometeo_forcing.precipitation", + "vertical.hydrometeo_forcing.q_land", "lateral.river.h_riv", "lateral.river.q_riv", ] -cyclic = ["vertical.leaf_area_index"] +#cyclic = ["vertical.leaf_area_index"] [input.vertical] -altitude = "wflow_dem" -canopyheight = "CanopyHeight" -erosk = "ErosK" -erosov = "eros_ov" -erosspl = "eros_spl_EUROSEM" -h_land = "levKinL" -interception = "int" -kext = "Kext" -leaf_area_index = "LAI" # cyclic +## interception parameters to be moved +#kext = "Kext" +#leaf_area_index = "LAI" # cyclic +#specific_leaf = "Sl" +#storage_wood = "Swood" +## other parameters pathfrac = "PathFrac" -pclay = "PercentClay" -precipitation = "P" -psilt = "PercentSilt" -q_land = "runL" rivcell = "wflow_river" slope = "Slope" -specific_leaf = "Sl" -storage_wood = "Swood" -usleC = "USLE_C" -usleK = "USLE_K" # Reservoir resareas = "wflow_reservoirareas" # Lake lakeareas = "wflow_lakeareas" +[input.vertical.hydrometeo_forcing] +precipitation = "P" +waterlevel_land = "levKinL" +#interception = "int" +q_land = "runL" + +[input.vertical.rainfall_erosion.parameters] +soil_detachability = "soil_detachability" +eurosem_exponent = "eros_spl_EUROSEM" +canopyheight = "CanopyHeight" +canopygapfraction = "CanopyGapFraction" +usle_k = "usle_k" +usle_c = "USLE_C" + +[input.vertical.overland_flow_erosion.parameters] +usle_k = "usle_k" +usle_c = "USLE_C" +answers_k = "eros_ov" + +[input.vertical.soil_erosion.parameters] +clay_fraction = "fclay_soil" +silt_fraction = "fsilt_soil" +sand_fraction = "fsand_soil" +sagg_fraction = "fsagg_soil" +lagg_fraction = "flagg_soil" + [input.lateral.land] slope = "Slope" @@ -121,37 +136,39 @@ landtransportmethod = "yalinpart" # Overland flow transport capacity method: ["y rainerosmethod = "answers" # Rainfall erosion equation: ["answers", "eurosem"] reinit = true rivtransportmethod = "bagnold" # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] -runrivermodel = true +runrivermodel = false type = "sediment" [output] path = "output-moselle-sed.nc" -[output.vertical] -TCclay = "TCclay" -TCsed = "TCsed" -erosclay = "erosclay" -pathfrac = "pathfrac" -precipitation = "prec" -sedov = "sedov" -sedspl = "sedspl" -soilloss = "soilloss" - -[output.lateral.land] -inlandclay = "inlandclay" -inlandsed = "inlandsed" -olclay = "olclay" -olsed = "olsed" - -[output.lateral.river] -Bedconc = "Bedconc" -SSconc = "SSconc" -Sedconc = "Sedconc" -clayload = "clayload" -h_riv = "h_riv" -inlandclay = "inlandclayriv" -outclay = "outclay" -width = "widthriv" +[output.vertical.rainfall_erosion.variables] +amount = "rainfall_erosion" + +[output.vertical.overland_flow_erosion.variables] +amount = "overland_flow_erosion" + +[output.vertical.soil_erosion.variables] +amount = "soilloss" +clay = "erosclay" + +# [output.lateral.land] +# #TCclay = "TCclay" +# #TCsed = "TCsed" +# inlandclay = "inlandclay" +# inlandsed = "inlandsed" +# olclay = "olclay" +# olsed = "olsed" + +# [output.lateral.river] +# Bedconc = "Bedconc" +# SSconc = "SSconc" +# Sedconc = "Sedconc" +# clayload = "clayload" +# h_riv = "h_riv" +# inlandclay = "inlandclayriv" +# outclay = "outclay" +# width = "widthriv" [csv] path = "output-moselle-sediment.csv" @@ -160,46 +177,46 @@ path = "output-moselle-sediment.csv" coordinate.x = 6.931 coordinate.y = 48.085 header = "SL" -parameter = "vertical.soilloss" +parameter = "vertical.soil_erosion.variables.amount" [[csv.column]] coordinate.x = 6.931 coordinate.y = 48.085 header = "SSPL" -parameter = "vertical.sedspl" +parameter = "vertical.rainfall_erosion.variables.amount" [[csv.column]] coordinate.x = 6.931 coordinate.y = 48.085 header = "SOV" -parameter = "vertical.sedov" +parameter = "vertical.overland_flow_erosion.variables.amount" [[csv.column]] coordinate.x = 6.931 coordinate.y = 48.085 header = "P" -parameter = "vertical.precipitation" +parameter = "vertical.hydrometeo_forcing.precipitation" [[csv.column]] coordinate.x = 6.931 coordinate.y = 48.085 header = "ql" -parameter = "vertical.q_land" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "TCsed" -parameter = "vertical.TCsed" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "TCclay" -parameter = "vertical.TCclay" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "inlandsed" -parameter = "lateral.land.inlandsed" +parameter = "vertical.hydrometeo_forcing.q_land" + +# [[csv.column]] +# coordinate.x = 6.931 +# coordinate.y = 48.085 +# header = "TCsed" +# parameter = "vertical.TCsed" + +# [[csv.column]] +# coordinate.x = 6.931 +# coordinate.y = 48.085 +# header = "TCclay" +# parameter = "vertical.TCclay" + +# [[csv.column]] +# coordinate.x = 6.931 +# coordinate.y = 48.085 +# header = "inlandsed" +# parameter = "lateral.land.inlandsed" From 4fbb8b53d7d25ee0098dc790b9d08e826d406bd9 Mon Sep 17 00:00:00 2001 From: hboisgon Date: Tue, 17 Sep 2024 14:41:53 +0800 Subject: [PATCH 19/42] Working tests for soil erosion --- test/run_sediment.jl | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/test/run_sediment.jl b/test/run_sediment.jl index 699808bef..c0729c5a5 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -11,33 +11,41 @@ Wflow.run_timestep!(model) @testset "first timestep sediment model (vertical)" begin eros = model.vertical + @test eros.hydrometeo_forcing.precipitation[1] ≈ 4.086122035980225f0 @test eros.hydrometeo_forcing.q_land[1] ≈ 0.0f0 - @test eros.overland_flow_erosion.parameters.usle_k[1] ≈ 0.026510488241910934 - @test eros.overland_flow_erosion.variables.amount[1] ≈ 0.9f0 + @test eros.overland_flow_erosion.parameters.usle_k[1] ≈ 0.026510488241910934f0 + @test eros.overland_flow_erosion.parameters.usle_c[1] ≈ 0.014194443821907043f0 + @test eros.overland_flow_erosion.parameters.answers_k[1] ≈ 0.9f0 + @test eros.overland_flow_erosion.variables.amount[1] ≈ 0.0f0 + @test eros.rainfall_erosion.variables.amount[1] ≈ 0.00027245577922893746f0 @test model.clock.iteration == 1 #@test mean(eros.leaf_area_index) ≈ 1.7120018886212223f0 #@test eros.dmsand[1] == 200.0f0 #@test eros.dmlagg[1] == 500.0f0 #@test mean(eros.interception) ≈ 0.4767846753916875f0 - @test mean(eros.soil_erosion.variables.amount) ≈ 0.008596682196335555f0 + @test mean(eros.overland_flow_erosion.variables.amount) ≈ 0.00861079076689589f0 + @test mean(eros.rainfall_erosion.variables.amount) ≈ 0.00016326203201620437f0 + @test mean(eros.soil_erosion.variables.amount) ≈ 0.008774052798912092f0 end -# # run the second timestep -# model = Wflow.run_timestep(model) +# run the second timestep +model = Wflow.run_timestep(model) -# @testset "second timestep sediment model (vertical)" begin -# eros = model.vertical +@testset "second timestep sediment model (vertical)" begin + eros = model.vertical -# @test mean(eros.soil_erosion.variables.amount) ≈ 0.07601393657280235f0 -# @test mean(eros.soil_erosion.variables.clay) ≈ 0.0022388720384961766f0 -# @test mean(eros.soil_erosion.variables.sand) ≈ 0.02572046244407882f0 -# @test mean(eros.soil_erosion.variables.lagg) ≈ 0.022126541806118796f0 -# #@test mean(eros.TCsed) == 0.0 -# #@test mean(eros.TCsilt) ≈ 1.0988158364353527f6 -# #@test mean(eros.TCsand) ≈ 1.0987090622888755f6 -# #@test mean(eros.TCclay) ≈ 1.0992655197016734f6 -# #@test mean(eros.TCsilt) ≈ 1.0988158364353527f6 -# end + @test mean(eros.soil_erosion.variables.amount) ≈ 0.07765800489746684f0 + @test mean(eros.soil_erosion.variables.clay) ≈ 0.002287480354866626f0 + @test mean(eros.soil_erosion.variables.silt) ≈ 0.0036164773521184896f0 + @test mean(eros.soil_erosion.variables.sand) ≈ 0.026301393837924607f0 + @test mean(eros.soil_erosion.variables.lagg) ≈ 0.022577957752547836f0 + @test mean(eros.soil_erosion.variables.sagg) ≈ 0.022874695590802723f0 + #@test mean(eros.TCsed) == 0.0 + #@test mean(eros.TCsilt) ≈ 1.0988158364353527f6 + #@test mean(eros.TCsand) ≈ 1.0987090622888755f6 + #@test mean(eros.TCclay) ≈ 1.0992655197016734f6 + #@test mean(eros.TCsilt) ≈ 1.0988158364353527f6 +end # @testset "second timestep sediment model (lateral)" begin # lat = model.lateral From 3db93fb31585332fb463fe31725cf039276075be Mon Sep 17 00:00:00 2001 From: hboisgon Date: Tue, 17 Sep 2024 14:50:35 +0800 Subject: [PATCH 20/42] re-enable all tests and remove non-used config --- test/runtests.jl | 2 + test/sediment_config_refactor.toml | 213 ----------------------------- 2 files changed, 2 insertions(+), 213 deletions(-) delete mode 100644 test/sediment_config_refactor.toml diff --git a/test/runtests.jl b/test/runtests.jl index 0f45dde45..6981af08d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -83,6 +83,8 @@ with_logger(NullLogger()) do include("vertical_process.jl") include("reservoir_lake.jl") include("run_sbm.jl") + include("run_sbm_piave.jl") + include("run_sbm_gwf_piave.jl") include("run_sbm_gwf.jl") include("run.jl") include("groundwater.jl") diff --git a/test/sediment_config_refactor.toml b/test/sediment_config_refactor.toml deleted file mode 100644 index 1844b208a..000000000 --- a/test/sediment_config_refactor.toml +++ /dev/null @@ -1,213 +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-01-03T00:00:00 -starttime = 1999-12-31T00:00:00 -time_units = "days since 1900-01-01 00:00:00" -timestepsecs = 86400 -dir_input = "data/input" -dir_output = "data/output" - -[state] -path_input = "instates-moselle-sed.nc" -path_output = "outstates-moselle-sed.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.lateral.river] -clayload = "clayload" -claystore = "claystore" -gravload = "gravload" -gravstore = "gravstore" -laggload = "laggload" -laggstore = "laggstore" -outclay = "outclay" -outgrav = "outgrav" -outlagg = "outlagg" -outsagg = "outsagg" -outsand = "outsand" -outsilt = "outsilt" -saggload = "saggload" -saggstore = "saggstore" -sandload = "sandload" -sandstore = "sandstore" -siltload = "siltload" -siltstore = "siltstore" - -[input] -path_forcing = "forcing-moselle-sed.nc" -path_static = "staticmaps-moselle-sed.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.h_land", - "vertical.interception", - "vertical.precipitation", - "vertical.q_land", - "lateral.river.h_riv", - "lateral.river.q_riv", -] - -cyclic = ["vertical.leaf_area_index"] - -[input.vertical] -pathfrac = "PathFrac" -slope = "Slope" -## boundary_conditions to be moved -precipitation = "P" -q_land = "runL" -interception = "int" -## interception to be merged with sbm -#kext = "Kext" -#leaf_area_index = "LAI" # cyclic -#specific_leaf = "Sl" -#storage_wood = "Swood" -## Land transport part -h_land = "levKinL" -rivcell = "wflow_river" -# Reservoir -resareas = "wflow_reservoirareas" -# Lake -lakeareas = "wflow_lakeareas" - -[input.vertical.soil_erosion.parameters] -soil_detachability = "ErosK" -eurosem_exponent = "eros_spl_EUROSEM" -canopyheight = "CanopyHeight" -canopygapfraction = "CanopyGapFraction" -usle_k = "USLE_K" -usle_c = "USLE_C" -answers_k = "eros_ov" -clay_fraction = "PercentClay" -silt_fraction = "PercentSilt" -sand_fraction = "PercentSand" -sagg_fraction = "PercentSagg" -lagg_fraction = "PercentLagg" - -[input.lateral.land] -slope = "Slope" - -[input.lateral.river] -h_riv = "h" -q_riv = "q" -cbagnold = "c_Bagnold" -d50 = "D50_River" -d50engelund = "D50_River" -ebagnold = "exp_Bagnold" -fclayriv = "ClayF_River" -fgravriv = "GravelF_River" -fsandriv = "SandF_River" -fsiltriv = "SiltF_River" -length = "wflow_riverlength" -slope = "RiverSlope" -width = "wflow_riverwidth" -# Reservoir -resarea = "ResSimpleArea" -restrapeff = "ResTrapEff" -resareas = "wflow_reservoirareas" -reslocs = "wflow_reservoirlocs" -# Lake -lakearea = "LakeArea" -lakeareas = "wflow_lakeareas" -lakelocs = "wflow_lakelocs" - -[model] -dolake = false -doreservoir = true -landtransportmethod = "yalinpart" # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] -rainerosmethod = "answers" # Rainfall erosion equation: ["answers", "eurosem"] -reinit = true -rivtransportmethod = "bagnold" # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] -runrivermodel = true -type = "sediment" - -[output] -path = "output-moselle-sed.nc" - -[output.vertical] -TCclay = "TCclay" -TCsed = "TCsed" -erosclay = "erosclay" -pathfrac = "pathfrac" -precipitation = "prec" -sedov = "sedov" -sedspl = "sedspl" -soilloss = "soilloss" - -[output.lateral.land] -inlandclay = "inlandclay" -inlandsed = "inlandsed" -olclay = "olclay" -olsed = "olsed" - -[output.lateral.river] -Bedconc = "Bedconc" -SSconc = "SSconc" -Sedconc = "Sedconc" -clayload = "clayload" -h_riv = "h_riv" -inlandclay = "inlandclayriv" -outclay = "outclay" -width = "widthriv" - -[csv] -path = "output-moselle-sediment.csv" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "SL" -parameter = "vertical.soilloss" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "SSPL" -parameter = "vertical.sedspl" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "SOV" -parameter = "vertical.sedov" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "P" -parameter = "vertical.precipitation" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "ql" -parameter = "vertical.q_land" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "TCsed" -parameter = "vertical.TCsed" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "TCclay" -parameter = "vertical.TCclay" - -[[csv.column]] -coordinate.x = 6.931 -coordinate.y = 48.085 -header = "inlandsed" -parameter = "lateral.land.inlandsed" From f4f7124cc5c33d3c5906bc9b69cc23897ba995c7 Mon Sep 17 00:00:00 2001 From: hboisgon Date: Thu, 19 Sep 2024 17:46:07 +0800 Subject: [PATCH 21/42] refactor sediment lateral land --- src/Wflow.jl | 5 + src/sediment.jl | 676 ------------------ src/sediment_flux.jl | 77 ++ src/sediment_model.jl | 96 ++- src/sediment_transport/land_to_river.jl | 155 ++++ .../overland_flow_transport.jl | 197 +++++ src/sediment_transport/transport_capacity.jl | 424 +++++++++++ .../transport_capacity_process.jl | 287 ++++++++ test/run_sediment.jl | 37 +- test/runtests.jl | 26 +- test/sediment_config.toml | 50 +- 11 files changed, 1250 insertions(+), 780 deletions(-) create mode 100644 src/sediment_flux.jl create mode 100644 src/sediment_transport/land_to_river.jl create mode 100644 src/sediment_transport/overland_flow_transport.jl create mode 100644 src/sediment_transport/transport_capacity.jl create mode 100644 src/sediment_transport/transport_capacity_process.jl diff --git a/src/Wflow.jl b/src/Wflow.jl index 6081b4ad3..0b25bba37 100644 --- a/src/Wflow.jl +++ b/src/Wflow.jl @@ -163,6 +163,11 @@ include("erosion/erosion_process.jl") include("erosion/rainfall_erosion.jl") include("erosion/overland_flow_erosion.jl") include("erosion/soil_erosion.jl") +include("sediment_flux.jl") +include("sediment_transport/transport_capacity_process.jl") +include("sediment_transport/transport_capacity.jl") +include("sediment_transport/overland_flow_transport.jl") +include("sediment_transport/land_to_river.jl") include("groundwater/connectivity.jl") include("groundwater/aquifer.jl") include("groundwater/boundary_conditions.jl") diff --git a/src/sediment.jl b/src/sediment.jl index c5083afa5..cff02b4d3 100644 --- a/src/sediment.jl +++ b/src/sediment.jl @@ -1,679 +1,3 @@ -### Soil erosion ### -@get_units @grid_loc @with_kw struct LandSediment{T} - # number of cells - n::Int | "-" | "none" - ### Soil erosion part ### - # length of cells in y direction [m] - yl::Vector{T} | "m" - # length of cells in x direction [m] - xl::Vector{T} | "m" - # Fraction of river [-] - riverfrac::Vector{T} | "-" - # Waterbodies areas [-] - wbcover::Vector{T} | "-" - # Depth of overland flow [m] - h_land::Vector{T} | "m" - # Canopy interception [mm Δt⁻¹] - interception::Vector{T} | "mm dt-1" - # Precipitation [mm Δt⁻¹] - precipitation::Vector{T} | "mm dt-1" - # Overland flow [m3/s] - q_land::Vector{T} | "m3 s-1" - # Canopy height [m] - canopyheight::Vector{T} | "m" - # Canopy gap fraction [-] - canopygapfraction::Vector{T} | "-" - # Coefficient for EUROSEM rainfall erosion [-] - erosk::Vector{T} | "-" - # Exponent for EUROSEM rainfall erosion [-] - erosspl::Vector{T} | "-" - # Coefficient for ANSWERS overland flow erosion [-] - erosov::Vector{T} | "-" - # Fraction of impervious area per grid cell [-] - pathfrac::Vector{T} | "-" - # Land slope [-] - slope::Vector{T} | "-" - # USLE crop management factor [-] - usleC::Vector{T} | "-" - # USLE soil erodibility factor [-] - usleK::Vector{T} | "-" - # Sediment eroded by rainfall [ton Δt⁻¹] - sedspl::Vector{T} | "t dt-1" - # Sediment eroded by overland flow [ton Δt⁻¹] - sedov::Vector{T} | "t dt-1" - # Total eroded soil [ton Δt⁻¹] - soilloss::Vector{T} | "t dt-1" - # Eroded soil per particle class [ton Δt⁻¹] - erosclay::Vector{T} | "t dt-1" # clay - erossilt::Vector{T} | "t dt-1" # silt - erossand::Vector{T} | "t dt-1" # sand - erossagg::Vector{T} | "t dt-1" # small aggregates - eroslagg::Vector{T} | "t dt-1" # large aggregates - ## 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" - ### Transport capacity part ### - # Drain length [m] - dl::Vector{T} | "m" - # Flow width [m] - dw::Vector{T} | "m" - # Govers transport capacity coefficients [-] - cGovers::Vector{T} | "-" - nGovers::Vector{T} | "-" - # Median particle diameter of the topsoil [mm] - D50::Vector{T} | "mm" - # Median diameter per particle class [um] - dmclay::Vector{T} | "µm" - dmsilt::Vector{T} | "µm" - dmsand::Vector{T} | "µm" - dmsagg::Vector{T} | "µm" - dmlagg::Vector{T} | "µm" - # Fraction of the different particle class [-] - fclay::Vector{T} | "-" - fsilt::Vector{T} | "-" - fsand::Vector{T} | "-" - fsagg::Vector{T} | "-" - flagg::Vector{T} | "-" - # Density of sediment [kg/m3] - rhos::Vector{T} | "kg m-3" - # Filter with river cells - rivcell::Vector{T} | "-" - # Total transport capacity of overland flow [ton Δt⁻¹] - TCsed::Vector{T} | "t dt-1" - # Transport capacity of overland flow per particle class [ton Δt⁻¹] - TCclay::Vector{T} | "t dt-1" - TCsilt::Vector{T} | "t dt-1" - TCsand::Vector{T} | "t dt-1" - TCsagg::Vector{T} | "t dt-1" - TClagg::Vector{T} | "t dt-1" - - function LandSediment{T}(args...) where {T} - equal_size_vectors(args) - return new(args...) - end -end - -function initialize_landsed(nc, config, river, riverfrac, xl, yl, inds) - # Initialize parameters for the soil loss part - n = length(inds) - do_river = get(config.model, "runrivermodel", false)::Bool - # Reservoir / lake - do_reservoirs = get(config.model, "doreservoir", false)::Bool - do_lakes = get(config.model, "dolake", false)::Bool - # Rainfall erosion equation: ["answers", "eurosem"] - rainerosmethod = get(config.model, "rainerosmethod", "answers")::String - # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] - landtransportmethod = get(config.model, "landtransportmethod", "yalinpart")::String - - altitude = - ncread(nc, config, "vertical.altitude"; optional = false, sel = inds, type = Float) - canopyheight = ncread( - nc, - config, - "vertical.canopyheight"; - sel = inds, - defaults = 3.0, - type = Float, - ) - erosk = ncread(nc, config, "vertical.erosk"; sel = inds, defaults = 0.6, type = Float) - erosspl = - ncread(nc, config, "vertical.erosspl"; sel = inds, defaults = 2.0, type = Float) - erosov = ncread(nc, config, "vertical.erosov"; sel = inds, defaults = 0.9, type = Float) - pathfrac = - ncread(nc, config, "vertical.pathfrac"; sel = inds, defaults = 0.01, type = Float) - slope = ncread(nc, config, "vertical.slope"; sel = inds, defaults = 0.01, type = Float) - usleC = ncread(nc, config, "vertical.usleC"; sel = inds, defaults = 0.01, type = Float) - usleK = ncread(nc, config, "vertical.usleK"; sel = inds, defaults = 0.1, type = Float) - - cmax, _, canopygapfraction, sl, swood, kext = initialize_canopy(nc, config, inds) - - # Initialise parameters for the transport capacity part - clamp!(slope, 0.00001, Inf) - ldd_2d = ncread(nc, config, "ldd"; optional = false, allow_missing = true) - ldd = ldd_2d[inds] - - dmclay = ncread(nc, config, "vertical.dmclay"; sel = inds, defaults = 2.0, type = Float) - dmsilt = - ncread(nc, config, "vertical.dmsilt"; sel = inds, defaults = 10.0, type = Float) - dmsand = - ncread(nc, config, "vertical.dmsand"; sel = inds, defaults = 200.0, type = Float) - dmsagg = - ncread(nc, config, "vertical.dmsagg"; sel = inds, defaults = 30.0, type = Float) - dmlagg = - ncread(nc, config, "vertical.dmlagg"; sel = inds, defaults = 500.0, type = Float) - pclay = ncread(nc, config, "vertical.pclay"; sel = inds, defaults = 0.1, type = Float) - psilt = ncread(nc, config, "vertical.psilt"; sel = inds, defaults = 0.1, type = Float) - rhos = - ncread(nc, config, "vertical.rhosed"; sel = inds, defaults = 2650.0, type = Float) - - ### Initialize transport capacity variables ### - rivcell = float(river) - # Percent Sand - psand = 100 .- pclay .- psilt - # Govers coefficient for transport capacity - if landtransportmethod != "yalinpart" - # Calculation of D50 and fraction of fine and very fine sand (fvfs) from Fooladmand et al, 2006 - psand999 = psand .* ((999 - 25) / (1000 - 25)) - vd50 = log.((1 ./ (0.01 .* (pclay .+ psilt)) .- 1) ./ (1 ./ (0.01 .* pclay) .- 1)) - wd50 = - log.( - (1 ./ (0.01 .* (pclay .+ psilt .+ psand999)) .- 1) ./ - (1 ./ (0.01 .* pclay) .- 1), - ) - ad50 = 1 / log((25 - 1) / (999 - 1)) - bd50 = ad50 ./ log((25 - 1) / 1) - cd50 = ad50 .* log.(vd50 ./ wd50) - ud50 = (.-vd50) .^ (1 .- bd50) ./ ((.-wd50) .^ (.-bd50)) - D50 = 1 .+ (-1 ./ ud50 .* log.(1 ./ (1 ./ (0.01 .* pclay) .- 1))) .^ (1 ./ cd50) #[um] - D50 = D50 ./ 1000 # [mm] - else - D50 = fill(mv, n) - end - if landtransportmethod == "govers" - cGovers = ((D50 .* 1000 .+ 5) ./ 0.32) .^ (-0.6) - nGovers = ((D50 .* 1000 .+ 5) ./ 300) .^ (0.25) - else - cGovers = fill(mv, n) - nGovers = fill(mv, n) - end - if do_river || landtransportmethod == "yalinpart" - # Determine sediment size distribution, estimated from primary particle size distribution (Foster et al., 1980) - fclay = 0.20 .* pclay ./ 100 - 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 in 1:n - if pclay[i] > 50.0 - fsagg[i] = 0.57 - elseif pclay[i] < 25 - fsagg[i] = 2.0 * 0.01 * pclay[i] - end - end - flagg = 1.0 .- fclay .- fsilt .- fsand .- fsagg - else - fclay = fill(mv, n) - fsilt = fill(mv, n) - fsand = fill(mv, n) - fsagg = fill(mv, n) - flagg = fill(mv, n) - end - - # Reservoir and lakes - wbcover = zeros(Float, n) - if do_reservoirs - rescoverage_2d = ncread( - nc, - config, - "vertical.resareas"; - optional = false, - sel = inds, - type = Float, - fill = 0.0, - ) - wbcover = wbcover .+ rescoverage_2d - end - if do_lakes - lakecoverage_2d = ncread( - nc, - config, - "vertical.lakeareas"; - optional = false, - sel = inds, - type = Float, - fill = 0.0, - ) - wbcover = wbcover .+ lakecoverage_2d - end - - eros = LandSediment{Float}(; - n = n, - yl = yl, - xl = xl, - riverfrac = riverfrac, - wbcover = wbcover, - ### Soil erosion part ### - # Forcing - interception = fill(mv, n), - h_land = fill(mv, n), - precipitation = fill(mv, n), - q_land = fill(mv, n), - # Parameters - canopyheight = canopyheight, - canopygapfraction = canopygapfraction, - erosk = erosk, - erosspl = erosspl, - erosov = erosov, - pathfrac = pathfrac, - slope = slope, - usleC = usleC, - usleK = usleK, - # Interception related to climatology (leaf_area_index) - sl = sl, - swood = swood, - kext = kext, - leaf_area_index = fill(mv, n), - # Outputs - sedspl = fill(mv, n), - sedov = fill(mv, n), - soilloss = fill(mv, n), - erosclay = fill(mv, n), - erossilt = fill(mv, n), - erossand = fill(mv, n), - erossagg = fill(mv, n), - eroslagg = fill(mv, n), - ### Transport capacity part ### - # Parameters - dl = map(detdrainlength, ldd, xl, yl), - dw = map(detdrainwidth, ldd, xl, yl), - cGovers = cGovers, - D50 = D50, - dmclay = dmclay, - dmsilt = dmsilt, - dmsand = dmsand, - dmsagg = dmsagg, - dmlagg = dmlagg, - fclay = fclay, - fsilt = fsilt, - fsand = fsand, - fsagg = fsagg, - flagg = flagg, - nGovers = nGovers, - rhos = rhos, - rivcell = rivcell, - # Outputs - TCsed = fill(mv, n), - TCclay = fill(mv, n), - TCsilt = fill(mv, n), - TCsand = fill(mv, n), - TCsagg = fill(mv, n), - TClagg = fill(mv, n), - ) - - return eros -end - -# Soil erosion -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 in 1:(eros.n) - - ### Splash / Rainfall erosion ### - # ANSWERS method - if rainerosmethod == "answers" - # calculate rainfall intensity [mm/min] - rintnsty = eros.precipitation[i] / (ts / 60) - # splash erosion [kg/min] - sedspl = - 0.108 * eros.usleC[i] * eros.usleK[i] * eros.xl[i] * eros.yl[i] * rintnsty^2 - # [ton/timestep] - sedspl = sedspl * (ts / 60) * 1e-3 - end - # TODO check eurosem method output values (too high!) - if rainerosmethod == "eurosem" - if do_lai - cmax = eros.sl[i] * eros.leaf_area_index[i] + eros.swood[i] - canopygapfraction = exp(-eros.kext[i] * eros.leaf_area_index[i]) - end - # calculate rainfall intensity [mm/h] - rintnsty = eros.precipitation[i] / (ts / 3600) - # Kinetic energy of direct throughfall [J/m2/mm] - # kedir = max(11.87 + 8.73 * log10(max(0.0001, rintnsty)),0.0) #basis used in USLE - kedir = max(8.95 + 8.44 * log10(max(0.0001, rintnsty)), 0.0) #variant used in most distributed mdoels - # Kinetic energy of leaf drainage [J/m2/mm] - pheff = 0.5 * eros.canopyheight[i] - keleaf = max((15.8 * pheff^0.5) - 5.87, 0.0) - - #Depths of rainfall (total, leaf drianage, direct) [mm] - rdtot = eros.precipitation[i] - rdleaf = rdtot * 0.1 * canopygapfraction #stemflow - rddir = max(rdtot - rdleaf - eros.interception[i], 0.0) #throughfall - - #Total kinetic energy by rainfall [J/m2] - ketot = (rddir * kedir + rdleaf * keleaf) * 0.001 - # Rainfall / splash erosion [g/m2] - sedspl = eros.erosk[i] * ketot * exp(-eros.erosspl[i] * eros.h_land[i]) - sedspl = sedspl * eros.xl[i] * eros.yl[i] * 1e-6 # ton/cell - end - - # Remove the impervious area - sedspl = sedspl * (1.0 - eros.pathfrac[i]) - - ### Overland flow erosion ### - # ANWERS method - # Overland flow rate [m2/min] - qr_land = eros.q_land[i] * 60 / ((eros.xl[i] + eros.yl[i]) / 2) - # Sine of the slope - sinslope = sin(atan(eros.slope[i])) - - # Overland flow erosion [kg/min] - # For a wide range of slope, it is better to use the sine of slope rather than tangeant - sedov = - eros.erosov[i] * - eros.usleC[i] * - eros.usleK[i] * - eros.xl[i] * - eros.yl[i] * - sinslope * - qr_land - # [ton/timestep] - sedov = sedov * (ts / 60) * 1e-3 - # Remove the impervious area - sedov = sedov * (1.0 - eros.pathfrac[i]) - - # Assume no erosion in reservoir/lake areas - if eros.wbcover[i] > 0 - sedspl = 0.0 - sedov = 0.0 - end - - # Total soil loss [ton/cell/timestep] - soilloss = sedspl + sedov - - # update the outputs and states - eros.sedspl[i] = sedspl - eros.sedov[i] = sedov - eros.soilloss[i] = soilloss - # Eroded amount per particle class - eros.erosclay[i] = soilloss * eros.fclay[i] - eros.erossilt[i] = soilloss * eros.fsilt[i] - eros.erossand[i] = soilloss * eros.fsand[i] - 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) - 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 in 1:(ols.n) - sinslope = sin(atan(ols.slope[i])) - - if !do_river - # Total transport capacity without particle differentiation - if tcmethod == "govers" - TC = tc_govers(ols, i, sinslope, ts) - end - - if tcmethod == "yalin" - TC = tc_yalin(ols, i, sinslope, ts) - end - - # Filter TC land for river cells (0 in order for sediment from land to stop when entering the river) - if ols.rivcell[i] == 1.0 - TC = 0.0 - end - - # Set particle TC to 0 - TCclay = 0.0 - TCsilt = 0.0 - TCsand = 0.0 - TCsagg = 0.0 - TClagg = 0.0 - end - - if do_river || tcmethod == "yalinpart" - TC, TCclay, TCsilt, TCsand, TCsagg, TClagg = tc_yalinpart(ols, i, sinslope, ts) - end - - # update the outputs and states - ols.TCsed[i] = TC - ols.TCclay[i] = TCclay - ols.TCsilt[i] = TCsilt - ols.TCsand[i] = TCsand - ols.TCsagg[i] = TCsagg - ols.TClagg[i] = TClagg - end - return nothing -end - -function tc_govers(ols::LandSediment, i::Int, sinslope::Float, ts::Float)::Float - # Transport capacity from govers 1990 - # Unit stream power - if ols.h_land[i] > 0.0 - velocity = ols.q_land[i] / (ols.dw[i] * ols.h_land[i]) - else - velocity = 0.0 - end - omega = 10 * sinslope * 100 * velocity #cm/s - if omega > 0.4 - TCf = ols.cGovers[i] * (omega - 0.4)^(ols.nGovers[i]) * 2650 #kg/m3 - else - TCf = 0.0 - end - TC = TCf * ols.q_land[i] * ts * 1e-3 #[ton/cell] - - return TC -end - -function tc_yalin(ols::LandSediment, i::Int, sinslope::Float, ts::Float)::Float - # Transport capacity from Yalin without particle differentiation - delta = max( - ( - ols.h_land[i] * sinslope / (ols.D50[i] * 0.001 * (ols.rhos[i] / 1000 - 1)) / - 0.06 - 1 - ), - 0.0, - ) - alphay = delta * 2.45 / (0.001 * ols.rhos[i])^0.4 * 0.06^(0.5) - if ols.q_land[i] > 0.0 && alphay != 0.0 - TC = ( - ols.dw[i] / ols.q_land[i] * - (ols.rhos[i] - 1000) * - ols.D50[i] * - 0.001 * - (9.81 * ols.h_land[i] * sinslope) * - 0.635 * - delta * - (1 - log(1 + alphay) / (alphay)) - ) # [kg/m3] - TC = TC * ols.q_land[i] * ts * 1e-3 #[ton] - else - TC = 0.0 - end - return TC -end - -function tc_yalinpart(ols::LandSediment, i::Int, sinslope::Float, ts::Float) - # Transport capacity from Yalin with particle differentiation - # Delta parameter of Yalin for each particle class - delta = ols.h_land[i] * sinslope / (1e-6 * (ols.rhos[i] / 1000 - 1)) / 0.06 - dclay = max(1 / ols.dmclay[i] * delta - 1, 0.0) - dsilt = max(1 / ols.dmsilt[i] * delta - 1, 0.0) - dsand = max(1 / ols.dmsand[i] * delta - 1, 0.0) - dsagg = max(1 / ols.dmsagg[i] * delta - 1, 0.0) - dlagg = max(1 / ols.dmlagg[i] * delta - 1, 0.0) - # Total transportability - dtot = dclay + dsilt + dsand + dsagg + dlagg - - # Yalin transport capacity of overland flow for each particle class - if ols.q_land[i] > 0.0 - TCa = - ols.dw[i] / ols.q_land[i] * - (ols.rhos[i] - 1000) * - 1e-6 * - (9.81 * ols.h_land[i] * sinslope) - else - TCa = 0.0 - end - TCb = 2.45 / (ols.rhos[i] / 1000)^0.4 * 0.06^0.5 - if dtot != 0.0 && dclay != 0.0 - TCclay = - TCa * ols.dmclay[i] * dclay / dtot * - 0.635 * - dclay * - (1 - log(1 + dclay * TCb) / dclay * TCb) # [kg/m3] - TCclay = TCclay * ols.q_land[i] * ts * 1e-3 # [ton] - else - TCclay = 0.0 - end - if dtot != 0.0 && dsilt != 0.0 - TCsilt = - TCa * ols.dmsilt[i] * dsilt / dtot * - 0.635 * - dsilt * - (1 - log(1 + dsilt * TCb) / dsilt * TCb) # [kg/m3] - TCsilt = TCsilt * ols.q_land[i] * ts * 1e-3 # [ton] - else - TCsilt = 0.0 - end - if dtot != 0.0 && dsand != 0.0 - TCsand = - TCa * ols.dmsand[i] * dsand / dtot * - 0.635 * - dsand * - (1 - log(1 + dsand * TCb) / dsand * TCb) # [kg/m3] - TCsand = TCsand * ols.q_land[i] * ts * 1e-3 # [ton] - else - TCsand = 0.0 - end - if dtot != 0.0 && dsagg != 0.0 - TCsagg = - TCa * ols.dmsagg[i] * dsagg / dtot * - 0.635 * - dsagg * - (1 - log(1 + dsagg * TCb) / dsagg * TCb) # [kg/m3] - TCsagg = TCsagg * ols.q_land[i] * ts * 1e-3 # [ton] - else - TCsagg = 0.0 - end - if dtot != 0.0 && dlagg != 0.0 - TClagg = - TCa * ols.dmlagg[i] * dlagg / dtot * - 0.635 * - dlagg * - (1 - log(1 + dlagg * TCb) / dlagg * TCb) # [kg/m3] - TClagg = TClagg * ols.q_land[i] * ts * 1e-3 # [ton] - else - TClagg = 0.0 - end - - # Assume that ols all reach the river in reservoir/lake areas (very high TC) - if ols.wbcover[i] > 0 - TC = 1e9 - TCclay = 1e9 - TCsilt = 1e9 - TCsand = 1e9 - TCsagg = 1e9 - TClagg = 1e9 - end - - # Filter TC land for river cells (0 in order for sediment from land to stop when entering the river) - if ols.rivcell[i] == 1.0 - TCclay = 0.0 - TCsilt = 0.0 - TCsand = 0.0 - TCsagg = 0.0 - TClagg = 0.0 - end - - # Set total TC to 0 - TC = 0.0 - - return TC, TCclay, TCsilt, TCsand, TCsagg, TClagg -end - -### Sediment transport in overland flow ### -@get_units @grid_loc @with_kw struct OverlandFlowSediment{T} - # number of cells - n::Int | "-" | "none" - # Filter with river cells - rivcell::Vector{T} | "-" - # Total eroded soil [ton Δt⁻¹] - soilloss::Vector{T} | "t dt-1" - # Eroded soil per particle class [ton Δt⁻¹] - erosclay::Vector{T} | "t dt-1" - erossilt::Vector{T} | "t dt-1" - erossand::Vector{T} | "t dt-1" - erossagg::Vector{T} | "t dt-1" - eroslagg::Vector{T} | "t dt-1" - # Total transport capacity of overland flow [ton Δt⁻¹] - TCsed::Vector{T} | "t dt-1" - # Transport capacity of overland flow per particle class [ton Δt⁻¹] - TCclay::Vector{T} | "t dt-1" - TCsilt::Vector{T} | "t dt-1" - TCsand::Vector{T} | "t dt-1" - TCsagg::Vector{T} | "t dt-1" - TClagg::Vector{T} | "t dt-1" - # Sediment flux in overland flow [ton dt-1] - olsed::Vector{T} | "t dt-1" - olclay::Vector{T} | "t dt-1" - olsilt::Vector{T} | "t dt-1" - olsand::Vector{T} | "t dt-1" - olsagg::Vector{T} | "t dt-1" - ollagg::Vector{T} | "t dt-1" - # Sediment reaching the river with overland flow [ton dt-1] - inlandsed::Vector{T} | "t dt-1" - inlandclay::Vector{T} | "t dt-1" - inlandsilt::Vector{T} | "t dt-1" - inlandsand::Vector{T} | "t dt-1" - 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) - inland[i] = rivcell[i] == 1 ? eroded[i] : no_erosion - end - return inland -end - -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) - - # transport sediment down the network - if do_river || tcmethod == "yalinpart" - # clay - accucapacityflux!(ols.olclay, ols.erosclay, network, ols.TCclay) - partial_update!(ols.inlandclay, ols.rivcell, ols.erosclay) - # silt - accucapacityflux!(ols.olsilt, ols.erossilt, network, ols.TCsilt) - partial_update!(ols.inlandsilt, ols.rivcell, ols.erossilt) - # sand - accucapacityflux!(ols.olsand, ols.erossand, network, ols.TCsand) - partial_update!(ols.inlandsand, ols.rivcell, ols.erossand) - # small aggregates - accucapacityflux!(ols.olsagg, ols.erossagg, network, ols.TCsagg) - partial_update!(ols.inlandsagg, ols.rivcell, ols.erossagg) - # large aggregates - accucapacityflux!(ols.ollagg, ols.eroslagg, network, ols.TClagg) - partial_update!(ols.inlandlagg, ols.rivcell, ols.eroslagg) - - # total sediment, all particle classes - ols.olsed .= ols.olclay .+ ols.olsilt .+ ols.olsand .+ ols.olsagg .+ ols.ollagg - ols.inlandsed .= - ols.inlandclay .+ ols.inlandsilt .+ ols.inlandsand .+ ols.inlandsagg .+ - ols.inlandlagg - else - 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 @grid_loc @with_kw struct RiverSediment{T} # number of cells diff --git a/src/sediment_flux.jl b/src/sediment_flux.jl new file mode 100644 index 000000000..fae0016b3 --- /dev/null +++ b/src/sediment_flux.jl @@ -0,0 +1,77 @@ +@get_units @with_kw struct OverlandFlowSediment{TT, SF, TR, T} + hydrometeo_forcing::HydrometeoForcing | "-" + transport_capacity::TT | "-" + sediment_flux::SF | "-" + to_river::TR | "-" + width::Vector{T} | "m" + waterbodies::Vector{Bool} | "-" + rivers::Vector{Bool} | "-" +end + +function initialize_overland_flow_sediment(nc, config, inds, width, waterbodies, rivers) + n = length(inds) + hydrometeo_forcing = initialize_hydrometeo_forcing(n) + # Check what transport capacity equation will be used + do_river = get(config.model, "runrivermodel", false)::Bool + # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] + landtransportmethod = get(config.model, "landtransportmethod", "yalinpart")::String + + if do_river || landtransportmethod == "yalinpart" + transport_capacity_model = + initialize_transport_capacity_yalin_diff_model(nc, config, inds) + elseif landtransportmethod == "govers" + transport_capacity_model = + initialize_transport_capacity_govers_model(nc, config, inds) + elseif landtransportmethod == "yalin" + transport_capacity_model = + initialize_transport_capacity_yalin_model(nc, config, inds) + else + error("Unknown land transport method: $landtransportmethod") + end + + if do_river || landtransportmethod == "yalinpart" + sediment_flux_model = initialize_sediment_land_transport_differentiation_model(inds) + to_river_model = initialize_sediment_to_river_differentiation_model(inds) + else + sediment_flux_model = initialize_sediment_land_transport_model(inds) + to_river_model = initialize_sediment_to_river_model(inds) + end + + overland_flow_sediment = OverlandFlowSediment{ + typeof(transport_capacity_model), + typeof(sediment_flux_model), + typeof(to_river_model), + Float, + }(; + hydrometeo_forcing = hydrometeo_forcing, + transport_capacity = transport_capacity_model, + sediment_flux = sediment_flux_model, + to_river = to_river_model, + width = width, + waterbodies = waterbodies, + rivers = rivers, + ) + return overland_flow_sediment +end + +function update!(model::OverlandFlowSediment, erosion_model::SoilErosionModel, network, dt) + # Convert dt to integer + ts = tosecond(dt) + # Update the boundary conditions of transport capacity + (; q, waterlevel) = model.transport_capacity.boundary_conditions + (; q_land, waterlevel_land) = model.hydrometeo_forcing + @. q = q_land + @. waterlevel = waterlevel_land + # Transport capacity + update!(model.transport_capacity, model.width, model.waterbodies, model.rivers, ts) + + # Update boundary conditions before transport + update_bc(model.sediment_flux, erosion_model, model.transport_capacity) + # Compute transport + update!(model.sediment_flux, network) + + # Update boundary conditions before computing sediment reaching the river + update_bc(model.to_river, model.sediment_flux) + # Compute sediment reaching the river + update!(model.to_river, model.rivers) +end \ No newline at end of file diff --git a/src/sediment_model.jl b/src/sediment_model.jl index b44f2604a..81cdaf6bd 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -13,9 +13,6 @@ function initialize_sediment_model(config::Config) reader = prepare_reader(config) clock = Clock(config, reader) - - do_river = get(config.model, "runrivermodel", false)::Bool - nc = NCDataset(static_path) subcatch_2d = ncread(nc, config, "subcatchment"; optional = false, allow_missing = true) @@ -47,7 +44,6 @@ function initialize_sediment_model(config::Config) sizeinmetres = get(config.model, "sizeinmetres", false)::Bool xl, yl = cell_lengths(y, cellength, sizeinmetres) - riverfrac = river_fraction(river, riverlength, riverwidth, xl, yl) area = xl .* yl landslope = ncread(nc, config, "vertical.slope"; optional = false, sel = inds, type = Float) @@ -55,49 +51,51 @@ function initialize_sediment_model(config::Config) soilloss = initialize_soil_loss(nc, config, inds, area, landslope) + # Get waterbodies mask + do_reservoirs = get(config.model, "reservoirs", false)::Bool + do_lakes = get(config.model, "lakes", false)::Bool + waterbodies = fill(0.0, n) + if do_reservoirs + reservoirs = ncread( + nc, + config, + "reservoir_areas"; + optional = false, + sel = inds, + type = Float, + fill = 0, + ) + waterbodies = waterbodies .+ reservoirs + end + if do_lakes + lakes = ncread( + nc, + config, + "lake_areas"; + optional = false, + sel = inds, + type = Float, + fill = 0, + ) + waterbodies = waterbodies .+ lakes + end + waterbodies = waterbodies .> 0 + ldd_2d = ncread(nc, config, "ldd"; optional = false, allow_missing = true) ldd = ldd_2d[inds] + drain_width = map(detdrainwidth, ldd, xl, yl) + # # lateral part sediment in overland flow - rivcell = float(river) - ols = OverlandFlowSediment{Float}(; - n = n, - rivcell = rivcell, - soilloss = fill(mv, n), - erosclay = fill(mv, n), - erossilt = fill(mv, n), - erossand = fill(mv, n), - erossagg = fill(mv, n), - eroslagg = fill(mv, n), - TCsed = fill(mv, n), - TCclay = fill(mv, n), - TCsilt = fill(mv, n), - TCsand = fill(mv, n), - TCsagg = fill(mv, n), - TClagg = fill(mv, n), - olsed = fill(mv, n), - olclay = fill(mv, n), - olsilt = fill(mv, n), - olsand = fill(mv, n), - olsagg = fill(mv, n), - ollagg = fill(mv, n), - inlandsed = fill(mv, n), - inlandclay = fill(mv, n), - inlandsilt = fill(mv, n), - inlandsand = fill(mv, n), - inlandsagg = fill(mv, n), - inlandlagg = fill(mv, n), - ) + overland_flow_sediment = + initialize_overland_flow_sediment(nc, config, inds, drain_width, waterbodies, river) graph = flowgraph(ldd, inds, pcr_dir) # River processes + do_river = get(config.model, "runrivermodel", false)::Bool # the indices of the river cells in the land(+river) cell vector - landslope = - ncread(nc, config, "lateral.land.slope"; optional = false, sel = inds, type = Float) - clamp!(landslope, 0.00001, Inf) - riverlength = riverlength_2d[inds_riv] riverwidth = riverwidth_2d[inds_riv] minimum(riverlength) > 0 || error("river length must be positive on river cells") @@ -111,7 +109,7 @@ function initialize_sediment_model(config::Config) rs = initialize_riversed(nc, config, riverwidth, riverlength, inds_riv) - modelmap = (vertical = soilloss, lateral = (land = ols, river = rs)) + modelmap = (vertical = soilloss, lateral = (land = overland_flow_sediment, river = rs)) indices_reverse = ( land = rev_inds, river = rev_inds_riv, @@ -139,7 +137,7 @@ function initialize_sediment_model(config::Config) model = Model( config, (; land, river, reservoir, lake, index_river, frac_toriver), - (land = ols, river = rs), + (land = overland_flow_sediment, river = rs), soilloss, clock, reader, @@ -159,23 +157,9 @@ function update!(model::Model{N, L, V, R, W, T}) where {N, L, V, R, W, T <: Sedi # Soil erosion update!(vertical, dt) - # update_until_oltransport(vertical, config) - - # lateral.land.soilloss .= vertical.soilloss - # lateral.land.erosclay .= vertical.erosclay - # lateral.land.erossilt .= vertical.erossilt - # lateral.land.erossand .= vertical.erossand - # lateral.land.erossagg .= vertical.erossagg - # lateral.land.eroslagg .= vertical.eroslagg - - # lateral.land.TCsed .= vertical.TCsed - # lateral.land.TCclay .= vertical.TCclay - # lateral.land.TCsilt .= vertical.TCsilt - # lateral.land.TCsand .= vertical.TCsand - # lateral.land.TCsagg .= vertical.TCsagg - # lateral.land.TClagg .= vertical.TClagg - - # update(lateral.land, network.land, config) + + # Overland flow sediment transport + update!(lateral.land, vertical.soil_erosion, network.land, dt) # do_river = get(config.model, "runrivermodel", false)::Bool diff --git a/src/sediment_transport/land_to_river.jl b/src/sediment_transport/land_to_river.jl new file mode 100644 index 000000000..5457295e3 --- /dev/null +++ b/src/sediment_transport/land_to_river.jl @@ -0,0 +1,155 @@ +abstract type AbstractSedimentToRiverModel end + +## Total sediment transport in overland flow structs and functions +@get_units @with_kw struct SedimentToRiverVars{T} + # Total sediment reaching the river + amount::Vector{T} | "t dt-1" +end + +function sediment_to_river_vars(n) + vars = SedimentToRiverVars(; amount = fill(mv, n)) + return vars +end + +@get_units @with_kw struct SedimentToRiverBC{T} + # Deposited material + deposition::Vector{T} | "t dt-1" +end + +function sediment_to_river_bc(n) + bc = SedimentToRiverBC(; deposition = fill(mv, n)) + return bc +end + +@get_units @with_kw struct SedimentToRiverModel{T} <: AbstractSedimentToRiverModel + boundary_conditions::SedimentToRiverBC{T} | "-" + variables::SedimentToRiverVars{T} | "-" +end + +function initialize_sediment_to_river_model(inds) + n = length(inds) + vars = sediment_to_river_vars(n) + bc = sediment_to_river_bc(n) + model = SedimentToRiverModel(; boundary_conditions = bc, variables = vars) + return model +end + +function update_bc(model::SedimentToRiverModel, transport_model::SedimentLandTransportModel) + (; deposition) = model.boundary_conditions + (; amount) = transport_model.variables + @. deposition = amount +end + +function update!(model::SedimentToRiverModel, rivers) + (; deposition) = model.boundary_conditions + (; amount) = model.variables + + zeros = fill(0.0, length(amount)) + amount .= ifelse.(rivers, deposition, zeros) +end + +## Different particles reaching the river structs and functions +@get_units @with_kw struct SedimentToRiverDifferentiationVars{T} + # Total sediment flux + amount::Vector{T} | "t dt-1" + # Clay flux + clay::Vector{T} | "t dt-1" + # Silt + silt::Vector{T} | "t dt-1" + # Sand flux + sand::Vector{T} | "t dt-1" + # Small aggregates flux + sagg::Vector{T} | "t dt-1" + # Large aggregates flux + lagg::Vector{T} | "t dt-1" +end + +function sediment_to_river_differentiation_vars(n) + vars = SedimentToRiverDifferentiationVars(; + amount = fill(mv, n), + clay = fill(mv, n), + silt = fill(mv, n), + sand = fill(mv, n), + sagg = fill(mv, n), + lagg = fill(mv, n), + ) + return vars +end + +@get_units @with_kw struct SedimentToRiverDifferentiationBC{T} + # Deposited clay + deposition_clay::Vector{T} | "t dt-1" + # Deposited silt + deposition_silt::Vector{T} | "t dt-1" + # Deposited sand + deposition_sand::Vector{T} | "t dt-1" + # Deposited small aggregates + deposition_sagg::Vector{T} | "t dt-1" + # Deposited large aggregates + deposition_lagg::Vector{T} | "t dt-1" +end + +function sediment_to_river_differentiation_bc(n) + bc = SedimentToRiverDifferentiationBC(; + deposition_clay = fill(mv, n), + deposition_silt = fill(mv, n), + deposition_sand = fill(mv, n), + deposition_sagg = fill(mv, n), + deposition_lagg = fill(mv, n), + ) + return bc +end + +@get_units @with_kw struct SedimentToRiverDifferentiationModel{T} <: + AbstractSedimentToRiverModel + boundary_conditions::SedimentToRiverDifferentiationBC{T} | "-" + variables::SedimentToRiverDifferentiationVars{T} | "-" +end + +function initialize_sediment_to_river_differentiation_model(inds) + n = length(inds) + vars = sediment_to_river_differentiation_vars(n) + bc = sediment_to_river_differentiation_bc(n) + model = + SedimentToRiverDifferentiationModel(; boundary_conditions = bc, variables = vars) + return model +end + +function update_bc( + model::SedimentToRiverDifferentiationModel, + transport_model::SedimentLandTransportDifferentiationModel, +) + (; + deposition_clay, + deposition_silt, + deposition_sand, + deposition_sagg, + deposition_lagg, + ) = model.boundary_conditions + (; clay, silt, sand, sagg, lagg) = transport_model.variables + @. deposition_clay = clay + @. deposition_silt = silt + @. deposition_sand = sand + @. deposition_sagg = sagg + @. deposition_lagg = lagg +end + +function update!(model::SedimentToRiverDifferentiationModel, rivers) + (; + deposition_clay, + deposition_silt, + deposition_sand, + deposition_sagg, + deposition_lagg, + ) = model.boundary_conditions + (; amount, clay, silt, sand, sagg, lagg) = model.variables + + zeros = fill(0.0, length(amount)) + clay .= ifelse.(rivers, deposition_clay, zeros) + silt .= ifelse.(rivers, deposition_silt, zeros) + sand .= ifelse.(rivers, deposition_sand, zeros) + sagg .= ifelse.(rivers, deposition_sagg, zeros) + lagg .= ifelse.(rivers, deposition_lagg, zeros) + + amount .= clay .+ silt .+ sand .+ sagg .+ lagg +end diff --git a/src/sediment_transport/overland_flow_transport.jl b/src/sediment_transport/overland_flow_transport.jl new file mode 100644 index 000000000..94d733c8e --- /dev/null +++ b/src/sediment_transport/overland_flow_transport.jl @@ -0,0 +1,197 @@ +abstract type AbstractSedimentLandTransportModel end + +## Total sediment transport in overland flow structs and functions +@get_units @with_kw struct SedimentLandTransportVars{T} + # Total sediment reaching the river + amount::Vector{T} | "t dt-1" +end + +function sediment_land_transport_vars(n) + vars = SedimentLandTransportVars(; amount = fill(mv, n)) + return vars +end + +@get_units @with_kw struct SedimentLandTransportBC{T} + # Eroded material + erosion::Vector{T} | "t dt-1" + # Transport capacity + transport_capacity::Vector{T} | "t dt-1" +end + +function sediment_land_transport_bc(n) + bc = SedimentLandTransportBC(; erosion = fill(mv, n), transport_capacity = fill(mv, n)) + return bc +end + +@get_units @with_kw struct SedimentLandTransportModel{T} <: + AbstractSedimentLandTransportModel + boundary_conditions::SedimentLandTransportBC{T} | "-" + variables::SedimentLandTransportVars{T} | "-" +end + +function initialize_sediment_land_transport_model(inds) + n = length(inds) + vars = sediment_land_transport_vars(n) + bc = sediment_land_transport_bc(n) + model = SedimentLandTransportModel(; boundary_conditions = bc, variables = vars) + return model +end + +function update_bc( + model::SedimentLandTransportModel, + erosion_model::SoilErosionModel, + transport_capacity_model::AbstractTransportCapacityModel, +) + (; erosion, transport_capacity) = model.boundary_conditions + (; amount) = erosion_model.variables + @. erosion = amount + + (; amount) = transport_capacity_model.variables + @. transport_capacity = amount +end + +function update!(model::SedimentLandTransportModel, network) + (; erosion, transport_capacity) = model.boundary_conditions + (; amount) = model.variables + + accucapacityflux!(amount, erosion, network, transport_capacity) +end + +## Total transport capacity with particle differentiation structs and functions +@get_units @with_kw struct SedimentLandTransportDifferentiationVars{T} + # Total sediment flux + amount::Vector{T} | "t dt-1" + # Clay flux + clay::Vector{T} | "t dt-1" + # Silt + silt::Vector{T} | "t dt-1" + # Sand flux + sand::Vector{T} | "t dt-1" + # Small aggregates flux + sagg::Vector{T} | "t dt-1" + # Large aggregates flux + lagg::Vector{T} | "t dt-1" +end + +function sediment_land_transport_differentiation_vars(n) + vars = SedimentLandTransportDifferentiationVars(; + amount = fill(mv, n), + clay = fill(mv, n), + silt = fill(mv, n), + sand = fill(mv, n), + sagg = fill(mv, n), + lagg = fill(mv, n), + ) + return vars +end + +@get_units @with_kw struct SedimentLandTransportDifferentiationBC{T} + # Eroded clay + erosion_clay::Vector{T} | "t dt-1" + # Eroded silt + erosion_silt::Vector{T} | "t dt-1" + # Eroded sand + erosion_sand::Vector{T} | "t dt-1" + # Eroded small aggregates + erosion_sagg::Vector{T} | "t dt-1" + # Eroded large aggregates + erosion_lagg::Vector{T} | "t dt-1" + # Transport capacity clay + transport_capacity_clay::Vector{T} | "t dt-1" + # Transport capacity silt + transport_capacity_silt::Vector{T} | "t dt-1" + # Transport capacity sand + transport_capacity_sand::Vector{T} | "t dt-1" + # Transport capacity small aggregates + transport_capacity_sagg::Vector{T} | "t dt-1" + # Transport capacity large aggregates + transport_capacity_lagg::Vector{T} | "t dt-1" +end + +function sediment_land_transport_differentiation_bc(n) + bc = SedimentLandTransportDifferentiationBC(; + erosion_clay = fill(mv, n), + erosion_silt = fill(mv, n), + erosion_sand = fill(mv, n), + erosion_sagg = fill(mv, n), + erosion_lagg = fill(mv, n), + transport_capacity_clay = fill(mv, n), + transport_capacity_silt = fill(mv, n), + transport_capacity_sand = fill(mv, n), + transport_capacity_sagg = fill(mv, n), + transport_capacity_lagg = fill(mv, n), + ) + return bc +end + +@get_units @with_kw struct SedimentLandTransportDifferentiationModel{T} <: + AbstractSedimentLandTransportModel + boundary_conditions::SedimentLandTransportDifferentiationBC{T} | "-" + variables::SedimentLandTransportDifferentiationVars{T} | "-" +end + +function initialize_sediment_land_transport_differentiation_model(inds) + n = length(inds) + vars = sediment_land_transport_differentiation_vars(n) + bc = sediment_land_transport_differentiation_bc(n) + model = SedimentLandTransportDifferentiationModel(; + boundary_conditions = bc, + variables = vars, + ) + return model +end + +function update_bc( + model::SedimentLandTransportDifferentiationModel, + erosion_model::SoilErosionModel, + transport_capacity_model::TransportCapacityYalinDifferentiationModel, +) + (; + erosion_clay, + erosion_silt, + erosion_sand, + erosion_sagg, + erosion_lagg, + transport_capacity_clay, + transport_capacity_silt, + transport_capacity_sand, + transport_capacity_sagg, + transport_capacity_lagg, + ) = model.boundary_conditions + (; clay, silt, sand, sagg, lagg) = erosion_model.variables + @. erosion_clay = clay + @. erosion_silt = silt + @. erosion_sand = sand + @. erosion_sagg = sagg + @. erosion_lagg = lagg + + (; clay, silt, sand, sagg, lagg) = transport_capacity_model.variables + @. transport_capacity_clay = clay + @. transport_capacity_silt = silt + @. transport_capacity_sand = sand + @. transport_capacity_sagg = sagg + @. transport_capacity_lagg = lagg +end + +function update!(model::SedimentLandTransportDifferentiationModel, network) + (; + erosion_clay, + erosion_silt, + erosion_sand, + erosion_sagg, + erosion_lagg, + transport_capacity_clay, + transport_capacity_silt, + transport_capacity_sand, + transport_capacity_sagg, + transport_capacity_lagg, + ) = model.boundary_conditions + (; amount, clay, silt, sand, sagg, lagg) = model.variables + + accucapacityflux!(clay, erosion_clay, network, transport_capacity_clay) + accucapacityflux!(silt, erosion_silt, network, transport_capacity_silt) + accucapacityflux!(sand, erosion_sand, network, transport_capacity_sand) + accucapacityflux!(sagg, erosion_sagg, network, transport_capacity_sagg) + accucapacityflux!(lagg, erosion_lagg, network, transport_capacity_lagg) + amount .= clay .+ silt .+ sand .+ sagg .+ lagg +end \ No newline at end of file diff --git a/src/sediment_transport/transport_capacity.jl b/src/sediment_transport/transport_capacity.jl new file mode 100644 index 000000000..c3fbe41cc --- /dev/null +++ b/src/sediment_transport/transport_capacity.jl @@ -0,0 +1,424 @@ +abstract type AbstractTransportCapacityModel end + +## Total sediment transport capacity structs and functions +@get_units @with_kw struct TransportCapacityModelVars{T} + # Total sediment transport capacity + amount::Vector{T} | "t dt-1" +end + +function transport_capacity_model_vars(n) + vars = TransportCapacityModelVars(; amount = fill(mv, n)) + return vars +end + +@get_units @with_kw struct TransportCapacityBC{T} + # Discharge + q::Vector{T} | "m3 s-1" + # Flow depth + waterlevel::Vector{T} | "m" +end + +function transport_capacity_bc(n) + bc = TransportCapacityBC(; q = fill(mv, n), waterlevel = fill(mv, n)) + return bc +end + +# Common parameters for transport capacity models +@get_units @with_kw struct TransportCapacityGoversParameters{T} + # Drain slope + slope::Vector{T} | "m m-1" + # Particle density + density::Vector{T} | "kg m-3" + # Govers transport capacity coefficient + c_govers::Vector{T} | "-" + # Govers transport capacity exponent + n_govers::Vector{T} | "-" +end + +function initialize_transport_capacity_govers_params(nc, config, inds) + slope = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.slope"; + sel = inds, + defaults = 0.01, + type = Float, + ) + density = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.density"; + sel = inds, + defaults = 2650.0, + type = Float, + ) + c_govers = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.c_govers"; + sel = inds, + defaults = 0.000505, + type = Float, + ) + n_govers = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.n_govers"; + sel = inds, + defaults = 4.27, + type = Float, + ) + tc_parameters = TransportCapacityGoversParameters(; + slope = slope, + density = density, + c_govers = c_govers, + n_govers = n_govers, + ) + + return tc_parameters +end + +@get_units @with_kw struct TransportCapacityGoversModel{T} <: AbstractTransportCapacityModel + boundary_conditions::TransportCapacityBC{T} | "-" + parameters::TransportCapacityGoversParameters{T} | "-" + variables::TransportCapacityModelVars{T} | "-" +end + +function initialize_transport_capacity_govers_model(nc, config, inds) + n = length(inds) + vars = transport_capacity_model_vars(n) + params = initialize_transport_capacity_govers_params(nc, config, inds) + bc = transport_capacity_bc(n) + model = TransportCapacityGoversModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::TransportCapacityGoversModel, width, waterbodies, rivers, ts) + (; q, waterlevel) = model.boundary_conditions + (; slope, density, c_govers, n_govers) = model.parameters + (; amount) = model.variables + + n = length(q) + threaded_foreach(1:n; basesize = 1000) do i + amount[i] = transport_capacity_govers( + q[i], + waterlevel[i], + c_govers[i], + n_govers[i], + density[i], + slope[i], + width[i], + waterbodies[i], + rivers[i], + ts, + ) + end +end + +# Common parameters for transport capacity models +@get_units @with_kw struct TransportCapacityYalinParameters{T} + # Drain slope + slope::Vector{T} | "m m-1" + # Particle density + density::Vector{T} | "kg m-3" + # Particle mean diameter + d50::Vector{T} | "mm" +end + +function initialize_transport_capacity_yalin_params(nc, config, inds) + slope = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.slope"; + sel = inds, + defaults = 0.01, + type = Float, + ) + density = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.density"; + sel = inds, + defaults = 2650.0, + type = Float, + ) + d50 = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.d50"; + sel = inds, + defaults = 0.1, + type = Float, + ) + tc_parameters = + TransportCapacityYalinParameters(; slope = slope, density = density, d50 = d50) + + return tc_parameters +end + +@get_units @with_kw struct TransportCapacityYalinModel{T} <: AbstractTransportCapacityModel + boundary_conditions::TransportCapacityBC{T} | "-" + parameters::TransportCapacityYalinParameters{T} | "-" + variables::TransportCapacityModelVars{T} | "-" +end + +function initialize_transport_capacity_yalin_model(nc, config, inds) + n = length(inds) + vars = transport_capacity_model_vars(n) + params = initialize_transport_capacity_yalin_params(nc, config, inds) + bc = transport_capacity_bc(n) + model = TransportCapacityYalinModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::TransportCapacityYalinModel, width, waterbodies, rivers, ts) + (; q, waterlevel) = model.boundary_conditions + (; slope, density, d50) = model.parameters + (; amount) = model.variables + + n = length(q) + threaded_foreach(1:n; basesize = 1000) do i + amount[i] = transport_capacity_yalin( + q[i], + waterlevel[i], + density[i], + d50[i], + slope[i], + width[i], + waterbodies[i], + rivers[i], + ts, + ) + end +end + +## Total transport capacity with particle differentiation structs and functions +@get_units @with_kw struct TransportCapacityYalinDifferentiationModelVars{T} + # Total sediment transport capacity + amount::Vector{T} | "t dt-1" + # Transport capacity clay + clay::Vector{T} | "t dt-1" + # Transport capacity silt + silt::Vector{T} | "t dt-1" + # Transport capacity sand + sand::Vector{T} | "t dt-1" + # Transport capacity small aggregates + sagg::Vector{T} | "t dt-1" + # Transport capacity large aggregates + lagg::Vector{T} | "t dt-1" +end + +function transport_capacity_yalin_differentiation_model_vars(n) + vars = TransportCapacityYalinDifferentiationModelVars(; + amount = fill(mv, n), + clay = fill(mv, n), + silt = fill(mv, n), + sand = fill(mv, n), + sagg = fill(mv, n), + lagg = fill(mv, n), + ) + return vars +end + +# Common parameters for transport capacity models +@get_units @with_kw struct TransportCapacityYalinDifferentiationParameters{T} + # Drain slope + slope::Vector{T} | "m m-1" + # Particle density + density::Vector{T} | "kg m-3" + # Clay mean diameter + dm_clay::Vector{T} | "µm" + # Silt mean diameter + dm_silt::Vector{T} | "µm" + # Sand mean diameter + dm_sand::Vector{T} | "µm" + # Small aggregates mean diameter + dm_sagg::Vector{T} | "µm" + # Large aggregates mean diameter + dm_lagg::Vector{T} | "µm" +end + +function initialize_transport_capacity_yalin_diff_params(nc, config, inds) + slope = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.slope"; + sel = inds, + defaults = 0.01, + type = Float, + ) + density = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.density"; + sel = inds, + defaults = 2650.0, + type = Float, + ) + dm_clay = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.dm_clay"; + sel = inds, + defaults = 2.0, + type = Float, + ) + dm_silt = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.dm_silt"; + sel = inds, + defaults = 10.0, + type = Float, + ) + dm_sand = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.dm_sand"; + sel = inds, + defaults = 200.0, + type = Float, + ) + dm_sagg = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.dm_sagg"; + sel = inds, + defaults = 30.0, + type = Float, + ) + dm_lagg = ncread( + nc, + config, + "lateral.land.transport_capacity.parameters.dm_lagg"; + sel = inds, + defaults = 500.0, + type = Float, + ) + tc_parameters = TransportCapacityYalinDifferentiationParameters(; + slope = slope, + density = density, + dm_clay = dm_clay, + dm_silt = dm_silt, + dm_sand = dm_sand, + dm_sagg = dm_sagg, + dm_lagg = dm_lagg, + ) + + return tc_parameters +end + +@get_units @with_kw struct TransportCapacityYalinDifferentiationModel{T} <: + AbstractTransportCapacityModel + boundary_conditions::TransportCapacityBC{T} | "-" + parameters::TransportCapacityYalinDifferentiationParameters{T} | "-" + variables::TransportCapacityYalinDifferentiationModelVars{T} | "-" +end + +function initialize_transport_capacity_yalin_diff_model(nc, config, inds) + n = length(inds) + vars = transport_capacity_yalin_differentiation_model_vars(n) + params = initialize_transport_capacity_yalin_diff_params(nc, config, inds) + bc = transport_capacity_bc(n) + model = TransportCapacityYalinDifferentiationModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!( + model::TransportCapacityYalinDifferentiationModel, + width, + waterbodies, + rivers, + ts, +) + (; q, waterlevel) = model.boundary_conditions + (; slope, density, dm_clay, dm_silt, dm_sand, dm_sagg, dm_lagg) = model.parameters + (; amount, clay, silt, sand, sagg, lagg) = model.variables + + n = length(q) + threaded_foreach(1:n; basesize = 1000) do i + dtot = transportability_yalin_differentiation( + waterlevel[i], + density[i], + dm_clay[i], + dm_silt[i], + dm_sand[i], + dm_sagg[i], + dm_lagg[i], + slope[i], + ) + clay[i] = transport_capacity_yalin_differentiation( + q[i], + waterlevel[i], + density[i], + dm_clay[i], + slope[i], + width[i], + waterbodies[i], + rivers[i], + dtot, + ts, + ) + silt[i] = transport_capacity_yalin_differentiation( + q[i], + waterlevel[i], + density[i], + dm_silt[i], + slope[i], + width[i], + waterbodies[i], + rivers[i], + dtot, + ts, + ) + sand[i] = transport_capacity_yalin_differentiation( + q[i], + waterlevel[i], + density[i], + dm_sand[i], + slope[i], + width[i], + waterbodies[i], + rivers[i], + dtot, + ts, + ) + sagg[i] = transport_capacity_yalin_differentiation( + q[i], + waterlevel[i], + density[i], + dm_sagg[i], + slope[i], + width[i], + waterbodies[i], + rivers[i], + dtot, + ts, + ) + lagg[i] = transport_capacity_yalin_differentiation( + q[i], + waterlevel[i], + density[i], + dm_lagg[i], + slope[i], + width[i], + waterbodies[i], + rivers[i], + dtot, + ts, + ) + amount[i] = clay[i] + silt[i] + sand[i] + sagg[i] + lagg[i] + end +end \ No newline at end of file diff --git a/src/sediment_transport/transport_capacity_process.jl b/src/sediment_transport/transport_capacity_process.jl new file mode 100644 index 000000000..7a900bded --- /dev/null +++ b/src/sediment_transport/transport_capacity_process.jl @@ -0,0 +1,287 @@ +""" + mask_transport_capacity( + transport_capacity, + waterbodies, + rivers, + ) + +Mask transport capacity values for waterbodies and rivers. + +# Arguments +- `transport_capacity` (total sediment transport capacity [t dt-1]) +- `waterbodies` (waterbodies mask [-]) +- `rivers` (rivers mask [-]) + +# Output +- `transport_capacity` (masked total sediment transport capacity [t dt-1]) +""" +function mask_transport_capacity(transport_capacity, waterbodies, rivers) + # Full deposition in rivers to switch to river concept + if rivers + tc = 0.0 + # Sediment flux in waterbodies will all reach the river + elseif waterbodies + tc = 1e9 + else + tc = transport_capacity + end + + return tc +end + +""" + transport_capacity_govers( + q, + waterlevel, + c_govers, + n_govers, + density, + slope, + width, + waterbodies, + rivers, + ts, + ) + +Total sediment transport capacity based on Govers. + +# Arguments +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `c_govers` (Govers transport capacity coefficient [-]) +- `n_govers` (Govers transport capacity exponent [-]) +- `density` (sediment density [kg m-3]) +- `slope` (slope [-]) +- `width` (drain width [m]) +- `waterbodies` (waterbodies mask [-]) +- `rivers` (rivers mask [-]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function transport_capacity_govers( + q, + waterlevel, + c_govers, + n_govers, + density, + slope, + width, + waterbodies, + rivers, + ts, +) + # Transport capacity from govers 1990 + sinslope = sin(atan(slope)) #slope in radians + # Unit stream power + if waterlevel > 0.0 + velocity = q / (width * waterlevel) #m/s + else + velocity = 0.0 + end + omega = 10 * sinslope * 100 * velocity #cm/s + if omega > 0.4 + TCf = c_govers * (omega - 0.4)^(n_govers) * density #kg/m3 + else + TCf = 0.0 + end + transport_capacity = TCf * q * ts * 1e-3 #[ton/cell] + + # Mask transport capacity values for waterbodies and rivers + transport_capacity = mask_transport_capacity(transport_capacity, waterbodies, rivers) + + return transport_capacity +end + +""" + transport_capacity_yalin( + q, + waterlevel, + density, + d50, + slope, + width, + waterbodies, + rivers, + ts, + ) + +Total sediment transport capacity based on Yalin. + +# Arguments +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `density` (sediment density [kg m-3]) +- `d50` (median grain size [m]) +- `slope` (slope [-]) +- `width` (drain width [m]) +- `waterbodies` (waterbodies mask [-]) +- `rivers` (rivers mask [-]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function transport_capacity_yalin( + q, + waterlevel, + density, + d50, + slope, + width, + waterbodies, + rivers, + ts, +) + sinslope = sin(atan(slope)) #slope in radians + # Transport capacity from Yalin without particle differentiation + delta = + max((waterlevel * sinslope / (d50 * 0.001 * (density / 1000 - 1)) / 0.06 - 1), 0.0) + alphay = delta * 2.45 / (0.001 * density)^0.4 * 0.06^(0.5) + if q > 0.0 && alphay != 0.0 + TC = ( + width / q * + (density - 1000) * + d50 * + 0.001 * + (9.81 * waterlevel * sinslope) * + 0.635 * + delta * + (1 - log(1 + alphay) / (alphay)) + ) # [kg/m3] + transport_capacity = TC * q * ts * 1e-3 #[ton] + else + transport_capacity = 0.0 + end + + # Mask transport capacity values for waterbodies and rivers + transport_capacity = mask_transport_capacity(transport_capacity, waterbodies, rivers) + + return transport_capacity +end + +""" + transportability_yalin_differentiation( + waterlevel, + density, + dm_clay, + dm_silt, + dm_sand, + dm_sagg, + dm_lagg, + slope, + ) + +Total flow transportability based on Yalin with particle differentiation. + +# Arguments +- `waterlevel` (water level [m]) +- `density` (sediment density [kg m-3]) +- `dm_clay` (clay median grain size [m]) +- `dm_silt` (silt median grain size [m]) +- `dm_sand` (sand median grain size [m]) +- `dm_sagg` (small aggregates median grain size [m]) +- `dm_lagg` (large aggregates median grain size [m]) +- `slope` (slope [-]) + +# Output +- `dtot` (total transportability of the flow [-]) +""" +function transportability_yalin_differentiation( + waterlevel, + density, + dm_clay, + dm_silt, + dm_sand, + dm_sagg, + dm_lagg, + slope, +) + sinslope = sin(atan(slope)) #slope in radians + # Delta parameter of Yalin for each particle class + delta = waterlevel * sinslope / (1e-6 * (density / 1000 - 1)) / 0.06 + dclay = max(1 / dm_clay * delta - 1, 0.0) + dsilt = max(1 / dm_silt * delta - 1, 0.0) + dsand = max(1 / dm_sand * delta - 1, 0.0) + dsagg = max(1 / dm_sagg * delta - 1, 0.0) + dlagg = max(1 / dm_lagg * delta - 1, 0.0) + # Total transportability + dtot = dclay + dsilt + dsand + dsagg + dlagg + + return dtot +end + +""" + transport_capacity_yalin_differentiation( + q, + waterlevel, + density, + dm, + slope, + width, + waterbodies, + rivers, + dtot, + ts, + ) + +Transport capacity for a specific grain size based on Yalin with particle differentiation. + +# Arguments +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `density` (sediment density [kg m-3]) +- `dm` (median grain size [m]) +- `slope` (slope [-]) +- `width` (drain width [m]) +- `waterbodies` (waterbodies mask [-]) +- `rivers` (rivers mask [-]) +- `dtot` (total flow transportability [t dt-1]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function transport_capacity_yalin_differentiation( + q, + waterlevel, + density, + dm, + slope, + width, + waterbodies, + rivers, + dtot, + ts, +) + sinslope = sin(atan(slope)) #slope in radians + # Transport capacity from Yalin with particle differentiation + # Delta parameter of Yalin for the specific particle class + delta = waterlevel * sinslope / (1e-6 * (density / 1000 - 1)) / 0.06 + d_part = max(1 / dm * delta - 1, 0.0) + + if q > 0.0 + TCa = width / q * (density - 1000) * 1e-6 * (9.81 * waterlevel * sinslope) + else + TCa = 0.0 + end + + TCb = 2.45 / (0.001 * density)^0.4 * 0.06^0.5 + + if dtot != 0.0 && d_part != 0.0 + TC = + TCa * dm * d_part / dtot * + 0.635 * + d_part * + (1 - log(1 + d_part * TCb) / d_part * TCb) # [kg/m3] + transport_capacity = TC * q * ts * 1e-3 #[ton] + else + transport_capacity = 0.0 + end + + # Mask transport capacity values for waterbodies and rivers + transport_capacity = mask_transport_capacity(transport_capacity, waterbodies, rivers) + + return transport_capacity +end \ No newline at end of file diff --git a/test/run_sediment.jl b/test/run_sediment.jl index c0729c5a5..8bfd5d72f 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -20,8 +20,6 @@ Wflow.run_timestep!(model) @test eros.rainfall_erosion.variables.amount[1] ≈ 0.00027245577922893746f0 @test model.clock.iteration == 1 #@test mean(eros.leaf_area_index) ≈ 1.7120018886212223f0 - #@test eros.dmsand[1] == 200.0f0 - #@test eros.dmlagg[1] == 500.0f0 #@test mean(eros.interception) ≈ 0.4767846753916875f0 @test mean(eros.overland_flow_erosion.variables.amount) ≈ 0.00861079076689589f0 @test mean(eros.rainfall_erosion.variables.amount) ≈ 0.00016326203201620437f0 @@ -40,26 +38,29 @@ model = Wflow.run_timestep(model) @test mean(eros.soil_erosion.variables.sand) ≈ 0.026301393837924607f0 @test mean(eros.soil_erosion.variables.lagg) ≈ 0.022577957752547836f0 @test mean(eros.soil_erosion.variables.sagg) ≈ 0.022874695590802723f0 - #@test mean(eros.TCsed) == 0.0 - #@test mean(eros.TCsilt) ≈ 1.0988158364353527f6 - #@test mean(eros.TCsand) ≈ 1.0987090622888755f6 - #@test mean(eros.TCclay) ≈ 1.0992655197016734f6 - #@test mean(eros.TCsilt) ≈ 1.0988158364353527f6 end -# @testset "second timestep sediment model (lateral)" begin -# lat = model.lateral +@testset "second timestep sediment model (lateral)" begin + land = model.lateral.land + + @test land.transport_capacity.parameters.dm_sand[1] == 200.0f0 + @test land.transport_capacity.parameters.dm_lagg[1] == 500.0f0 -# @test mean(lat.land.inlandsed) ≈ 0.07463801685030906f0 -# @test mean(lat.land.inlandclay) ≈ 0.0022367786781657497f0 -# @test mean(lat.land.inlandsand) ≈ 0.02519222037812127f0 -# @test mean(lat.land.olclay) ≈ 0.006443036462118322f0 + @test mean(land.transport_capacity.boundary_conditions.q) ≈ 0.006879398771052133f0 + @test mean(land.transport_capacity.variables.silt) ≈ 1.0988158364353527f6 + @test mean(land.transport_capacity.variables.sand) ≈ 1.0987090622888755f6 + @test mean(land.transport_capacity.variables.clay) ≈ 1.0992655197016734f6 -# @test mean(lat.river.SSconc) ≈ 0.8259993252994058f0 -# @test mean(lat.river.inlandclay) ≈ 0.01980468760667709f0 -# @test lat.river.h_riv[network.river.order[end]] ≈ 0.006103649735450745f0 -# @test lat.river.outclay[5649] ≈ 2.359031898208781f-9 -# end + @test mean(land.to_river.variables.amount) ≈ 0.07463801685030906f0 + @test mean(land.to_river.variables.clay) ≈ 0.0022367786781657497f0 + @test mean(land.to_river.variables.sand) ≈ 0.02519222037812127f0 + @test mean(land.sediment_flux.variables.clay) ≈ 0.006443036462118322f0 + + # @test mean(lat.river.SSconc) ≈ 0.8259993252994058f0 + # @test mean(lat.river.inlandclay) ≈ 0.01980468760667709f0 + # @test lat.river.h_riv[network.river.order[end]] ≈ 0.006103649735450745f0 + # @test lat.river.outclay[5649] ≈ 2.359031898208781f-9 +end @testset "Exchange and grid location sediment" begin @test Wflow.exchange(model.vertical.n) == false diff --git a/test/runtests.jl b/test/runtests.jl index 6981af08d..a1dfbc51e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -78,20 +78,20 @@ include("testing_utils.jl") with_logger(NullLogger()) do ## run all tests @testset "Wflow.jl" begin - include("horizontal_process.jl") - include("io.jl") - include("vertical_process.jl") - include("reservoir_lake.jl") - include("run_sbm.jl") - include("run_sbm_piave.jl") - include("run_sbm_gwf_piave.jl") - include("run_sbm_gwf.jl") - include("run.jl") - include("groundwater.jl") - include("utils.jl") - include("bmi.jl") + #include("horizontal_process.jl") + #include("io.jl") + #include("vertical_process.jl") + #include("reservoir_lake.jl") + #include("run_sbm.jl") + #include("run_sbm_piave.jl") + #include("run_sbm_gwf_piave.jl") + #include("run_sbm_gwf.jl") + #include("run.jl") + #include("groundwater.jl") + #include("utils.jl") + #include("bmi.jl") include("run_sediment.jl") - include("subdomains.jl") + #include("subdomains.jl") Aqua.test_all(Wflow; ambiguities = false, persistent_tasks = false) end diff --git a/test/sediment_config.toml b/test/sediment_config.toml index 946f0786f..ac2dd05ee 100644 --- a/test/sediment_config.toml +++ b/test/sediment_config.toml @@ -47,14 +47,18 @@ gauges = "wflow_gauges" ldd = "wflow_ldd" river_location = "wflow_river" subcatchment = "wflow_subcatch" +reservoir_areas = "wflow_reservoirareas" +lake_areas = "wflow_lakeareas" # 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.hydrometeo_forcing.waterlevel_land", #"vertical.interception", "vertical.hydrometeo_forcing.precipitation", + "vertical.hydrometeo_forcing.waterlevel_land", + "lateral.land.hydrometeo_forcing.waterlevel_land", "vertical.hydrometeo_forcing.q_land", + "lateral.land.hydrometeo_forcing.q_land", "lateral.river.h_riv", "lateral.river.q_riv", ] @@ -69,12 +73,7 @@ forcing = [ #storage_wood = "Swood" ## other parameters pathfrac = "PathFrac" -rivcell = "wflow_river" slope = "Slope" -# Reservoir -resareas = "wflow_reservoirareas" -# Lake -lakeareas = "wflow_lakeareas" [input.vertical.hydrometeo_forcing] precipitation = "P" @@ -102,8 +101,21 @@ sand_fraction = "fsand_soil" sagg_fraction = "fsagg_soil" lagg_fraction = "flagg_soil" -[input.lateral.land] +[input.lateral.land.hydrometeo_forcing] +waterlevel_land = "levKinL" +q_land = "runL" + +[input.lateral.land.transport_capacity.parameters] slope = "Slope" +density = "sediment_density" +d50 = "d50_soil" +c_govers = "c_govers" +n_govers = "n_govers" +dm_clay = "dm_clay" +dm_silt = "dm_silt" +dm_sand = "dm_sand" +dm_sagg = "dm_sagg" +dm_lagg = "dm_lagg" [input.lateral.river] h_riv = "h" @@ -152,13 +164,17 @@ amount = "overland_flow_erosion" amount = "soilloss" clay = "erosclay" -# [output.lateral.land] -# #TCclay = "TCclay" -# #TCsed = "TCsed" -# inlandclay = "inlandclay" -# inlandsed = "inlandsed" -# olclay = "olclay" -# olsed = "olsed" +[output.lateral.land.transport_capacity.variables] +clay = "TCclay" +amount = "TCsed" + +[output.lateral.land.to_river.variables] +clay = "inlandclay" +amount = "inlandsed" + +[output.lateral.land.sediment_flux.variables] +clay = "olclay" +amount = "olsed" # [output.lateral.river] # Bedconc = "Bedconc" @@ -207,16 +223,16 @@ parameter = "vertical.hydrometeo_forcing.q_land" # coordinate.x = 6.931 # coordinate.y = 48.085 # header = "TCsed" -# parameter = "vertical.TCsed" +# parameter = "lateral.land.transport_capacity.variables.amount" # [[csv.column]] # coordinate.x = 6.931 # coordinate.y = 48.085 # header = "TCclay" -# parameter = "vertical.TCclay" +# parameter = "lateral.land.transport_capacity.variables.clay" # [[csv.column]] # coordinate.x = 6.931 # coordinate.y = 48.085 # header = "inlandsed" -# parameter = "lateral.land.inlandsed" +# parameter = "lateral.land.to_river.variables.amount" From 1014297d93848b5523690b711ed2084fea44d11e Mon Sep 17 00:00:00 2001 From: hboisgon Date: Fri, 20 Sep 2024 09:54:52 +0800 Subject: [PATCH 22/42] working refactor of lateral land sediment --- src/sediment_model.jl | 4 +- src/sediment_transport/land_to_river.jl | 14 +++--- .../overland_flow_transport.jl | 49 +++++++++++++++++-- test/run_sediment.jl | 8 +-- test/sediment_config.toml | 2 +- 5 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/sediment_model.jl b/src/sediment_model.jl index 81cdaf6bd..f50e84a62 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -52,8 +52,8 @@ function initialize_sediment_model(config::Config) soilloss = initialize_soil_loss(nc, config, inds, area, landslope) # Get waterbodies mask - do_reservoirs = get(config.model, "reservoirs", false)::Bool - do_lakes = get(config.model, "lakes", false)::Bool + do_reservoirs = get(config.model, "doreservoir", false)::Bool + do_lakes = get(config.model, "dolake", false)::Bool waterbodies = fill(0.0, n) if do_reservoirs reservoirs = ncread( diff --git a/src/sediment_transport/land_to_river.jl b/src/sediment_transport/land_to_river.jl index 5457295e3..f1910c993 100644 --- a/src/sediment_transport/land_to_river.jl +++ b/src/sediment_transport/land_to_river.jl @@ -36,8 +36,7 @@ end function update_bc(model::SedimentToRiverModel, transport_model::SedimentLandTransportModel) (; deposition) = model.boundary_conditions - (; amount) = transport_model.variables - @. deposition = amount + @. deposition = transport_model.variables.deposition end function update!(model::SedimentToRiverModel, rivers) @@ -126,12 +125,11 @@ function update_bc( deposition_sagg, deposition_lagg, ) = model.boundary_conditions - (; clay, silt, sand, sagg, lagg) = transport_model.variables - @. deposition_clay = clay - @. deposition_silt = silt - @. deposition_sand = sand - @. deposition_sagg = sagg - @. deposition_lagg = lagg + @. deposition_clay = transport_model.variables.deposition_clay + @. deposition_silt = transport_model.variables.deposition_silt + @. deposition_sand = transport_model.variables.deposition_sand + @. deposition_sagg = transport_model.variables.deposition_sagg + @. deposition_lagg = transport_model.variables.deposition_lagg end function update!(model::SedimentToRiverDifferentiationModel, rivers) diff --git a/src/sediment_transport/overland_flow_transport.jl b/src/sediment_transport/overland_flow_transport.jl index 94d733c8e..89113effb 100644 --- a/src/sediment_transport/overland_flow_transport.jl +++ b/src/sediment_transport/overland_flow_transport.jl @@ -2,12 +2,13 @@ abstract type AbstractSedimentLandTransportModel end ## Total sediment transport in overland flow structs and functions @get_units @with_kw struct SedimentLandTransportVars{T} - # Total sediment reaching the river + # Total sediment flux amount::Vector{T} | "t dt-1" + deposition::Vector{T} | "t dt-1" end function sediment_land_transport_vars(n) - vars = SedimentLandTransportVars(; amount = fill(mv, n)) + vars = SedimentLandTransportVars(; amount = fill(mv, n), deposition = fill(mv, n)) return vars end @@ -52,35 +53,54 @@ end function update!(model::SedimentLandTransportModel, network) (; erosion, transport_capacity) = model.boundary_conditions - (; amount) = model.variables + (; amount, deposition) = model.variables accucapacityflux!(amount, erosion, network, transport_capacity) + deposition .= erosion end ## Total transport capacity with particle differentiation structs and functions @get_units @with_kw struct SedimentLandTransportDifferentiationVars{T} # Total sediment flux amount::Vector{T} | "t dt-1" + # Deposition + deposition::Vector{T} | "t dt-1" # Clay flux clay::Vector{T} | "t dt-1" + # Deposition clay + deposition_clay::Vector{T} | "t dt-1" # Silt silt::Vector{T} | "t dt-1" + # Deposition silt + deposition_silt::Vector{T} | "t dt-1" # Sand flux sand::Vector{T} | "t dt-1" + # Deposition sand + deposition_sand::Vector{T} | "t dt-1" # Small aggregates flux sagg::Vector{T} | "t dt-1" + # Deposition small aggregates + deposition_sagg::Vector{T} | "t dt-1" # Large aggregates flux lagg::Vector{T} | "t dt-1" + # Deposition large aggregates + deposition_lagg::Vector{T} | "t dt-1" end function sediment_land_transport_differentiation_vars(n) vars = SedimentLandTransportDifferentiationVars(; amount = fill(mv, n), + deposition = fill(mv, n), clay = fill(mv, n), + deposition_clay = fill(mv, n), silt = fill(mv, n), + deposition_silt = fill(mv, n), sand = fill(mv, n), + deposition_sand = fill(mv, n), sagg = fill(mv, n), + deposition_sagg = fill(mv, n), lagg = fill(mv, n), + deposition_lagg = fill(mv, n), ) return vars end @@ -186,12 +206,33 @@ function update!(model::SedimentLandTransportDifferentiationModel, network) transport_capacity_sagg, transport_capacity_lagg, ) = model.boundary_conditions - (; amount, clay, silt, sand, sagg, lagg) = model.variables + (; + amount, + deposition, + clay, + deposition_clay, + silt, + deposition_silt, + sand, + deposition_sand, + sagg, + deposition_sagg, + lagg, + deposition_lagg, + ) = model.variables accucapacityflux!(clay, erosion_clay, network, transport_capacity_clay) + deposition_clay .= erosion_clay accucapacityflux!(silt, erosion_silt, network, transport_capacity_silt) + deposition_silt .= erosion_silt accucapacityflux!(sand, erosion_sand, network, transport_capacity_sand) + deposition_sand .= erosion_sand accucapacityflux!(sagg, erosion_sagg, network, transport_capacity_sagg) + deposition_sagg .= erosion_sagg accucapacityflux!(lagg, erosion_lagg, network, transport_capacity_lagg) + deposition_lagg .= erosion_lagg amount .= clay .+ silt .+ sand .+ sagg .+ lagg + deposition .= + deposition_clay .+ deposition_silt .+ deposition_sand .+ deposition_sagg .+ + deposition_lagg end \ No newline at end of file diff --git a/test/run_sediment.jl b/test/run_sediment.jl index 8bfd5d72f..56b161f1f 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -51,10 +51,10 @@ end @test mean(land.transport_capacity.variables.sand) ≈ 1.0987090622888755f6 @test mean(land.transport_capacity.variables.clay) ≈ 1.0992655197016734f6 - @test mean(land.to_river.variables.amount) ≈ 0.07463801685030906f0 - @test mean(land.to_river.variables.clay) ≈ 0.0022367786781657497f0 - @test mean(land.to_river.variables.sand) ≈ 0.02519222037812127f0 - @test mean(land.sediment_flux.variables.clay) ≈ 0.006443036462118322f0 + @test mean(land.to_river.variables.amount) ≈ 0.07624135182616738f0 + @test mean(land.to_river.variables.clay) ≈ 0.002285341387958068f0 + @test mean(land.to_river.variables.sand) ≈ 0.02575351604673049f0 + @test mean(land.sediment_flux.variables.clay) ≈ 0.006578791733506439f0 # @test mean(lat.river.SSconc) ≈ 0.8259993252994058f0 # @test mean(lat.river.inlandclay) ≈ 0.01980468760667709f0 diff --git a/test/sediment_config.toml b/test/sediment_config.toml index ac2dd05ee..48abb84db 100644 --- a/test/sediment_config.toml +++ b/test/sediment_config.toml @@ -143,7 +143,7 @@ lakelocs = "wflow_lakelocs" [model] dolake = false -doreservoir = true +doreservoir = true # cannot use reservoirs as in sbm because then states/volume need to be added landtransportmethod = "yalinpart" # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] rainerosmethod = "answers" # Rainfall erosion equation: ["answers", "eurosem"] reinit = true From ed03716305adaf641f58be7ea283dd76560926e6 Mon Sep 17 00:00:00 2001 From: hboisgon Date: Mon, 23 Sep 2024 17:50:08 +0800 Subject: [PATCH 23/42] draft refactor river sediment --- src/Wflow.jl | 8 +- src/erosion.jl | 11 +- src/erosion/erosion_process.jl | 96 ++ src/erosion/overland_flow_erosion.jl | 17 +- src/erosion/river_erosion.jl | 81 ++ src/geometry.jl | 79 ++ src/sediment_flux.jl | 139 ++- src/sediment_model.jl | 69 +- src/sediment_transport/deposition.jl | 49 + src/sediment_transport/river_transport.jl | 867 ++++++++++++++++++ src/sediment_transport/transport_capacity.jl | 336 ++++++- .../transport_capacity_process.jl | 361 ++++++++ src/states.jl | 36 +- test/run_sediment.jl | 10 +- test/sediment_config.toml | 125 ++- 15 files changed, 2140 insertions(+), 144 deletions(-) create mode 100644 src/erosion/river_erosion.jl create mode 100644 src/geometry.jl create mode 100644 src/sediment_transport/deposition.jl create mode 100644 src/sediment_transport/river_transport.jl diff --git a/src/Wflow.jl b/src/Wflow.jl index 0b25bba37..07a88346e 100644 --- a/src/Wflow.jl +++ b/src/Wflow.jl @@ -141,6 +141,7 @@ Base.show(io::IO, m::Model) = print(io, "model of type ", typeof(m)) include("forcing.jl") include("parameters.jl") +include("geometry.jl") include("flow.jl") include("horizontal_process.jl") include("vegetation/rainfall_interception.jl") @@ -154,7 +155,7 @@ include("soil/soil.jl") include("soil/soil_process.jl") include("sbm.jl") include("demand/water_demand.jl") -include("sediment.jl") +#include("sediment.jl") include("reservoir_lake.jl") include("sbm_model.jl") include("sediment_model.jl") @@ -163,11 +164,14 @@ include("erosion/erosion_process.jl") include("erosion/rainfall_erosion.jl") include("erosion/overland_flow_erosion.jl") include("erosion/soil_erosion.jl") -include("sediment_flux.jl") +include("erosion/river_erosion.jl") +include("sediment_transport/deposition.jl") include("sediment_transport/transport_capacity_process.jl") include("sediment_transport/transport_capacity.jl") include("sediment_transport/overland_flow_transport.jl") include("sediment_transport/land_to_river.jl") +include("sediment_transport/river_transport.jl") +include("sediment_flux.jl") include("groundwater/connectivity.jl") include("groundwater/aquifer.jl") include("groundwater/boundary_conditions.jl") diff --git a/src/erosion.jl b/src/erosion.jl index 8b328bd9f..18ad50797 100644 --- a/src/erosion.jl +++ b/src/erosion.jl @@ -1,14 +1,15 @@ @get_units @with_kw struct SoilLoss{RE, OLE, SE, T} hydrometeo_forcing::HydrometeoForcing | "-" + geometry::LandGeometry | "-" rainfall_erosion::RE | "-" overland_flow_erosion::OLE | "-" soil_erosion::SE | "-" - area::Vector{T} | "m²" end -function initialize_soil_loss(nc, config, inds, area, slope) +function initialize_soil_loss(nc, config, inds) n = length(inds) hydrometeo_forcing = initialize_hydrometeo_forcing(n) + geometry = initialize_land_geometry(nc, config, inds) # Rainfall erosion rainfallerosionmodel = get(config.model, "rainfall_erosion", "answers")::String if rainfallerosionmodel == "answers" @@ -23,7 +24,7 @@ function initialize_soil_loss(nc, config, inds, area, slope) overlandflowerosionmodel = get(config.model, "overland_flow_erosion", "answers")::String if overlandflowerosionmodel == "answers" overland_flow_erosion_model = - initialize_answers_overland_flow_erosion_model(nc, config, inds, slope) + initialize_answers_overland_flow_erosion_model(nc, config, inds) else error("Unknown overland flow erosion model: $overlandflowerosionmodel") # overland_flow_erosion_model = NoOverlandFlowErosionModel() @@ -39,10 +40,10 @@ function initialize_soil_loss(nc, config, inds, area, slope) Float, }(; hydrometeo_forcing = hydrometeo_forcing, + geometry = geometry, rainfall_erosion = rainfall_erosion_model, overland_flow_erosion = overland_flow_erosion_model, soil_erosion = soil_erosion_model, - area = area, ) return soil_loss end @@ -55,7 +56,7 @@ function update!(model::SoilLoss, dt) # Rainfall erosion update!(model.rainfall_erosion, model.hydrometeo_forcing, model.area, ts) # Overland flow erosion - update!(model.overland_flow_erosion, model.hydrometeo_forcing, model.area, ts) + update!(model.overland_flow_erosion, model.hydrometeo_forcing, model.geometry, ts) # Total soil erosion and particle differentiation re = get_rainfall_erosion(model.rainfall_erosion) ole = get_overland_flow_erosion(model.overland_flow_erosion) diff --git a/src/erosion/erosion_process.jl b/src/erosion/erosion_process.jl index 00a3d5415..eaf9d184c 100644 --- a/src/erosion/erosion_process.jl +++ b/src/erosion/erosion_process.jl @@ -201,4 +201,100 @@ function total_soil_erosion( sand_erosion, sagg_erosion, lagg_erosion +end + +""" + function river_erosion_julian_torres( + waterlevel, + d50, + width, + length, + slope, + ts, + ) + +River erosion model based on Julian Torres. +Repartition of the effective shear stress between the bank and the bed from Knight et al. 1984 [%] + +# Arguments +- `waterlevel` (water level [m]) +- `d50` (median grain size [m]) +- `width` (width [m]) +- `length` (length [m]) +- `slope` (slope [-]) +- `ts` (timestep [seconds]) + +# Output +- `bed` (potential river erosion [t Δt⁻¹]) +- `bank` (potential bank erosion [t Δt⁻¹]) +""" +function river_erosion_julian_torres(waterlevel, d50, width, length, slope, ts) + if waterlevel > 0.0 + # Bed and Bank from Shields diagram, Da Silva & Yalin (2017) + E_ = (2.65 - 1) * 9.81 + E = (E_ * (d50 * 1e-3)^3 / 1e-12)^0.33 + TCrbed = + E_ * + d50 * + (0.13 * E^(-0.392) * exp(-0.015 * E^2) + 0.045 * (1 - exp(-0.068 * E))) + TCrbank = TCrbed + # kd from Hanson & Simon 2001 + kdbank = 0.2 * TCrbank^(-0.5) * 1e-6 + kdbed = 0.2 * TCrbed^(-0.5) * 1e-6 + + # Hydraulic radius of the river [m] (rectangular channel) + hydrad = waterlevel * width / (width + 2 * waterlevel) + + # Repartition of the effective shear stress between the bank and the Bed + SFbank = exp(-3.23 * log10(width / waterlevel + 3) + 6.146) + # Effective shear stress on river bed and banks [N/m2] + TEffbank = + 1000 * 9.81 * hydrad * slope * SFbank / 100 * (1 + width / (2 * waterlevel)) + TEffbed = + 1000 * 9.81 * hydrad * slope * (1 - SFbank / 100) * (1 + 2 * waterlevel / width) + + # Potential erosion rates of the bed and bank [t/cell/timestep] + #(assuming only one bank is eroding) + Tex = max(TEffbank - TCrbank, 0.0) + # 1.4 is bank default bulk density + ERbank = kdbank * Tex * length * waterlevel * 1.4 * ts + # 1.5 is bed default bulk density + ERbed = kdbed * (TEffbed - TCrbed) * length * width * 1.5 * ts + + # Potential maximum bed/bank erosion + bed = max(ERbed, 0.0) + bank = max(ERbank, 0.0) + + else + bed = 0.0 + bank = 0.0 + end + + return bed, bank +end + +""" + function river_erosion_store( + excess_sediment, + store, + ) + +River erosion of the previously deposited sediment. + +# Arguments +- `excess_sediment` (excess sediment [t Δt⁻¹]) +- `store` (sediment store [t]) + +# Output +- `erosion` (river erosion [t Δt⁻¹]) +- `excess_sediment` (updated excess sediment [t Δt⁻¹]) +- `store` (updated sediment store [t]) +""" +function river_erosion_store(excess_sediment, store) + # River erosion of the previously deposited sediment + erosion = min(store, excess_sediment) + # Update the excess sediment and the sediment store + excess_sediment -= erosion + store -= erosion + return erosion, excess_sediment, store end \ No newline at end of file diff --git a/src/erosion/overland_flow_erosion.jl b/src/erosion/overland_flow_erosion.jl index 43a4da684..567c36757 100644 --- a/src/erosion/overland_flow_erosion.jl +++ b/src/erosion/overland_flow_erosion.jl @@ -21,8 +21,6 @@ end usle_c::Vector{T} | "-" # Answers overland flow factor answers_k::Vector{T} | "-" - # slope - slope::Vector{T} | "-" end @get_units @with_kw struct OverlandFlowErosionAnswersModel{T} <: @@ -31,7 +29,7 @@ end variables::OverlandFlowErosionModelVars{T} | "-" end -function initialize_answers_params_overland_flow(nc, config, inds, slope) +function initialize_answers_params_overland_flow(nc, config, inds) usle_k = ncread( nc, config, @@ -60,15 +58,14 @@ function initialize_answers_params_overland_flow(nc, config, inds, slope) usle_k = usle_k, usle_c = usle_c, answers_k = answers_k, - slope = slope, ) return answers_parameters end -function initialize_answers_overland_flow_erosion_model(nc, config, inds, slope) +function initialize_answers_overland_flow_erosion_model(nc, config, inds) n = length(inds) vars = overland_flow_erosion_model_vars(n) - params = initialize_answers_params_overland_flow(nc, config, inds, slope) + params = initialize_answers_params_overland_flow(nc, config, inds) model = OverlandFlowErosionAnswersModel(; parameters = params, variables = vars) return model end @@ -76,11 +73,11 @@ end function update!( model::OverlandFlowErosionAnswersModel, hydrometeo_forcing::HydrometeoForcing, - area, + geometry::LandGeometry, ts, ) (; q_land) = hydrometeo_forcing - (; usle_k, usle_c, answers_k, slope) = model.parameters + (; usle_k, usle_c, answers_k) = model.parameters (; amount) = model.variables n = length(q_land) @@ -90,8 +87,8 @@ function update!( usle_k[i], usle_c[i], answers_k[i], - slope[i], - area[i], + geometry.slope[i], + geometry.area[i], ts, ) end diff --git a/src/erosion/river_erosion.jl b/src/erosion/river_erosion.jl new file mode 100644 index 000000000..d7d0a52bc --- /dev/null +++ b/src/erosion/river_erosion.jl @@ -0,0 +1,81 @@ +abstract type AbstractRiverErosionModel end + +## Potential direct river erosion structs and functions +@get_units @with_kw struct RiverErosionModelVars{T} + # Potential river bed erosion + bed::Vector{T} | "t dt-1" + # Potential river bank erosion + bank::Vector{T} | "t dt-1" +end + +function river_erosion_model_vars(n) + vars = RiverErosionModelVars(; bed = fill(mv, n), bank = fill(mv, n)) + return vars +end + +@get_units @with_kw struct RiverErosionBC{T} + # Waterlevel + waterlevel::Vector{T} | "t dt-1" +end + +function river_erosion_bc(n) + bc = RiverErosionBC(; waterlevel = fill(mv, n)) + return bc +end + +# Parameters for the Julian Torres river erosion model +@get_units @with_kw struct RiverErosionParameters{T} + # Mean diameter in the river bed/bank + d50::Vector{T} | "mm" +end + +@get_units @with_kw struct RiverErosionJulianTorresModel{T} <: AbstractRiverErosionModel + boundary_conditions::RiverErosionBC{T} | "-" + parameters::RiverErosionParameters{T} | "-" + variables::RiverErosionModelVars{T} | "-" +end + +function initialize_river_erosion_params(nc, config, inds) + d50 = ncread( + nc, + config, + "lateral.river.potential_erosion.parameters.d50"; + sel = inds, + defaults = 0.1, + type = Float, + ) + river_parameters = RiverErosionParameters(; d50 = d50) + + return river_parameters +end + +function initialize_river_erosion_julian_torres_model(nc, config, inds) + n = length(inds) + vars = river_erosion_model_vars(n) + params = initialize_river_erosion_params(nc, config, inds) + bc = river_erosion_bc(n) + model = RiverErosionJulianTorresModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::RiverErosionJulianTorresModel, geometry::RiverGeometry, ts) + (; waterlevel) = model.boundary_conditions + (; d50) = model.parameters + (; bed, bank) = model.variables + + n = length(waterlevel) + threaded_foreach(1:n; basesize = 1000) do i + bed[i], bank[i] = river_erosion_julian_torres( + waterlevel[i], + d50[i], + geometry.width[i], + geometry.length[i], + geometry.slope[i], + ts, + ) + end +end \ No newline at end of file diff --git a/src/geometry.jl b/src/geometry.jl new file mode 100644 index 000000000..4695e8d4e --- /dev/null +++ b/src/geometry.jl @@ -0,0 +1,79 @@ + +@get_units @with_kw struct LandGeometry{T} + # cell area [m^2] + area::Vector{T} | "m^2" + # drain width [m] + width::Vector{T} | "m" + # drain slope + slope::Vector{T} | "-" +end + +function initialize_land_geometry(nc, config, inds) + # 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) + area = xl .* yl + ldd = ncread(nc, config, "ldd"; optional = false, sel = inds, allow_missing = true) + drain_width = map(detdrainwidth, ldd, xl, yl) + landslope = ncread( + nc, + config, + "vertical.geometry.slope"; + optional = false, + sel = inds, + type = Float, + ) + clamp!(landslope, 0.00001, Inf) + + land_geometry = + LandGeometry{Float}(; area = area, width = drain_width, slope = landslope) + return land_geometry +end + +@get_units @with_kw struct RiverGeometry{T} + # drain width [m] + width::Vector{T} | "m" + # drain length + length::Vector{T} | "m" + # slope + slope::Vector{T} | "-" +end + +function initialize_river_geometry(nc, config, inds) + riverwidth = ncread( + nc, + config, + "lateral.river.geometry.width"; + optional = false, + sel = inds, + type = Float, + ) + riverlength = ncread( + nc, + config, + "lateral.river.geometry.length"; + optional = false, + sel = inds, + type = Float, + ) + riverslope = ncread( + nc, + config, + "lateral.river.geometry.slope"; + optional = false, + sel = inds, + type = Float, + ) + minimum(riverlength) > 0 || error("river length must be positive on river cells") + minimum(riverwidth) > 0 || error("river width must be positive on river cells") + clamp!(riverslope, 0.00001, Inf) + + river_geometry = + RiverGeometry{Float}(; width = riverwidth, length = riverlength, slope = riverslope) + return river_geometry +end \ No newline at end of file diff --git a/src/sediment_flux.jl b/src/sediment_flux.jl index fae0016b3..7d4a5c987 100644 --- a/src/sediment_flux.jl +++ b/src/sediment_flux.jl @@ -1,16 +1,18 @@ +### Overland flow ### @get_units @with_kw struct OverlandFlowSediment{TT, SF, TR, T} hydrometeo_forcing::HydrometeoForcing | "-" + geometry::LandGeometry | "-" transport_capacity::TT | "-" sediment_flux::SF | "-" to_river::TR | "-" - width::Vector{T} | "m" waterbodies::Vector{Bool} | "-" rivers::Vector{Bool} | "-" end -function initialize_overland_flow_sediment(nc, config, inds, width, waterbodies, rivers) +function initialize_overland_flow_sediment(nc, config, inds, waterbodies, rivers) n = length(inds) hydrometeo_forcing = initialize_hydrometeo_forcing(n) + geometry = initialize_land_geometry(nc, config, inds) # Check what transport capacity equation will be used do_river = get(config.model, "runrivermodel", false)::Bool # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] @@ -44,10 +46,10 @@ function initialize_overland_flow_sediment(nc, config, inds, width, waterbodies, Float, }(; hydrometeo_forcing = hydrometeo_forcing, + geometry = geometry, transport_capacity = transport_capacity_model, sediment_flux = sediment_flux_model, to_river = to_river_model, - width = width, waterbodies = waterbodies, rivers = rivers, ) @@ -74,4 +76,135 @@ function update!(model::OverlandFlowSediment, erosion_model::SoilErosionModel, n update_bc(model.to_river, model.sediment_flux) # Compute sediment reaching the river update!(model.to_river, model.rivers) +end + +### River ### +@get_units @with_kw struct RiverSediment{TTR, ER, SFR, CR, T} + hydrometeo_forcing::HydrometeoForcing | "-" + geometry::RiverGeometry | "-" + transport_capacity::TTR | "-" + potential_erosion::ER | "-" + sediment_flux::SFR | "-" + concentrations::CR | "-" + waterbodies::Vector{Bool} | "-" +end + +function initialize_river_flow_sediment(nc, config, inds, waterbodies) + n = length(inds) + hydrometeo_forcing = initialize_hydrometeo_forcing(n) + geometry = initialize_river_geometry(nc, config, inds) + + # Check what transport capacity equation will be used + # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] + transport_method = get(config.model, "rivtransportmethod", "bagnold")::String + if transport_method == "bagnold" + transport_capacity_model = + initialize_transport_capacity_bagnold_model(nc, config, inds) + elseif transport_method == "engelund" + transport_capacity_model = + initialize_transport_capacity_engelund_model(nc, config, inds) + elseif transport_method == "yang" + transport_capacity_model = + initialize_transport_capacity_yang_model(nc, config, inds) + elseif transport_method == "kodatie" + transport_capacity_model = + initialize_transport_capacity_kodatie_model(nc, config, inds) + elseif transport_method == "molinas" + transport_capacity_model = + initialize_transport_capacity_molinas_model(nc, config, inds) + else + error("Unknown river transport method: $transport_method") + end + + # Potential river erosion + potential_erosion_model = initialize_river_erosion_julian_torres_model(nc, config, inds) + + # Sediment flux in river / mass balance + sediment_flux_model = initialize_sediment_river_transport_model(nc, config, inds) + + # Concentrations + concentrations_model = initialize_sediment_concentrations_river_model(nc, config, inds) + + river_sediment = RiverSediment{ + typeof(transport_capacity_model), + typeof(potential_erosion_model), + typeof(sediment_flux_model), + typeof(concentrations_model), + Float, + }(; + hydrometeo_forcing = hydrometeo_forcing, + geometry = geometry, + transport_capacity = transport_capacity_model, + potential_erosion = potential_erosion_model, + sediment_flux = sediment_flux_model, + concentrations = concentrations_model, + waterbodies = waterbodies, + ) + return river_sediment +end + +function update!( + model::RiverSediment, + to_river_model::SedimentToRiverDifferentiationModel, + network, + inds_riv, + dt, +) + # Convert dt to integer + ts = tosecond(dt) + # Update the boundary conditions of transport capacity + (; q, waterlevel) = model.transport_capacity.boundary_conditions + (; q_river, waterlevel_river) = model.hydrometeo_forcing + @. q = q_river + @. waterlevel = waterlevel_river + + # Transport capacity + update!(model.transport_capacity, model.geometry, ts) + + # Potential maximum river erosion + (; waterlevel) = model.potential_erosion.boundary_conditions + @. waterlevel = waterlevel_river + update!(model.potential_erosion, model.geometry, ts) + + # River transport + (; + waterlevel, + q, + transport_capacity, + erosion_land_clay, + erosion_land_silt, + erosion_land_sand, + erosion_land_sagg, + erosion_land_lagg, + potential_erosion_river, + ) = model.boundary_conditions + @. q = q_river + @. waterlevel = waterlevel_river + + @. transport_capacity = model.transport_capacity.variables.amount + + (; clay, silt, sand, sagg, lagg) = to_river_model.variables + @. erosion_land_clay = clay[inds_riv] + @. erosion_land_silt = silt[inds_riv] + @. erosion_land_sand = sand[inds_riv] + @. erosion_land_sagg = sagg[inds_riv] + @. erosion_land_lagg = lagg[inds_riv] + + @. potential_erosion_river = model.potential_erosion.variables.amount + + update!(model.sediment_flux, network, model.geometry, ts) + + # Concentrations + (; q, waterlevel, clay, silt, sand, sagg, lagg, gravel) = + model.concentrations.boundary_conditions + @. q = q_river + @. waterlevel = waterlevel_river + @. clay = model.sediment_flux.variables.clay + @. silt = model.sediment_flux.variables.silt + @. sand = model.sediment_flux.variables.sand + @. sagg = model.sediment_flux.variables.sagg + @. lagg = model.sediment_flux.variables.lagg + @. gravel = model.sediment_flux.variables.gravel + + update!(model.concentrations, model.geometry, ts) end \ No newline at end of file diff --git a/src/sediment_model.jl b/src/sediment_model.jl index f50e84a62..4e7f1caba 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -23,33 +23,12 @@ function initialize_sediment_model(config::Config) 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) # Needed to update the forcing reservoir = () lake = () - # 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) - area = xl .* yl - landslope = - ncread(nc, config, "vertical.slope"; optional = false, sel = inds, type = Float) - clamp!(landslope, 0.00001, Inf) - - soilloss = initialize_soil_loss(nc, config, inds, area, landslope) + soilloss = initialize_soil_loss(nc, config, inds) # Get waterbodies mask do_reservoirs = get(config.model, "doreservoir", false)::Bool @@ -84,30 +63,40 @@ function initialize_sediment_model(config::Config) ldd_2d = ncread(nc, config, "ldd"; optional = false, allow_missing = true) ldd = ldd_2d[inds] - drain_width = map(detdrainwidth, ldd, xl, yl) - # # lateral part sediment in overland flow overland_flow_sediment = - initialize_overland_flow_sediment(nc, config, inds, drain_width, waterbodies, river) + initialize_overland_flow_sediment(nc, config, inds, waterbodies, river) graph = flowgraph(ldd, inds, pcr_dir) # River processes do_river = get(config.model, "runrivermodel", false)::Bool + # TODO: see if we can skip init if the river model is not needed + # or if we leave it when we restructure the Wflow Model struct - # the indices of the river cells in the land(+river) cell vector - 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") + inds_riv, rev_inds_riv = active_indices(river_2d, 0) ldd_riv = ldd_2d[inds_riv] graph_riv = flowgraph(ldd_riv, inds_riv, pcr_dir) + # Needed for frac_to_river? + landslope = ncread( + nc, + config, + "vertical.geometry.slope"; + optional = false, + sel = inds, + type = Float, + ) + clamp!(landslope, 0.00001, Inf) + index_river = filter(i -> !isequal(river[i], 0), 1:n) frac_toriver = fraction_runoff_toriver(graph, ldd, index_river, landslope, n) - rs = initialize_riversed(nc, config, riverwidth, riverlength, inds_riv) + rs = initialize_river_flow_sediment(nc, config, inds_riv, waterbodies) + + y_nc = read_y_axis(nc) + x_nc = read_x_axis(nc) modelmap = (vertical = soilloss, lateral = (land = overland_flow_sediment, river = rs)) indices_reverse = ( @@ -161,18 +150,12 @@ function update!(model::Model{N, L, V, R, W, T}) where {N, L, V, R, W, T <: Sedi # Overland flow sediment transport update!(lateral.land, vertical.soil_erosion, network.land, dt) - # do_river = get(config.model, "runrivermodel", false)::Bool - - # if do_river - # inds_riv = network.index_river - # lateral.river.inlandclay .= lateral.land.inlandclay[inds_riv] - # lateral.river.inlandsilt .= lateral.land.inlandsilt[inds_riv] - # lateral.river.inlandsand .= lateral.land.inlandsand[inds_riv] - # lateral.river.inlandsagg .= lateral.land.inlandsagg[inds_riv] - # lateral.river.inlandlagg .= lateral.land.inlandlagg[inds_riv] - - # update(lateral.river, network.river, config) - # end + # River sediment transport + do_river = get(config.model, "runrivermodel", false)::Bool + if do_river + inds_riv = network.index_river + update!(lateral.river, lateral.land.to_river, network.river, inds_riv, dt) + end return nothing end diff --git a/src/sediment_transport/deposition.jl b/src/sediment_transport/deposition.jl new file mode 100644 index 000000000..720c6685f --- /dev/null +++ b/src/sediment_transport/deposition.jl @@ -0,0 +1,49 @@ +""" + reservoir_deposition_camp( + input, + q, + waterlevel, + wb_area, + wb_trapping_efficiency, + dm, + slope, + ) + +Deposition of sediment in waterbodies from Camp 1945. + +# Arguments +- `input` (sediment input [t Δt⁻¹]) +- `q` (discharge [m³ Δt⁻¹]) +- `waterlevel` (water level [m]) +- `wb_area` (waterbody area [m²]) +- `wb_trapping_efficiency` (waterbody trapping efficiency [-]) +- `dm` (mean diameter [m]) +- `slope` (slope [-]) + +# Output +- `deposition` (deposition [t Δt⁻¹]) +""" +function reservoir_deposition_camp( + input, + q, + waterlevel, + wb_area, + wb_trapping_efficiency, + dm, + slope, +) + # Compute critical velocity + vcres = q / wb_area + DCres = 411 / 3600 / vcres + # Natural deposition + deposition = input * min(1.0, (DCres * (dm / 1000)^2)) + + # Check if the particles was travelling in suspension or bed load using Rouse number + dsuspf = 1e3 * (1.2 * 3600 * 0.41 / 411 * (9.81 * waterlevel * slope)^0.5)^0.5 + # If bed load, we have extra deposition depending on the reservoir type + if dm > dsuspf + deposition = max(deposition, wb_trapping_efficiency * input) + end + + return deposition +end \ No newline at end of file diff --git a/src/sediment_transport/river_transport.jl b/src/sediment_transport/river_transport.jl new file mode 100644 index 000000000..b258be3bd --- /dev/null +++ b/src/sediment_transport/river_transport.jl @@ -0,0 +1,867 @@ +abstract type AbstractSedimentRiverTransportModel end + +## Total sediment transport in overland flow structs and functions +@get_units @with_kw struct SedimentRiverTransportVars{T} + # Sediment flux [ton] + amount::Vector{T} | "t dt-1" + clay::Vector{T} | "t dt-1" + silt::Vector{T} | "t dt-1" + sand::Vector{T} | "t dt-1" + sagg::Vector{T} | "t dt-1" + lagg::Vector{T} | "t dt-1" + gravel::Vector{T} | "t dt-1" + # Total Sediment deposition [ton] + deposition::Vector{T} | "t dt-1" + # Total sediment erosion (from store + direct river bed/bank) [ton] + erosion::Vector{T} | "t dt-1" + # Sediment / particle left in the cell [ton] - states + leftover_clay::Vector{T} | "t dt-1" + leftover_silt::Vector{T} | "t dt-1" + leftover_sand::Vector{T} | "t dt-1" + leftover_sagg::Vector{T} | "t dt-1" + leftover_lagg::Vector{T} | "t dt-1" + leftover_gravel::Vector{T} | "t dt-1" + # Sediment / particle stored on the river bed after deposition [ton] -states + store_clay::Vector{T} | "t dt-1" + store_silt::Vector{T} | "t dt-1" + store_sand::Vector{T} | "t dt-1" + store_sagg::Vector{T} | "t dt-1" + store_lagg::Vector{T} | "t dt-1" + store_gravel::Vector{T} | "t dt-1" +end + +function sediment_river_transport_vars(n) + vars = SedimentRiverTransportVars(; + amount = fill(mv, n), + clay = fill(mv, n), + silt = fill(mv, n), + sand = fill(mv, n), + sagg = fill(mv, n), + lagg = fill(mv, n), + gravel = fill(mv, n), + deposition = fill(mv, n), + erosion = fill(mv, n), + leftover_clay = fill(mv, n), + leftover_silt = fill(mv, n), + leftover_sand = fill(mv, n), + leftover_sagg = fill(mv, n), + leftover_lagg = fill(mv, n), + leftover_gravel = fill(mv, n), + store_clay = fill(mv, n), + store_silt = fill(mv, n), + store_sand = fill(mv, n), + store_sagg = fill(mv, n), + store_lagg = fill(mv, n), + store_gravel = fill(mv, n), + ) + return vars +end + +@get_units @with_kw struct SedimentRiverTransportBC{T} + # Waterlevel + waterlevel::Vector{T} | "t dt-1" + # Discharge + q::Vector{T} | "m3 s-1" + # Transport capacity of the flow + transport_capacity::Vector{T} | "t dt-1" + # Sediment input from land erosion + erosion_land_clay::Vector{T} | "t dt-1" + erosion_land_silt::Vector{T} | "t dt-1" + erosion_land_sand::Vector{T} | "t dt-1" + erosion_land_sagg::Vector{T} | "t dt-1" + erosion_land_lagg::Vector{T} | "t dt-1" + # Sediment available from direct river erosion + potential_erosion_river::Vector{T} | "t dt-1" +end + +function sediment_river_transport_bc(n) + bc = SedimentRiverTransportBC(; + waterlevel = fill(mv, n), + q = fill(mv, n), + transport_capacity = fill(mv, n), + erosion_land_clay = fill(mv, n), + erosion_land_silt = fill(mv, n), + erosion_land_sand = fill(mv, n), + erosion_land_sagg = fill(mv, n), + erosion_land_lagg = fill(mv, n), + potential_erosion_river = fill(mv, n), + ) + return bc +end + +# Parameters for river transport +@get_units @with_kw struct SedimentRiverTransportParameters{T} + # River bed/bank content clay + clay_fraction::Vector{T} | "-" + # River bed/bank content silt + silt_fraction::Vector{T} | "-" + # River bed/bank content sand + sand_fraction::Vector{T} | "-" + # River bed/bank content gravel + gravel_fraction::Vector{T} | "-" + # Clay mean diameter + dm_clay::Vector{T} | "µm" + # Silt mean diameter + dm_silt::Vector{T} | "µm" + # Sand mean diameter + dm_sand::Vector{T} | "µm" + # Small aggregates mean diameter + dm_sagg::Vector{T} | "µm" + # Large aggregates mean diameter + dm_lagg::Vector{T} | "µm" + # Gravel mean diameter + dm_gravel::Vector{T} | "µm" + # Waterbodies outlets + waterbodies_locs::Vector{Bool} | "-" + # Waterbodies area + waterbodies_area::Vector{T} | "m2" + # Waterbodies trapping efficiency + waterbodies_trapping_efficiency::Vector{T} | "-" +end + +function initialize_sediment_river_transport_params(nc, config, inds) + n = length(inds) + clay_fraction = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.clay_fraction"; + sel = inds, + defaults = 0.15, + type = Float, + ) + silt_fraction = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.silt_fraction"; + sel = inds, + defaults = 0.65, + type = Float, + ) + sand_fraction = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.sand_fraction"; + sel = inds, + defaults = 0.15, + type = Float, + ) + gravel_fraction = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.sagg_fraction"; + sel = inds, + defaults = 0.05, + type = Float, + ) + # Check that river fractions sum to 1 + river_fractions = clay_fraction + silt_fraction + sand_fraction + gravel_fraction + if any(abs.(river_fractions .- 1.0) .> 1e-3) + error("Particle fractions in the river bed must sum to 1") + end + dm_clay = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.dm_clay"; + sel = inds, + defaults = 2.0, + type = Float, + ) + dm_silt = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.dm_silt"; + sel = inds, + defaults = 10.0, + type = Float, + ) + dm_sand = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.dm_sand"; + sel = inds, + defaults = 200.0, + type = Float, + ) + dm_sagg = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.dm_sagg"; + sel = inds, + defaults = 30.0, + type = Float, + ) + dm_lagg = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.dm_lagg"; + sel = inds, + defaults = 500.0, + type = Float, + ) + dm_gravel = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.dm_gravel"; + sel = inds, + defaults = 2000.0, + type = Float, + ) + # Waterbodies + wblocs = zeros(Float, n) + wbarea = zeros(Float, n) + wbtrap = zeros(Float, n) + do_reservoirs = get(config.model, "doreservoir", false)::Bool + do_lakes = get(config.model, "dolake", false)::Bool + + if do_reservoirs + reslocs = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.reslocs"; + optional = false, + sel = inds, + type = Float, + fill = 0, + ) + resarea = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.resarea"; + optional = false, + sel = inds, + type = Float, + fill = 0.0, + ) + restrapefficiency = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.restrapeff"; + optional = false, + sel = inds, + type = Float, + defaults = 1.0, + fill = 0.0, + ) + wblocs = wblocs .+ reslocs + wbarea = wbarea .+ resarea + wbtrap = wbtrap .+ restrapefficiency + end + + if do_lakes + lakelocs = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.lakelocs"; + optional = false, + sel = inds, + type = Float, + fill = 0, + ) + lakearea = ncread( + nc, + config, + "lateral.river.sediment_flux.parameters.lakearea"; + optional = false, + sel = inds, + type = Float, + fill = 0.0, + ) + wblocs = wblocs .+ lakelocs + wbarea = wbarea .+ lakearea + end + + river_parameters = SedimentRiverTransportParameters(; + clay_fraction = clay_fraction, + silt_fraction = silt_fraction, + sand_fraction = sand_fraction, + gravel_fraction = gravel_fraction, + dm_clay = dm_clay, + dm_silt = dm_silt, + dm_sand = dm_sand, + dm_sagg = dm_sagg, + dm_lagg = dm_lagg, + dm_gravel = dm_gravel, + waterbodies_locs = wblocs .> 0, + waterbodies_area = wbarea, + waterbodies_trapping_efficiency = wbtrap, + ) + + return river_parameters +end + +@get_units @with_kw struct SedimentRiverTransportModel{T} <: + AbstractSedimentRiverTransportModel + boundary_conditions::SedimentRiverTransportBC{T} | "-" + parameters::SedimentRiverTransportParameters{T} | "-" + variables::SedimentRiverTransportVars{T} | "-" +end + +function initialize_sediment_river_transport_model(nc, config, inds) + n = length(inds) + vars = sediment_river_transport_vars(n) + params = initialize_sediment_river_transport_params(nc, config, inds) + bc = sediment_river_transport_bc(n) + model = SedimentRiverTransportModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeometry, ts) + (; + waterlevel, + q, + transport_capacity, + erosion_land_clay, + erosion_land_silt, + erosion_land_sand, + erosion_land_sagg, + erosion_land_lagg, + potential_erosion_river_bed, + potential_erosion_river_bank, + ) = model.boundary_conditions + (; clay_fraction, silt_fraction, sand_fraction, gravel_fraction) = model.parameters + (; + amount, + clay, + silt, + sand, + sagg, + lagg, + gravel, + deposition, + erosion, + leftover_clay, + leftover_silt, + leftover_sand, + leftover_sagg, + leftover_lagg, + leftover_gravel, + store_clay, + store_silt, + store_sand, + store_sagg, + store_lagg, + store_gravel, + ) = model.variables + + @unpack graph, order = network + + # Sediment transport - water balance in the river + for v in order + ### Sediment input in the cell (left from previous timestep + from land + from upstream outflux) ### + input_clay = leftover_clay[v] + erosion_land_clay[v] + input_silt = leftover_silt[v] + erosion_land_silt[v] + input_sand = leftover_sand[v] + erosion_land_sand[v] + input_sagg = leftover_sagg[v] + erosion_land_sagg[v] + input_lagg = leftover_lagg[v] + erosion_land_lagg[v] + input_gravel = leftover_gravel[v] + + # Add upstream contribution + upstream_nodes = inneighbors(graph, v) + if !isempty(upstream_nodes) + for i in upstream_nodes + if clay[i] >= 0.0 # avoid NaN from upstream non-river cells + input_clay += clay[i] + input_silt += silt[i] + input_sand += sand[i] + input_sagg += sagg[i] + input_lagg += lagg[i] + input_gravel += gravel[i] + end + end + end + + input_sediment = + input_clay + input_silt + input_sand + input_sagg + input_lagg + input_gravel + + ### River erosion ### + # Erosion only if the load is below the transport capacity of the flow. + sediment_need = max(transport_capacity - input_sediment, 0.0) + # No erosion in reservoirs + if waterbodies[v] + sediment_need = 0.0 + end + + # Available sediment stored from previous deposition + store_sediment = + store_clay[v] + + store_silt[v] + + store_sand[v] + + store_sagg[v] + + store_lagg[v] + + store_gravel[v] + + # Direct erosion from the river bed/bank + if sediment_need > store_sediment + # Effective sediment needed fom river bed and bank erosion [ton] + effsediment_need = sediment_need - store_sediment + # Relative potential erosion rates of the bed and the bank [-] + if (potential_erosion_river_bank + potential_erosion_river_bed > 0.0) + RTEbank = + potential_erosion_river_bank / + (potential_erosion_river_bank + potential_erosion_river_bed) + else + RTEbank = 0.0 + end + RTEbed = 1.0 - RTEbank + + # Actual bed and bank erosion + erosion_bank = max(RTEbank * effsediment_need, potential_erosion_river_bank) + erosion_bed = max(RTEbed * effsediment_need, potential_erosion_river_bed) + erosion_river = erosion_bank + erosion_bed + # Per particle + erosion_clay = erosion_river * clay_fraction[v] + erosion_silt = erosion_river * silt_fraction[v] + erosion_sand = erosion_river * sand_fraction[v] + erosion_gravel = erosion_river * gravel_fraction[v] + # No small and large aggregates in the river bed/bank + erosion_sagg = 0.0 + erosion_lagg = 0.0 + else + erosion_clay = 0.0 + erosion_silt = 0.0 + erosion_sand = 0.0 + erosion_gravel = 0.0 + erosion_sagg = 0.0 + erosion_lagg = 0.0 + end + + # Erosion/degradation of the previously deposited sediment (from clay to gravel) [ton] + if sediment_need > 0.0 + # Erosion in priority of the smaller particles + # Clay + if store_clay[v] > 0.0 + erosion_store_clay, sediment_need, store_clay[v] = + river_erosion_store(sediment_need, store_clay[v]) + # Update the clay erosion + erosion_clay += erosion_store_clay + end + # Silt + if store_silt[v] > 0.0 + erosion_store_silt, sediment_need, store_silt[v] = + river_erosion_store(sediment_need, store_silt[v]) + # Update the silt erosion + erosion_silt += erosion_store_silt + end + # Small aggregates + if store_sagg[v] > 0.0 + erosion_store_sagg, sediment_need, store_sagg[v] = + river_erosion_store(sediment_need, store_sagg[v]) + # Update the sagg erosion + erosion_sagg += erosion_store_sagg + end + # Sand + if store_sand[v] > 0.0 + erosion_store_sand, sediment_need, store_sand[v] = + river_erosion_store(sediment_need, store_sand[v]) + # Update the sand erosion + erosion_sand += erosion_store_sand + end + # Large aggregates + if store_lagg[v] > 0.0 + erosion_store_lagg, sediment_need, store_lagg[v] = + river_erosion_store(sediment_need, store_lagg[v]) + # Update the lagg erosion + erosion_lagg += erosion_store_lagg + end + # Gravel + if store_gravel[v] > 0.0 + erosion_store_gravel, sediment_need, store_gravel[v] = + river_erosion_store(sediment_need, store_gravel[v]) + # Update the gravel erosion + erosion_gravel += erosion_store_gravel + end + end + + # Compute total erosion + erosion[v] = + erosion_clay + + erosion_silt + + erosion_sand + + erosion_sagg + + erosion_lagg + + erosion_gravel + + ### Deposition / settling ### + # Different deposition if waterbody outlet or river + if waterbodies_locs[v] + # Deposition in waterbodies outlets + deposition_clay = reservoir_deposition_camp( + (input_clay + erosion_clay), + q[v], + waterlevel[v], + waterbodies_area[v], + waterbodies_trapping_efficiency[v], + dm_clay[v], + geometry.slope[v], + ) + deposition_silt = reservoir_deposition_camp( + (input_silt + erosion_silt), + q[v], + waterlevel[v], + waterbodies_area[v], + waterbodies_trapping_efficiency[v], + dm_silt[v], + geometry.slope[v], + ) + deposition_sand = reservoir_deposition_camp( + (input_sand + erosion_sand), + q[v], + waterlevel[v], + waterbodies_area[v], + waterbodies_trapping_efficiency[v], + dm_sand[v], + geometry.slope[v], + ) + deposition_sagg = reservoir_deposition_camp( + (input_sagg + erosion_sagg), + q[v], + waterlevel[v], + waterbodies_area[v], + waterbodies_trapping_efficiency[v], + dm_sagg[v], + geometry.slope[v], + ) + deposition_lagg = reservoir_deposition_camp( + (input_lagg + erosion_lagg), + q[v], + waterlevel[v], + waterbodies_area[v], + waterbodies_trapping_efficiency[v], + dm_lagg[v], + geometry.slope[v], + ) + deposition_gravel = reservoir_deposition_camp( + (input_gravel + erosion_gravel), + q[v], + waterlevel[v], + waterbodies_area[v], + waterbodies_trapping_efficiency[v], + dm_gravel[v], + geometry.slope[v], + ) + elseif waterbodies[v] + # No deposition in waterbodies, only at the outlets + deposition_clay = 0.0 + deposition_silt = 0.0 + deposition_sand = 0.0 + deposition_sagg = 0.0 + deposition_lagg = 0.0 + deposition_gravel = 0.0 + else + # Deposition in the river + # From trasnport capacity exceedance + excess_sediment = max(input_sediment - transport_capacity, 0.0) + if excess_sediment > 0.0 + # Sediment deposited in the channel (from gravel to clay) [ton] + # Gravel + deposition_gravel = ifelse( + excess_sediment > (input_gravel + erosion_gravel), + (input_gravel + erosion_gravel), + excess_sediment, + ) + excess_sediment = max(excess_sediment - deposition_gravel, 0.0) + # Large aggregates + deposition_lagg = ifelse( + excess_sediment > (input_lagg + erosion_lagg), + (input_lagg + erosion_lagg), + excess_sediment, + ) + excess_sediment = max(excess_sediment - deposition_lagg, 0.0) + # Sand + deposition_sand = ifelse( + excess_sediment > (input_sand + erosion_sand), + (input_sand + erosion_sand), + excess_sediment, + ) + excess_sediment = max(excess_sediment - deposition_sand, 0.0) + # Small aggregates + deposition_sagg = ifelse( + excess_sediment > (input_sagg + erosion_sagg), + (input_sagg + erosion_sagg), + excess_sediment, + ) + excess_sediment = max(excess_sediment - deposition_sagg, 0.0) + # Silt + deposition_silt = ifelse( + excess_sediment > (input_silt + erosion_silt), + (input_silt + erosion_silt), + excess_sediment, + ) + excess_sediment = max(excess_sediment - deposition_silt, 0.0) + # Clay + deposition_clay = ifelse( + excess_sediment > (input_clay + erosion_clay), + (input_clay + erosion_clay), + excess_sediment, + ) + excess_sediment = max(excess_sediment - deposition_clay, 0.0) + else + # Natural deposition from Einstein's formula (density controlled) + # Particle fall velocity [m/s] from Stokes + xs = ifelse( + q[v] > 0.0, + 1.055 * geometry.length[v] / (q[v] / geometry.width[v]), + 0.0, + ) + xclay = + min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_clay[v] / 1000)^2 / 3600))) + deposition_clay = xclay * (input_clay + erosion_clay) + xsilt = + min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_silt[v] / 1000)^2 / 3600))) + deposition_silt = xsilt * (input_silt + erosion_silt) + xsand = + min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_sand[v] / 1000)^2 / 3600))) + deposition_sand = xsand * (input_sand + erosion_sand) + xsagg = + min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_sagg[v] / 1000)^2 / 3600))) + deposition_sagg = xsagg * (input_sagg + erosion_sagg) + xlagg = + min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_lagg[v] / 1000)^2 / 3600))) + deposition_lagg = xlagg * (input_lagg + erosion_lagg) + xgrav = + min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_grav[v] / 1000)^2 / 3600))) + deposition_gravel = xgrav * (input_gravel + erosion_gravel) + end + end + + # Update the sediment store + store_clay[v] += deposition_clay + store_silt[v] += deposition_silt + store_sand[v] += deposition_sand + store_sagg[v] += deposition_sagg + store_lagg[v] += deposition_lagg + store_gravel[v] += deposition_gravel + + # Compute total deposition + deposition[v] = + deposition_clay + + deposition_silt + + deposition_sand + + deposition_sagg + + deposition_lagg + + deposition_gravel + + ### Output loads ### + # Sediment transported out of the cell during the timestep [ton] + # 0 in case all sediment are deposited in the cell + # Reduce the fraction so that there is still some sediment staying in the river cell + fwaterout = + min(q[v] * ts / (waterlevel[v] * geometry.width[v] * geometry.length[v]), 1.0) + clay = fwaterout * (input_clay + erosion_clay - deposition_clay) + silt = fwaterout * (input_silt + erosion_silt - deposition_silt) + sand = fwaterout * (input_sand + erosion_sand - deposition_sand) + sagg = fwaterout * (input_sagg + erosion_sagg - deposition_sagg) + lagg = fwaterout * (input_lagg + erosion_lagg - deposition_lagg) + gravel = fwaterout * (input_gravel + erosion_gravel - deposition_gravel) + + amount = clay + silt + sand + sagg + lagg + gravel + + ### Leftover / mass balance ### + # Sediment left in the cell [ton] + leftover_clay[v] = input_clay + erosion_clay - deposition_clay - clay + leftover_silt[v] = input_silt + erosion_silt - deposition_silt - silt + leftover_sand[v] = input_sand + erosion_sand - deposition_sand - sand + leftover_sagg[v] = input_sagg + erosion_sagg - deposition_sagg - sagg + leftover_lagg[v] = input_lagg + erosion_lagg - deposition_lagg - lagg + leftover_gravel[v] = input_gravel + erosion_gravel - deposition_gravel - gravel + end +end + +abstract type AbstractSedimentConcentrationsRiverModel end + +## Total sediment transport in overland flow structs and functions +@get_units @with_kw struct SedimentConcentrationsRiverVars{T} + # Total sediment concentration in the river + total::Vector{T} | "g m-3" + # suspended sediemnt concentration in the river + suspended::Vector{T} | "g m-3" + # bed load sediment concentration in the river + bed::Vector{T} | "g m-3" +end + +function sediment_concentrations_river_vars(n) + vars = SedimentConcentrationsRiverVars(; + total = fill(mv, n), + suspended = fill(mv, n), + bed = fill(mv, n), + ) + return vars +end + +@get_units @with_kw struct SedimentConcentrationsRiverBC{T} + # Discharge + q::Vector{T} | "m3 s-1" + waterlevel::Vector{T} | "m" + # Clay load + clay::Vector{T} | "g m-3" + # Silt load + silt::Vector{T} | "g m-3" + # Sand load + sand::Vector{T} | "g m-3" + # Small aggregates load + sagg::Vector{T} | "g m-3" + # Large aggregates load + lagg::Vector{T} | "g m-3" + # Gravel load + gravel::Vector{T} | "g m-3" +end + +function sediment_concentrations_river_bc(n) + bc = SedimentConcentrationsRiverBC(; + q = fill(mv, n), + waterlevel = fill(mv, n), + clay = fill(mv, n), + silt = fill(mv, n), + sand = fill(mv, n), + sagg = fill(mv, n), + lagg = fill(mv, n), + gravel = fill(mv, n), + ) + return bc +end + +# Common parameters for transport capacity models +@get_units @with_kw struct SedimentConcentrationsRiverParameters{T} + # Clay mean diameter + dm_clay::Vector{T} | "µm" + # Silt mean diameter + dm_silt::Vector{T} | "µm" + # Sand mean diameter + dm_sand::Vector{T} | "µm" + # Small aggregates mean diameter + dm_sagg::Vector{T} | "µm" + # Large aggregates mean diameter + dm_lagg::Vector{T} | "µm" + # Gravel mean diameter + dm_gravel::Vector{T} | "µm" +end + +function initialize_sediment_concentrations_river_params(nc, config, inds) + dm_clay = ncread( + nc, + config, + "lateral.river.concentrations.parameters.dm_clay"; + sel = inds, + defaults = 2.0, + type = Float, + ) + dm_silt = ncread( + nc, + config, + "lateral.river.concentrations.parameters.dm_silt"; + sel = inds, + defaults = 10.0, + type = Float, + ) + dm_sand = ncread( + nc, + config, + "lateral.river.concentrations.parameters.dm_sand"; + sel = inds, + defaults = 200.0, + type = Float, + ) + dm_sagg = ncread( + nc, + config, + "lateral.river.concentrations.parameters.dm_sagg"; + sel = inds, + defaults = 30.0, + type = Float, + ) + dm_lagg = ncread( + nc, + config, + "lateral.river.concentrations.parameters.dm_lagg"; + sel = inds, + defaults = 500.0, + type = Float, + ) + dm_gravel = ncread( + nc, + config, + "lateral.river.concentrations.parameters.dm_gravel"; + sel = inds, + defaults = 2000.0, + type = Float, + ) + conc_parameters = SedimentConcentrationsRiverParameters(; + dm_clay = dm_clay, + dm_silt = dm_silt, + dm_sand = dm_sand, + dm_sagg = dm_sagg, + dm_lagg = dm_lagg, + dm_gravel = dm_gravel, + ) + + return conc_parameters +end + +@get_units @with_kw struct SedimentConcentrationsRiverModel{T} <: + AbstractSedimentConcentrationsRiverModel + boundary_conditions::SedimentConcentrationsRiverBC{T} | "-" + parameters::SedimentConcentrationsRiverParameters{T} | "-" + variables::SedimentConcentrationsRiverVars{T} | "-" +end + +function initialize_sediment_concentrations_river_model(nc, config, inds) + n = length(inds) + vars = sediment_concentrations_river_vars(n) + params = initialize_sediment_concentrations_river_params(nc, config, inds) + bc = sediment_concentrations_river_bc(n) + model = SedimentConcentrationsRiverModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::SedimentConcentrationsRiverModel, geometry::RiverGeometry, ts) + (; q, waterlevel, clay, silt, sand, sagg, lagg, gravel) = model.boundary_conditions + (; dm_clay, dm_silt, dm_sand, dm_sagg, dm_lagg, dm_gravel) = model.parameters + (; total, suspended, bed) = model.variables + + zeros = zeros(Float, length(q)) + # Conversion from load [ton] to concentration for rivers [mg/L] + toconc .= ifelse.(q .> 0.0, 1e6 ./ (q .* ts), zeros) + + # Differentiation of bed and suspended load using Rouse number for suspension + # threshold diameter between bed load and mixed load using Rouse number + dbedf .= + 1e3 .* + (2.5 .* 3600 .* 0.41 ./ 411 .* (9.81 .* waterlevel .* geometry.slope) .^ 0.5) .^ 0.5 + # threshold diameter between suspended load and mixed load using Rouse number + dsuspf .= + 1e3 .* + (1.2 .* 3600 .* 0.41 ./ 411 .* (9.81 .* waterlevel .* geometry.slope) .^ 0.5) .^ 0.5 + + # Rouse with diameter + SSclay .= + ifelse.(dm_clay .<= dsuspf, clay, ifelse.(dm_clay .<= dbedf, clay ./ 2, zeros)) + SSsilt .= + ifelse.(dm_silt .<= dsuspf, silt, ifelse.(dm_silt .<= dbedf, silt ./ 2, zeros)) + SSsand .= + ifelse.(dm_sand .<= dsuspf, sand, ifelse.(dm_sand .<= dbedf, sand ./ 2, zeros)) + SSsagg .= + ifelse.(dm_sagg .<= dsuspf, sagg, ifelse.(dm_sagg .<= dbedf, sagg ./ 2, zeros)) + SSlagg .= + ifelse.(dm_lagg .<= dsuspf, lagg, ifelse.(dm_lagg .<= dbedf, lagg ./ 2, zeros)) + SSgrav .= + ifelse.( + dm_gravel .<= dsuspf, + gravel, + ifelse.(dm_gravel .<= dbedf, gravel ./ 2, zeros), + ) + + SS .= SSclay .+ SSsilt .+ SSsagg .+ SSsand .+ SSlagg .+ SSgrav + Bedload .= (clay .+ silt .+ sagg .+ sand .+ lagg .+ gravel) .- SS + + suspended .= SS .* toconc + bed .= Bedload .* toconc + total .= (clay .+ silt .+ sagg .+ sand .+ lagg .+ gravel) .* toconc +end \ No newline at end of file diff --git a/src/sediment_transport/transport_capacity.jl b/src/sediment_transport/transport_capacity.jl index c3fbe41cc..c0d4f999c 100644 --- a/src/sediment_transport/transport_capacity.jl +++ b/src/sediment_transport/transport_capacity.jl @@ -23,7 +23,9 @@ function transport_capacity_bc(n) return bc end -# Common parameters for transport capacity models +##################### Overland Flow ##################### + +# Govers parameters for transport capacity models @get_units @with_kw struct TransportCapacityGoversParameters{T} # Drain slope slope::Vector{T} | "m m-1" @@ -230,8 +232,6 @@ end # Common parameters for transport capacity models @get_units @with_kw struct TransportCapacityYalinDifferentiationParameters{T} - # Drain slope - slope::Vector{T} | "m m-1" # Particle density density::Vector{T} | "kg m-3" # Clay mean diameter @@ -247,14 +247,6 @@ end end function initialize_transport_capacity_yalin_diff_params(nc, config, inds) - slope = ncread( - nc, - config, - "lateral.land.transport_capacity.parameters.slope"; - sel = inds, - defaults = 0.01, - type = Float, - ) density = ncread( nc, config, @@ -304,7 +296,6 @@ function initialize_transport_capacity_yalin_diff_params(nc, config, inds) type = Float, ) tc_parameters = TransportCapacityYalinDifferentiationParameters(; - slope = slope, density = density, dm_clay = dm_clay, dm_silt = dm_silt, @@ -421,4 +412,325 @@ function update!( ) amount[i] = clay[i] + silt[i] + sand[i] + sagg[i] + lagg[i] end +end + +##################### River Flow ##################### +@get_units @with_kw struct TransportCapacityRiverParameters{T} + # Particle density + density::Vector{T} | "kg m-3" + # Particle mean diameter + d50::Vector{T} | "mm" +end + +function initialize_transport_capacity_river_params(nc, config, inds) + density = ncread( + nc, + config, + "lateral.river.transport_capacity.parameters.density"; + sel = inds, + defaults = 2650.0, + type = Float, + ) + d50 = ncread( + nc, + config, + "lateral.river.transport_capacity.parameters.d50"; + sel = inds, + defaults = 0.1, + type = Float, + ) + tc_parameters = TransportCapacityRiverParameters(; density = density, d50 = d50) + + return tc_parameters +end + +# Bagnold parameters for transport capacity models +@get_units @with_kw struct TransportCapacityBagnoldParameters{T} + # Bagnold transport capacity coefficient + c_bagnold::Vector{T} | "-" + # Bagnold transport capacity exponent + e_bagnold::Vector{T} | "-" +end + +function initialize_transport_capacity_bagnold_params(nc, config, inds) + c_bagnold = ncread( + nc, + config, + "lateral.river.transport_capacity.parameters.c_bagnold"; + sel = inds, + optional = false, + type = Float, + ) + e_bagnold = ncread( + nc, + config, + "lateral.river.transport_capacity.parameters.e_bagnold"; + sel = inds, + optional = false, + type = Float, + ) + tc_parameters = + TransportCapacityBagnoldParameters(; c_bagnold = c_bagnold, e_bagnold = e_bagnold) + + return tc_parameters +end + +@get_units @with_kw struct TransportCapacityBagnoldModel{T} <: + AbstractTransportCapacityModel + boundary_conditions::TransportCapacityBC{T} | "-" + parameters::TransportCapacityBagnoldParameters{T} | "-" + variables::TransportCapacityModelVars{T} | "-" +end + +function initialize_transport_capacity_bagnold_model(nc, config, inds) + n = length(inds) + vars = transport_capacity_model_vars(n) + params = initialize_transport_capacity_bagnold_params(nc, config, inds) + bc = transport_capacity_bc(n) + model = TransportCapacityBagnoldModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::TransportCapacityBagnoldModel, geometry::RiverGeometry, ts) + (; q, waterlevel) = model.boundary_conditions + (; c_bagnold, e_bagnold) = model.parameters + (; amount) = model.variables + + n = length(q) + # Note: slope is not used here but this allows for a consistent interface of update! functions + # Only Bagnold does not use it + threaded_foreach(1:n; basesize = 1000) do i + amount[i] = transport_capacity_bagnold( + q[i], + waterlevel[i], + c_bagnold[i], + e_bagnold[i], + geometry.width[i], + geometry.length[i], + ts, + ) + end +end + +# Engelund and Hansen parameters for transport capacity models +@get_units @with_kw struct TransportCapacityEngelundModel{T} <: + AbstractTransportCapacityModel + boundary_conditions::TransportCapacityBC{T} | "-" + parameters::TransportCapacityRiverParameters{T} | "-" + variables::TransportCapacityModelVars{T} | "-" +end + +function initialize_transport_capacity_engelund_model(nc, config, inds) + n = length(inds) + vars = transport_capacity_model_vars(n) + params = initialize_transport_capacity_river_params(nc, config, inds) + bc = transport_capacity_bc(n) + model = TransportCapacityEngelundModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::TransportCapacityEngelundModel, geometry::RiverGeometry, ts) + (; q, waterlevel) = model.boundary_conditions + (; density, d50) = model.parameters + (; amount) = model.variables + + n = length(q) + threaded_foreach(1:n; basesize = 1000) do i + amount[i] = transport_capacity_engelund( + q[i], + waterlevel[i], + density[i], + d50[i], + geometry.width[i], + geometry.length[i], + geometry.slope[i], + ts, + ) + end +end + +# Kodatie parameters for transport capacity models +@get_units @with_kw struct TransportCapacityKodatieParameters{T} + # Kodatie transport capacity coefficient a + a_kodatie::Vector{T} | "-" + # Kodatie transport capacity coefficient b + b_kodatie::Vector{T} | "-" + # Kodatie transport capacity coefficient c + c_kodatie::Vector{T} | "-" + # Kodatie transport capacity coefficient d + d_kodatie::Vector{T} | "-" +end + +function initialize_transport_capacity_kodatie_params(nc, config, inds) + a_kodatie = ncread( + nc, + config, + "lateral.river.transport_capacity.parameters.a_kodatie"; + sel = inds, + optional = false, + type = Float, + ) + b_kodatie = ncread( + nc, + config, + "lateral.river.transport_capacity.parameters.b_kodatie"; + sel = inds, + optional = false, + type = Float, + ) + c_kodatie = ncread( + nc, + config, + "lateral.river.transport_capacity.parameters.c_kodatie"; + sel = inds, + optional = false, + type = Float, + ) + d_kodatie = ncread( + nc, + config, + "lateral.river.transport_capacity.parameters.d_kodatie"; + sel = inds, + optional = false, + type = Float, + ) + tc_parameters = TransportCapacityKodatieParameters(; + a_kodatie = a_kodatie, + b_kodatie = b_kodatie, + c_kodatie = c_kodatie, + d_kodatie = d_kodatie, + ) + + return tc_parameters +end + +@get_units @with_kw struct TransportCapacityKodatieModel{T} <: + AbstractTransportCapacityModel + boundary_conditions::TransportCapacityBC{T} | "-" + parameters::TransportCapacityKodatieParameters{T} | "-" + variables::TransportCapacityModelVars{T} | "-" +end + +function initialize_transport_capacity_kodatie_model(nc, config, inds) + n = length(inds) + vars = transport_capacity_model_vars(n) + params = initialize_transport_capacity_kodatie_params(nc, config, inds) + bc = transport_capacity_bc(n) + model = TransportCapacityKodatieModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::TransportCapacityKodatieModel, geometry::RiverGeometry, ts) + (; q, waterlevel) = model.boundary_conditions + (; a_kodatie, b_kodatie, c_kodatie, d_kodatie) = model.parameters + (; amount) = model.variables + + n = length(q) + threaded_foreach(1:n; basesize = 1000) do i + amount[i] = transport_capacity_kodatie( + q[i], + waterlevel[i], + a_kodatie[i], + b_kodatie[i], + c_kodatie[i], + d_kodatie[i], + geometry.width[i], + geometry.length[i], + geometry.slope[i], + ts, + ) + end +end + +# Yang parameters for transport capacity models +@get_units @with_kw struct TransportCapacityYangModel{T} <: AbstractTransportCapacityModel + boundary_conditions::TransportCapacityBC{T} | "-" + parameters::TransportCapacityRiverParameters{T} | "-" + variables::TransportCapacityModelVars{T} | "-" +end + +function initialize_transport_capacity_yang_model(nc, config, inds) + n = length(inds) + vars = transport_capacity_model_vars(n) + params = initialize_transport_capacity_river_params(nc, config, inds) + bc = transport_capacity_bc(n) + model = TransportCapacityYangModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::TransportCapacityYangModel, geometry::RiverGeometry, ts) + (; q, waterlevel) = model.boundary_conditions + (; density, d50) = model.parameters + (; amount) = model.variables + + n = length(q) + threaded_foreach(1:n; basesize = 1000) do i + amount[i] = transport_capacity_yang( + q[i], + waterlevel[i], + density[i], + d50[i], + geometry.width[i], + geometry.length[i], + geometry.slope[i], + ts, + ) + end +end + +# Molinas and Wu parameters for transport capacity models +@get_units @with_kw struct TransportCapacityMolinasModel{T} <: + AbstractTransportCapacityModel + boundary_conditions::TransportCapacityBC{T} | "-" + parameters::TransportCapacityRiverParameters{T} | "-" + variables::TransportCapacityModelVars{T} | "-" +end + +function initialize_transport_capacity_molinas_model(nc, config, inds) + n = length(inds) + vars = transport_capacity_model_vars(n) + params = initialize_transport_capacity_river_params(nc, config, inds) + bc = transport_capacity_bc(n) + model = TransportCapacityMolinasModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) + return model +end + +function update!(model::TransportCapacityMolinasModel, geometry::RiverGeometry, ts) + (; q, waterlevel) = model.boundary_conditions + (; density, d50) = model.parameters + (; amount) = model.variables + + n = length(q) + threaded_foreach(1:n; basesize = 1000) do i + amount[i] = transport_capacity_molinas( + q[i], + waterlevel[i], + density[i], + d50[i], + geometry.width[i], + geometry.length[i], + geometry.slope[i], + ts, + ) + end end \ No newline at end of file diff --git a/src/sediment_transport/transport_capacity_process.jl b/src/sediment_transport/transport_capacity_process.jl index 7a900bded..f4b85e4df 100644 --- a/src/sediment_transport/transport_capacity_process.jl +++ b/src/sediment_transport/transport_capacity_process.jl @@ -29,6 +29,45 @@ function mask_transport_capacity(transport_capacity, waterbodies, rivers) return tc end +""" + limit_and_convert_transport_capacity( + transport_capacity, + q, + waterlevel, + width, + length, + ts, + ) + +Limit to stremaflow and not debris flow and convert transport in ton/m3 to ton. + +# Arguments +- `transport_capacity` (total sediment transport capacity [t m-3]) +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `width` (drain width [m]) +- `length` (drain length [m]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function limit_and_convert_transport_capacity( + transport_capacity, + q, + waterlevel, + width, + length, + ts, +) + # 1285 g/L: boundary between streamflow and debris flow (Costa, 1988) + transport_capacity = min(transport_capacity, 1.285) + # Transport capacity [ton] + transport_capacity = transport_capacity * (waterlevel * width * length + q * ts) + + return transport_capacity +end + """ transport_capacity_govers( q, @@ -283,5 +322,327 @@ function transport_capacity_yalin_differentiation( # Mask transport capacity values for waterbodies and rivers transport_capacity = mask_transport_capacity(transport_capacity, waterbodies, rivers) + return transport_capacity +end + +""" + function trasnport_capacity_bagnold( + q, + waterlevel, + c_bagnold, + e_bagnold, + width, + length, + ts, + ) + +Total sediment transport capacity based on Bagnold. + +# Arguments +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `c_bagnold` (Bagnold transport capacity coefficient [-]) +- `e_bagnold` (Bagnold transport capacity exponent [-]) +- `width` (drain width [m]) +- `length` (drain length [m]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function transport_capacity_bagnold(q, waterlevel, c_bagnold, e_bagnold, width, length, ts) + # Transport capacity from Bagnold + if waterlevel > 0.0 + # Transport capacity [tons/m3] + transport_capacity = c_bagnold * (q / (waterlevel * width))^e_bagnold + transport_capacity = limit_and_convert_transport_capacity( + transport_capacity, + q, + waterlevel, + width, + length, + ts, + ) + else + transport_capacity = 0.0 + end + + return transport_capacity +end + +""" + function trasnport_capacity_engelund( + q, + waterlevel, + density, + d50, + width, + length, + slope, + ts, + ) + +Total sediment transport capacity based on Engelund and Hansen. + +# Arguments +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `density` (sediment density [kg m-3]) +- `d50` (median grain size [m]) +- `width` (drain width [m]) +- `length` (drain length [m]) +- `slope` (slope [-]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function transport_capacity_engelund(q, waterlevel, density, d50, width, length, slope, ts) + # Transport capacity from Engelund and Hansen + if waterlevel > 0.0 + # Hydraulic radius of the river [m] (rectangular channel) + hydrad = waterlevel * width / (width + 2 * waterlevel) + vshear = sqrt(9.81 * hydrad * slope) + + # Flow velocity [m/s] + velocity = (q / (waterlevel * width)) + + # Concentration by weight + cw = + density / 1000 * 0.05 * velocity * vshear^3 / + ((density / 1000 - 1)^2 * 9.81^2 * d50 * hydrad) + cw = min(1.0, cw) + + # Transport capacity [tons/m3] + transport_capacity = cw / (cw + (1 - cw) * density / 1000) * density / 1000 + transport_capacity = max(transport_capacity, 0.0) + # Transport capacity [tons] + transport_capacity = limit_and_convert_transport_capacity( + transport_capacity, + q, + waterlevel, + width, + length, + ts, + ) + + else + transport_capacity = 0.0 + end + + return transport_capacity +end + +""" + function trasnport_capacity_kodatie( + q, + waterlevel, + a_kodatie, + b_kodatie, + c_kodatie, + d_kodatie, + width, + length, + slope, + ts, + ) + +Total sediment transport capacity based on Kodatie. + +# Arguments +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `a_kodatie` (Kodatie transport capacity coefficient [-]) +- `b_kodatie` (Kodatie transport capacity coefficient [-]) +- `c_kodatie` (Kodatie transport capacity coefficient [-]) +- `d_kodatie` (Kodatie transport capacity coefficient [-]) +- `width` (drain width [m]) +- `slope` (slope [-]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function transport_capacity_kodatie( + q, + waterlevel, + a_kodatie, + b_kodatie, + c_kodatie, + d_kodatie, + width, + length, + slope, + ts, +) + # Transport capacity from Kodatie + if waterlevel > 0.0 + # Flow velocity [m/s] + velocity = (q / (waterlevel * width)) + + # Concentration + transport_capacity = + a_kodatie * velocity^b_kodatie * waterlevel^c_kodatie * slope^d_kodatie + + # Transport capacity [tons/m3] + transport_capacity = transport_capacity * width / (q * ts) + transport_capacity = limit_and_convert_transport_capacity( + transport_capacity, + q, + waterlevel, + width, + length, + ts, + ) + + else + transport_capacity = 0.0 + end + + return transport_capacity +end + +""" + function trasnport_capacity_yang( + q, + waterlevel, + density, + d50, + width, + length, + slope, + ts, + ) + +Total sediment transport capacity based on Yang sand and gravel equations. + +# Arguments +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `density` (sediment density [kg m-3]) +- `d50` (median grain size [m]) +- `width` (drain width [m]) +- `length` (drain length [m]) +- `slope` (slope [-]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function transport_capacity_yang(q, waterlevel, density, d50, width, length, slope, ts) + # Transport capacity from Yang + omegas = 411 * d50^2 / 3600 + # Hydraulic radius of the river [m] (rectangular channel) + hydrad = waterlevel * width / (width + 2 * waterlevel) + # Critical shear stress velocity + vshear = sqrt(9.81 * hydrad * slope) + var1 = vshear * d50 / 1000 / (1.16 * 1e-6) + var2 = omegas * d50 / 1000 / (1.16 * 1e-6) + vcr = ifelse(var1 >= 70.0, 2.05 * omegas, omegas * (2.5 / (log10(var1) - 0.06) + 0.66)) + vcr = min(vcr, 0.0) + + # Sand equation + if (width * waterlevel) > vcr && d50 < 2.0 + logcppm = ( + 5.435 - 0.286 * log10(var2) - 0.457 * log10(vshear / omegas) + 1.799 - + 0.409 * log10(var2) - + 0.314 * + log10(vshear / omegas) * + log10((q / (width * waterlevel) - vcr) * slope / omegas) + ) + # Gravel equation + elseif (width * waterlevel) > vcr && d50 >= 2.0 + logcppm = ( + 6.681 - 0.633 * log10(var2) - 4.816 * log10(vshear / omegas) + 2.784 - + 0.305 * log10(var2) - + 0.282 * + log10(vshear / omegas) * + log10((q / (width * waterlevel) - vcr) * slope / omegas) + ) + else + logcppm = 0.0 + end + + # Sediment concentration by weight + cw = 10^logcppm * 1e-6 + # Transport capacity [tons/m3] + transport_capacity = cw / (cw + (1 - cw) * density / 1000) * density / 1000 + transport_capacity = max(transport_capacity, 0.0) + # Transport capacity [tons] + transport_capacity = limit_and_convert_transport_capacity( + transport_capacity, + q, + waterlevel, + width, + length, + ts, + ) + + return transport_capacity +end + +""" + function trasnport_capacity_molinas( + q, + waterlevel, + density, + d50, + width, + length, + slope, + ts, + ) + +Total sediment transport capacity based on Molinas and Wu. + +# Arguments +- `q` (discharge [m3 s-1]) +- `waterlevel` (water level [m]) +- `density` (sediment density [kg m-3]) +- `d50` (median grain size [m]) +- `width` (drain width [m]) +- `length` (drain length [m]) +- `slope` (slope [-]) +- `ts` (time step [s]) + +# Output +- `transport_capacity` (total sediment transport capacity [t dt-1]) +""" +function transport_capacity_molinas(q, waterlevel, density, d50, width, length, slope, ts) + # Transport capacity from Molinas and Wu + if waterlevel > 0.0 + # Flow velocity [m/s] + velocity = (q / (waterlevel * width)) + omegas = 411 * d50^2 / 3600 + + # PSI parameter + psi = ( + velocity^3 / ( + (density / 1000 - 1) * + 9.81 * + waterlevel * + omegas * + log10(1000 * waterlevel / d50)^2 + ) + ) + # Concentration by weight + cw = 1430 * (0.86 + psi^0.5) * psi^1.5 / (0.016 + psi) * 1e-6 + # Transport capacity [tons/m3] + transport_capacity = cw / (cw + (1 - cw) * density / 1000) * density / 1000 + transport_capacity = max(transport_capacity, 0.0) + # Transport capacity [tons] + transport_capacity = limit_and_convert_transport_capacity( + transport_capacity, + q, + waterlevel, + width, + length, + ts, + ) + + else + transport_capacity = 0.0 + end + return transport_capacity end \ No newline at end of file diff --git a/src/states.jl b/src/states.jl index 0987d0984..6d092daba 100644 --- a/src/states.jl +++ b/src/states.jl @@ -183,24 +183,24 @@ function extract_required_states(config::Config) # River states if model_type == "sediment" river_states = ( - :clayload, - :siltload, - :sandload, - :saggload, - :laggload, - :gravload, - :claystore, - :siltstore, - :sandstore, - :saggstore, - :laggstore, - :gravstore, - :outclay, - :outsilt, - :outsand, - :outsagg, - :outlagg, - :outgrav, + :leftover_clay, + :leftover_silt, + :leftover_sand, + :leftover_sagg, + :leftover_lagg, + :leftover_gravel, + :store_clay, + :store_silt, + :store_sand, + :store_sagg, + :store_lagg, + :store_gravel, + :clay, + :silt, + :sand, + :sagg, + :lagg, + :gravel, ) elseif model_type == "sbm" || model_type == "sbm_gwf" river_states = (:q, :h, :h_av) diff --git a/test/run_sediment.jl b/test/run_sediment.jl index 56b161f1f..5bde18826 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -56,10 +56,12 @@ end @test mean(land.to_river.variables.sand) ≈ 0.02575351604673049f0 @test mean(land.sediment_flux.variables.clay) ≈ 0.006578791733506439f0 - # @test mean(lat.river.SSconc) ≈ 0.8259993252994058f0 - # @test mean(lat.river.inlandclay) ≈ 0.01980468760667709f0 - # @test lat.river.h_riv[network.river.order[end]] ≈ 0.006103649735450745f0 - # @test lat.river.outclay[5649] ≈ 2.359031898208781f-9 + @test mean(lat.river.concentrations.variables.suspended) ≈ 0.8259993252994058f0 + @test mean(lat.river.sediment_flux.boundary_conditions.erosion_land_clay) ≈ + 0.01980468760667709f0 + @test lat.river.hydrometeo_forcing.waterlevel_river[network.river.order[end]] ≈ + 0.006103649735450745f0 + @test lat.river.sediment_flux.varibales.clay[5649] ≈ 2.359031898208781f-9 end @testset "Exchange and grid location sediment" begin diff --git a/test/sediment_config.toml b/test/sediment_config.toml index 48abb84db..29a5eea21 100644 --- a/test/sediment_config.toml +++ b/test/sediment_config.toml @@ -18,25 +18,25 @@ path_output = "outstates-moselle-sed.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.lateral.river] -clayload = "clayload" -claystore = "claystore" -gravload = "gravload" -gravstore = "gravstore" -laggload = "laggload" -laggstore = "laggstore" -outclay = "outclay" -outgrav = "outgrav" -outlagg = "outlagg" -outsagg = "outsagg" -outsand = "outsand" -outsilt = "outsilt" -saggload = "saggload" -saggstore = "saggstore" -sandload = "sandload" -sandstore = "sandstore" -siltload = "siltload" -siltstore = "siltstore" +[state.lateral.river.sediment_flux.variables] +leftover_clay = "clayload" +store_clay = "claystore" +leftover_gravel = "gravload" +store_gravel = "gravstore" +leftover_lagg = "laggload" +store_lagg = "laggstore" +clay = "outclay" +gravel = "outgrav" +lagg= "outlagg" +sagg = "outsagg" +sand = "outsand" +silt = "outsilt" +leftover_sagg= "saggload" +store_sagg = "saggstore" +leftover_sand = "sandload" +store_sand = "sandstore" +leftover_silt = "siltload" +store_silt = "siltstore" [input] path_forcing = "forcing-moselle-sed.nc" @@ -59,21 +59,19 @@ forcing = [ "lateral.land.hydrometeo_forcing.waterlevel_land", "vertical.hydrometeo_forcing.q_land", "lateral.land.hydrometeo_forcing.q_land", - "lateral.river.h_riv", - "lateral.river.q_riv", + "lateral.river.hydrometeo_forcing.waterlevel_river", + "lateral.river.hydrometeo_forcing.q_river", ] #cyclic = ["vertical.leaf_area_index"] -[input.vertical] +#[input.vertical] ## interception parameters to be moved #kext = "Kext" #leaf_area_index = "LAI" # cyclic #specific_leaf = "Sl" #storage_wood = "Swood" ## other parameters -pathfrac = "PathFrac" -slope = "Slope" [input.vertical.hydrometeo_forcing] precipitation = "P" @@ -81,6 +79,9 @@ waterlevel_land = "levKinL" #interception = "int" q_land = "runL" +[input.vertical.geometry] +slope = "Slope" + [input.vertical.rainfall_erosion.parameters] soil_detachability = "soil_detachability" eurosem_exponent = "eros_spl_EUROSEM" @@ -88,6 +89,7 @@ canopyheight = "CanopyHeight" canopygapfraction = "CanopyGapFraction" usle_k = "usle_k" usle_c = "USLE_C" +pathfrac = "PathFrac" [input.vertical.overland_flow_erosion.parameters] usle_k = "usle_k" @@ -106,7 +108,6 @@ waterlevel_land = "levKinL" q_land = "runL" [input.lateral.land.transport_capacity.parameters] -slope = "Slope" density = "sediment_density" d50 = "d50_soil" c_govers = "c_govers" @@ -117,30 +118,55 @@ dm_sand = "dm_sand" dm_sagg = "dm_sagg" dm_lagg = "dm_lagg" -[input.lateral.river] -h_riv = "h" -q_riv = "q" -cbagnold = "c_Bagnold" -d50 = "D50_River" -d50engelund = "D50_River" -ebagnold = "exp_Bagnold" -fclayriv = "ClayF_River" -fgravriv = "GravelF_River" -fsandriv = "SandF_River" -fsiltriv = "SiltF_River" +[input.lateral.river.hydrometeo_forcing] +waterlevel_river = "h" +q_river = "q" + +[input.lateral.river.geometry] length = "wflow_riverlength" slope = "RiverSlope" width = "wflow_riverwidth" + +[input.lateral.river.transport_capacity.parameters] +density = "sediment_density" +d50 = "D50_River" +c_bagnold = "c_Bagnold" +e_bagnold = "exp_Bagnold" +a_kodatie = "a_kodatie" +b_kodatie = "b_kodatie" +c_kodatie = "c_kodatie" +d_kodatie = "d_kodatie" + +[input.lateral.river.potential_erosion.parameters] +d50 = "D50_River" + +[input.lateral.river.sediment_flux.parameters] +clay_fraction = "ClayF_River" +silt_fraction = "SiltF_River" +sand_fraction = "SandF_River" +gravel_fraction = "GravelF_River" +dm_clay = "dm_clay" +dm_silt = "dm_silt" +dm_sand = "dm_sand" +dm_sagg = "dm_sagg" +dm_lagg = "dm_lagg" +dm_gravel = "dm_gravel" # Reservoir resarea = "ResSimpleArea" restrapeff = "ResTrapEff" -resareas = "wflow_reservoirareas" reslocs = "wflow_reservoirlocs" # Lake lakearea = "LakeArea" -lakeareas = "wflow_lakeareas" lakelocs = "wflow_lakelocs" +[input.lateral.river.concentrations.parameters] +dm_clay = "dm_clay" +dm_silt = "dm_silt" +dm_sand = "dm_sand" +dm_sagg = "dm_sagg" +dm_lagg = "dm_lagg" +dm_gravel = "dm_gravel" + [model] dolake = false doreservoir = true # cannot use reservoirs as in sbm because then states/volume need to be added @@ -176,15 +202,20 @@ amount = "inlandsed" clay = "olclay" amount = "olsed" -# [output.lateral.river] -# Bedconc = "Bedconc" -# SSconc = "SSconc" -# Sedconc = "Sedconc" -# clayload = "clayload" -# h_riv = "h_riv" -# inlandclay = "inlandclayriv" -# outclay = "outclay" -# width = "widthriv" +[output.lateral.river.concentrations.variables] +bed = "Bedconc" +suspended = "SSconc" +total = "Sedconc" + +[output.lateral.river.hydrometeo_forcing] +waterlevel_river = "h_riv" + +[output.lateral.river.sediment_flux.variables] +leftover_clay = "clayload" +clay = "outclay" + +[output.lateral.river.sediment_flux.boundary_conditions] +erosion_land_clay = "inlandclayriv" [csv] path = "output-moselle-sediment.csv" From 15ff5561194db2f57f74bfea5fc891bee39fa0fb Mon Sep 17 00:00:00 2001 From: hboisgon Date: Wed, 25 Sep 2024 15:06:24 +0800 Subject: [PATCH 24/42] working tests and all results comparable to master --- src/erosion.jl | 2 +- src/erosion/rainfall_erosion.jl | 15 +- src/sediment_flux.jl | 75 ++---- src/sediment_model.jl | 14 +- src/sediment_transport/land_to_river.jl | 17 +- .../overland_flow_transport.jl | 4 +- src/sediment_transport/river_transport.jl | 222 ++++++++++++------ src/sediment_transport/transport_capacity.jl | 43 +++- src/states.jl | 57 +++-- test/run_sediment.jl | 30 ++- test/sediment_config.toml | 5 +- 11 files changed, 304 insertions(+), 180 deletions(-) diff --git a/src/erosion.jl b/src/erosion.jl index 18ad50797..696f7b79e 100644 --- a/src/erosion.jl +++ b/src/erosion.jl @@ -54,7 +54,7 @@ function update!(model::SoilLoss, dt) #TODO add interception/canopygapfraction calculation here for eurosem #need SBM refactor # Rainfall erosion - update!(model.rainfall_erosion, model.hydrometeo_forcing, model.area, ts) + update!(model.rainfall_erosion, model.hydrometeo_forcing, model.geometry, ts) # Overland flow erosion update!(model.overland_flow_erosion, model.hydrometeo_forcing, model.geometry, ts) # Total soil erosion and particle differentiation diff --git a/src/erosion/rainfall_erosion.jl b/src/erosion/rainfall_erosion.jl index b7643aa53..d6e16e950 100644 --- a/src/erosion/rainfall_erosion.jl +++ b/src/erosion/rainfall_erosion.jl @@ -110,7 +110,7 @@ end function update!( model::RainfallErosionEurosemModel, hydrometeo_forcing::HydrometeoForcing, - area, + geometry::LandGeometry, ts, ) (; precipitation, waterlevel_land) = hydrometeo_forcing @@ -135,7 +135,7 @@ function update!( canopyheight[i], canopygapfraction[i], soilcover_fraction[i], - area[i], + geometry.area[i], ts, ) end @@ -187,7 +187,7 @@ end function update!( model::RainfallErosionAnswersModel, hydrometeo_forcing::HydrometeoForcing, - area, + geometry::LandGeometry, ts, ) (; precipitation) = hydrometeo_forcing @@ -196,8 +196,13 @@ function update!( n = length(precipitation) threaded_foreach(1:n; basesize = 1000) do i - amount[i] = - rainfall_erosion_answers(precipitation[i], usle_k[i], usle_c[i], area[i], ts) + amount[i] = rainfall_erosion_answers( + precipitation[i], + usle_k[i], + usle_c[i], + geometry.area[i], + ts, + ) end end diff --git a/src/sediment_flux.jl b/src/sediment_flux.jl index 7d4a5c987..fc4c6feaa 100644 --- a/src/sediment_flux.jl +++ b/src/sediment_flux.jl @@ -59,21 +59,22 @@ end function update!(model::OverlandFlowSediment, erosion_model::SoilErosionModel, network, dt) # Convert dt to integer ts = tosecond(dt) - # Update the boundary conditions of transport capacity - (; q, waterlevel) = model.transport_capacity.boundary_conditions - (; q_land, waterlevel_land) = model.hydrometeo_forcing - @. q = q_land - @. waterlevel = waterlevel_land + # Transport capacity - update!(model.transport_capacity, model.width, model.waterbodies, model.rivers, ts) + update_boundary_conditions!(model.transport_capacity, model.hydrometeo_forcing, :land) + update!(model.transport_capacity, model.geometry, model.waterbodies, model.rivers, ts) # Update boundary conditions before transport - update_bc(model.sediment_flux, erosion_model, model.transport_capacity) + update_boundary_conditions!( + model.sediment_flux, + erosion_model, + model.transport_capacity, + ) # Compute transport update!(model.sediment_flux, network) # Update boundary conditions before computing sediment reaching the river - update_bc(model.to_river, model.sediment_flux) + update_boundary_conditions!(model.to_river, model.sediment_flux) # Compute sediment reaching the river update!(model.to_river, model.rivers) end @@ -152,59 +153,33 @@ function update!( ) # Convert dt to integer ts = tosecond(dt) - # Update the boundary conditions of transport capacity - (; q, waterlevel) = model.transport_capacity.boundary_conditions - (; q_river, waterlevel_river) = model.hydrometeo_forcing - @. q = q_river - @. waterlevel = waterlevel_river # Transport capacity + update_boundary_conditions!(model.transport_capacity, model.hydrometeo_forcing, :river) update!(model.transport_capacity, model.geometry, ts) # Potential maximum river erosion (; waterlevel) = model.potential_erosion.boundary_conditions + (; waterlevel_river) = model.hydrometeo_forcing @. waterlevel = waterlevel_river update!(model.potential_erosion, model.geometry, ts) # River transport - (; - waterlevel, - q, - transport_capacity, - erosion_land_clay, - erosion_land_silt, - erosion_land_sand, - erosion_land_sagg, - erosion_land_lagg, - potential_erosion_river, - ) = model.boundary_conditions - @. q = q_river - @. waterlevel = waterlevel_river - - @. transport_capacity = model.transport_capacity.variables.amount - - (; clay, silt, sand, sagg, lagg) = to_river_model.variables - @. erosion_land_clay = clay[inds_riv] - @. erosion_land_silt = silt[inds_riv] - @. erosion_land_sand = sand[inds_riv] - @. erosion_land_sagg = sagg[inds_riv] - @. erosion_land_lagg = lagg[inds_riv] - - @. potential_erosion_river = model.potential_erosion.variables.amount - - update!(model.sediment_flux, network, model.geometry, ts) + update_boundary_conditions!( + model.sediment_flux, + model.hydrometeo_forcing, + model.transport_capacity, + to_river_model, + model.potential_erosion, + inds_riv, + ) + update!(model.sediment_flux, network, model.geometry, model.waterbodies, ts) # Concentrations - (; q, waterlevel, clay, silt, sand, sagg, lagg, gravel) = - model.concentrations.boundary_conditions - @. q = q_river - @. waterlevel = waterlevel_river - @. clay = model.sediment_flux.variables.clay - @. silt = model.sediment_flux.variables.silt - @. sand = model.sediment_flux.variables.sand - @. sagg = model.sediment_flux.variables.sagg - @. lagg = model.sediment_flux.variables.lagg - @. gravel = model.sediment_flux.variables.gravel - + update_boundary_conditions!( + model.concentrations, + model.hydrometeo_forcing, + model.sediment_flux, + ) update!(model.concentrations, model.geometry, ts) end \ No newline at end of file diff --git a/src/sediment_model.jl b/src/sediment_model.jl index 4e7f1caba..0db933ef3 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -93,18 +93,20 @@ function initialize_sediment_model(config::Config) index_river = filter(i -> !isequal(river[i], 0), 1:n) frac_toriver = fraction_runoff_toriver(graph, ldd, index_river, landslope, n) - rs = initialize_river_flow_sediment(nc, config, inds_riv, waterbodies) + river_sediment = initialize_river_flow_sediment(nc, config, inds_riv, waterbodies) - y_nc = read_y_axis(nc) - x_nc = read_x_axis(nc) - - modelmap = (vertical = soilloss, lateral = (land = overland_flow_sediment, river = rs)) + modelmap = ( + vertical = soilloss, + lateral = (land = overland_flow_sediment, river = river_sediment), + ) indices_reverse = ( land = rev_inds, river = rev_inds_riv, reservoir = isempty(reservoir) ? nothing : reservoir.reverse_indices, lake = isempty(lake) ? nothing : lake.reverse_indices, ) + y_nc = read_y_axis(nc) + x_nc = read_x_axis(nc) writer = prepare_writer(config, modelmap, indices_reverse, x_nc, y_nc, nc) close(nc) @@ -126,7 +128,7 @@ function initialize_sediment_model(config::Config) model = Model( config, (; land, river, reservoir, lake, index_river, frac_toriver), - (land = overland_flow_sediment, river = rs), + (land = overland_flow_sediment, river = river_sediment), soilloss, clock, reader, diff --git a/src/sediment_transport/land_to_river.jl b/src/sediment_transport/land_to_river.jl index f1910c993..5ece4f514 100644 --- a/src/sediment_transport/land_to_river.jl +++ b/src/sediment_transport/land_to_river.jl @@ -34,7 +34,10 @@ function initialize_sediment_to_river_model(inds) return model end -function update_bc(model::SedimentToRiverModel, transport_model::SedimentLandTransportModel) +function update_boundary_conditions!( + model::SedimentToRiverModel, + transport_model::SedimentLandTransportModel, +) (; deposition) = model.boundary_conditions @. deposition = transport_model.variables.deposition end @@ -114,7 +117,7 @@ function initialize_sediment_to_river_differentiation_model(inds) return model end -function update_bc( +function update_boundary_conditions!( model::SedimentToRiverDifferentiationModel, transport_model::SedimentLandTransportDifferentiationModel, ) @@ -143,11 +146,11 @@ function update!(model::SedimentToRiverDifferentiationModel, rivers) (; amount, clay, silt, sand, sagg, lagg) = model.variables zeros = fill(0.0, length(amount)) - clay .= ifelse.(rivers, deposition_clay, zeros) - silt .= ifelse.(rivers, deposition_silt, zeros) - sand .= ifelse.(rivers, deposition_sand, zeros) - sagg .= ifelse.(rivers, deposition_sagg, zeros) - lagg .= ifelse.(rivers, deposition_lagg, zeros) + clay .= ifelse.(rivers .> 0, deposition_clay, zeros) + silt .= ifelse.(rivers .> 0, deposition_silt, zeros) + sand .= ifelse.(rivers .> 0, deposition_sand, zeros) + sagg .= ifelse.(rivers .> 0, deposition_sagg, zeros) + lagg .= ifelse.(rivers .> 0, deposition_lagg, zeros) amount .= clay .+ silt .+ sand .+ sagg .+ lagg end diff --git a/src/sediment_transport/overland_flow_transport.jl b/src/sediment_transport/overland_flow_transport.jl index 89113effb..66eca1bde 100644 --- a/src/sediment_transport/overland_flow_transport.jl +++ b/src/sediment_transport/overland_flow_transport.jl @@ -38,7 +38,7 @@ function initialize_sediment_land_transport_model(inds) return model end -function update_bc( +function update_boundary_conditions!( model::SedimentLandTransportModel, erosion_model::SoilErosionModel, transport_capacity_model::AbstractTransportCapacityModel, @@ -161,7 +161,7 @@ function initialize_sediment_land_transport_differentiation_model(inds) return model end -function update_bc( +function update_boundary_conditions!( model::SedimentLandTransportDifferentiationModel, erosion_model::SoilErosionModel, transport_capacity_model::TransportCapacityYalinDifferentiationModel, diff --git a/src/sediment_transport/river_transport.jl b/src/sediment_transport/river_transport.jl index b258be3bd..a658bb497 100644 --- a/src/sediment_transport/river_transport.jl +++ b/src/sediment_transport/river_transport.jl @@ -33,26 +33,26 @@ end function sediment_river_transport_vars(n) vars = SedimentRiverTransportVars(; amount = fill(mv, n), - clay = fill(mv, n), - silt = fill(mv, n), - sand = fill(mv, n), - sagg = fill(mv, n), - lagg = fill(mv, n), - gravel = fill(mv, n), + clay = fill(0.0, n), + silt = fill(0.0, n), + sand = fill(0.0, n), + sagg = fill(0.0, n), + lagg = fill(0.0, n), + gravel = fill(0.0, n), deposition = fill(mv, n), erosion = fill(mv, n), - leftover_clay = fill(mv, n), - leftover_silt = fill(mv, n), - leftover_sand = fill(mv, n), - leftover_sagg = fill(mv, n), - leftover_lagg = fill(mv, n), - leftover_gravel = fill(mv, n), - store_clay = fill(mv, n), - store_silt = fill(mv, n), - store_sand = fill(mv, n), - store_sagg = fill(mv, n), - store_lagg = fill(mv, n), - store_gravel = fill(mv, n), + leftover_clay = fill(0.0, n), + leftover_silt = fill(0.0, n), + leftover_sand = fill(0.0, n), + leftover_sagg = fill(0.0, n), + leftover_lagg = fill(0.0, n), + leftover_gravel = fill(0.0, n), + store_clay = fill(0.0, n), + store_silt = fill(0.0, n), + store_sand = fill(0.0, n), + store_sagg = fill(0.0, n), + store_lagg = fill(0.0, n), + store_gravel = fill(0.0, n), ) return vars end @@ -71,7 +71,8 @@ end erosion_land_sagg::Vector{T} | "t dt-1" erosion_land_lagg::Vector{T} | "t dt-1" # Sediment available from direct river erosion - potential_erosion_river::Vector{T} | "t dt-1" + potential_erosion_river_bed::Vector{T} | "t dt-1" + potential_erosion_river_bank::Vector{T} | "t dt-1" end function sediment_river_transport_bc(n) @@ -84,7 +85,8 @@ function sediment_river_transport_bc(n) erosion_land_sand = fill(mv, n), erosion_land_sagg = fill(mv, n), erosion_land_lagg = fill(mv, n), - potential_erosion_river = fill(mv, n), + potential_erosion_river_bed = fill(mv, n), + potential_erosion_river_bank = fill(mv, n), ) return bc end @@ -309,7 +311,14 @@ function initialize_sediment_river_transport_model(nc, config, inds) return model end -function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeometry, ts) +function update_boundary_conditions!( + model::SedimentRiverTransportModel, + hydrometeo_forcing::HydrometeoForcing, + transport_capacity_model::AbstractTransportCapacityModel, + to_river_model::SedimentToRiverDifferentiationModel, + potential_erosion_model::AbstractRiverErosionModel, + inds_riv, +) (; waterlevel, q, @@ -322,7 +331,59 @@ function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeo potential_erosion_river_bed, potential_erosion_river_bank, ) = model.boundary_conditions - (; clay_fraction, silt_fraction, sand_fraction, gravel_fraction) = model.parameters + + # HydroMeteo forcing + (; q_river, waterlevel_river) = hydrometeo_forcing + @. q = q_river + @. waterlevel = waterlevel_river + # Transport capacity + @. transport_capacity = transport_capacity_model.variables.amount + # Input from soil erosion + (; clay, silt, sand, sagg, lagg) = to_river_model.variables + @. erosion_land_clay = clay[inds_riv] + @. erosion_land_silt = silt[inds_riv] + @. erosion_land_sand = sand[inds_riv] + @. erosion_land_sagg = sagg[inds_riv] + @. erosion_land_lagg = lagg[inds_riv] + # Maximum direct river bed/bank erosion + @. potential_erosion_river_bed = potential_erosion_model.variables.bed + @. potential_erosion_river_bank = potential_erosion_model.variables.bank +end + +function update!( + model::SedimentRiverTransportModel, + network, + geometry::RiverGeometry, + waterbodies, + ts, +) + (; + waterlevel, + q, + transport_capacity, + erosion_land_clay, + erosion_land_silt, + erosion_land_sand, + erosion_land_sagg, + erosion_land_lagg, + potential_erosion_river_bed, + potential_erosion_river_bank, + ) = model.boundary_conditions + (; + clay_fraction, + silt_fraction, + sand_fraction, + gravel_fraction, + dm_clay, + dm_silt, + dm_sand, + dm_sagg, + dm_lagg, + dm_gravel, + waterbodies_locs, + waterbodies_area, + waterbodies_trapping_efficiency, + ) = model.parameters (; amount, clay, @@ -379,7 +440,7 @@ function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeo ### River erosion ### # Erosion only if the load is below the transport capacity of the flow. - sediment_need = max(transport_capacity - input_sediment, 0.0) + sediment_need = max(transport_capacity[v] - input_sediment, 0.0) # No erosion in reservoirs if waterbodies[v] sediment_need = 0.0 @@ -399,18 +460,18 @@ function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeo # Effective sediment needed fom river bed and bank erosion [ton] effsediment_need = sediment_need - store_sediment # Relative potential erosion rates of the bed and the bank [-] - if (potential_erosion_river_bank + potential_erosion_river_bed > 0.0) + if (potential_erosion_river_bank[v] + potential_erosion_river_bed[v] > 0.0) RTEbank = - potential_erosion_river_bank / - (potential_erosion_river_bank + potential_erosion_river_bed) + potential_erosion_river_bank[v] / + (potential_erosion_river_bank[v] + potential_erosion_river_bed[v]) else RTEbank = 0.0 end RTEbed = 1.0 - RTEbank # Actual bed and bank erosion - erosion_bank = max(RTEbank * effsediment_need, potential_erosion_river_bank) - erosion_bed = max(RTEbed * effsediment_need, potential_erosion_river_bed) + erosion_bank = min(RTEbank * effsediment_need, potential_erosion_river_bank[v]) + erosion_bed = min(RTEbed * effsediment_need, potential_erosion_river_bed[v]) erosion_river = erosion_bank + erosion_bed # Per particle erosion_clay = erosion_river * clay_fraction[v] @@ -484,6 +545,15 @@ function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeo erosion_sagg + erosion_lagg + erosion_gravel + #if erosion[v] > transport_capacity[v] + # println("Erosion exceeds transport capacity for node $v") + #end + if v == 5642 + println("Erosion at node 5642: ", erosion[v]) + println("Transport capacity at node 5642: ", transport_capacity[v]) + println("Sediment stored at node 5642: ", store_sediment) + println("Sediemnt need: ", sediment_need) + end ### Deposition / settling ### # Different deposition if waterbody outlet or river @@ -553,8 +623,8 @@ function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeo deposition_gravel = 0.0 else # Deposition in the river - # From trasnport capacity exceedance - excess_sediment = max(input_sediment - transport_capacity, 0.0) + # From transport capacity exceedance + excess_sediment = max(input_sediment - transport_capacity[v], 0.0) if excess_sediment > 0.0 # Sediment deposited in the channel (from gravel to clay) [ton] # Gravel @@ -623,7 +693,7 @@ function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeo min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_lagg[v] / 1000)^2 / 3600))) deposition_lagg = xlagg * (input_lagg + erosion_lagg) xgrav = - min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_grav[v] / 1000)^2 / 3600))) + min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (dm_gravel[v] / 1000)^2 / 3600))) deposition_gravel = xgrav * (input_gravel + erosion_gravel) end end @@ -649,25 +719,31 @@ function update!(model::SedimentRiverTransportModel, network, geometry::RiverGeo # Sediment transported out of the cell during the timestep [ton] # 0 in case all sediment are deposited in the cell # Reduce the fraction so that there is still some sediment staying in the river cell - fwaterout = - min(q[v] * ts / (waterlevel[v] * geometry.width[v] * geometry.length[v]), 1.0) - clay = fwaterout * (input_clay + erosion_clay - deposition_clay) - silt = fwaterout * (input_silt + erosion_silt - deposition_silt) - sand = fwaterout * (input_sand + erosion_sand - deposition_sand) - sagg = fwaterout * (input_sagg + erosion_sagg - deposition_sagg) - lagg = fwaterout * (input_lagg + erosion_lagg - deposition_lagg) - gravel = fwaterout * (input_gravel + erosion_gravel - deposition_gravel) + if waterlevel[v] > 0.0 + fwaterout = min( + q[v] * ts / (waterlevel[v] * geometry.width[v] * geometry.length[v]), + 1.0, + ) + else + fwaterout = 1.0 + end + clay[v] = fwaterout * (input_clay + erosion_clay - deposition_clay) + silt[v] = fwaterout * (input_silt + erosion_silt - deposition_silt) + sand[v] = fwaterout * (input_sand + erosion_sand - deposition_sand) + sagg[v] = fwaterout * (input_sagg + erosion_sagg - deposition_sagg) + lagg[v] = fwaterout * (input_lagg + erosion_lagg - deposition_lagg) + gravel[v] = fwaterout * (input_gravel + erosion_gravel - deposition_gravel) - amount = clay + silt + sand + sagg + lagg + gravel + amount[v] = clay[v] + silt[v] + sand[v] + sagg[v] + lagg[v] + gravel[v] ### Leftover / mass balance ### # Sediment left in the cell [ton] - leftover_clay[v] = input_clay + erosion_clay - deposition_clay - clay - leftover_silt[v] = input_silt + erosion_silt - deposition_silt - silt - leftover_sand[v] = input_sand + erosion_sand - deposition_sand - sand - leftover_sagg[v] = input_sagg + erosion_sagg - deposition_sagg - sagg - leftover_lagg[v] = input_lagg + erosion_lagg - deposition_lagg - lagg - leftover_gravel[v] = input_gravel + erosion_gravel - deposition_gravel - gravel + leftover_clay[v] = input_clay + erosion_clay - deposition_clay - clay[v] + leftover_silt[v] = input_silt + erosion_silt - deposition_silt - silt[v] + leftover_sand[v] = input_sand + erosion_sand - deposition_sand - sand[v] + leftover_sagg[v] = input_sagg + erosion_sagg - deposition_sagg - sagg[v] + leftover_lagg[v] = input_lagg + erosion_lagg - deposition_lagg - lagg[v] + leftover_gravel[v] = input_gravel + erosion_gravel - deposition_gravel - gravel[v] end end @@ -821,47 +897,61 @@ function initialize_sediment_concentrations_river_model(nc, config, inds) return model end +function update_boundary_conditions!( + model::SedimentConcentrationsRiverModel, + hydrometeo_forcing::HydrometeoForcing, + sediment_flux_model::AbstractSedimentRiverTransportModel, +) + (; q, waterlevel, clay, silt, sand, sagg, lagg, gravel) = model.boundary_conditions + # Hydrometeo forcing + (; q_river, waterlevel_river) = hydrometeo_forcing + @. q = q_river + @. waterlevel = waterlevel_river + # Sediment flux per particle + @. clay = sediment_flux_model.variables.clay + @. silt = sediment_flux_model.variables.silt + @. sand = sediment_flux_model.variables.sand + @. sagg = sediment_flux_model.variables.sagg + @. lagg = sediment_flux_model.variables.lagg + @. gravel = sediment_flux_model.variables.gravel +end + function update!(model::SedimentConcentrationsRiverModel, geometry::RiverGeometry, ts) (; q, waterlevel, clay, silt, sand, sagg, lagg, gravel) = model.boundary_conditions (; dm_clay, dm_silt, dm_sand, dm_sagg, dm_lagg, dm_gravel) = model.parameters (; total, suspended, bed) = model.variables - zeros = zeros(Float, length(q)) + zeros = fill(0.0, length(q)) # Conversion from load [ton] to concentration for rivers [mg/L] - toconc .= ifelse.(q .> 0.0, 1e6 ./ (q .* ts), zeros) + toconc = ifelse.(q .> 0.0, 1e6 ./ (q .* ts), zeros) # Differentiation of bed and suspended load using Rouse number for suspension # threshold diameter between bed load and mixed load using Rouse number - dbedf .= + dbedf = 1e3 .* (2.5 .* 3600 .* 0.41 ./ 411 .* (9.81 .* waterlevel .* geometry.slope) .^ 0.5) .^ 0.5 # threshold diameter between suspended load and mixed load using Rouse number - dsuspf .= + dsuspf = 1e3 .* (1.2 .* 3600 .* 0.41 ./ 411 .* (9.81 .* waterlevel .* geometry.slope) .^ 0.5) .^ 0.5 # Rouse with diameter - SSclay .= - ifelse.(dm_clay .<= dsuspf, clay, ifelse.(dm_clay .<= dbedf, clay ./ 2, zeros)) - SSsilt .= - ifelse.(dm_silt .<= dsuspf, silt, ifelse.(dm_silt .<= dbedf, silt ./ 2, zeros)) - SSsand .= - ifelse.(dm_sand .<= dsuspf, sand, ifelse.(dm_sand .<= dbedf, sand ./ 2, zeros)) - SSsagg .= - ifelse.(dm_sagg .<= dsuspf, sagg, ifelse.(dm_sagg .<= dbedf, sagg ./ 2, zeros)) - SSlagg .= - ifelse.(dm_lagg .<= dsuspf, lagg, ifelse.(dm_lagg .<= dbedf, lagg ./ 2, zeros)) - SSgrav .= + SSclay = ifelse.(dm_clay .<= dsuspf, clay, ifelse.(dm_clay .<= dbedf, clay ./ 2, zeros)) + SSsilt = ifelse.(dm_silt .<= dsuspf, silt, ifelse.(dm_silt .<= dbedf, silt ./ 2, zeros)) + SSsand = ifelse.(dm_sand .<= dsuspf, sand, ifelse.(dm_sand .<= dbedf, sand ./ 2, zeros)) + SSsagg = ifelse.(dm_sagg .<= dsuspf, sagg, ifelse.(dm_sagg .<= dbedf, sagg ./ 2, zeros)) + SSlagg = ifelse.(dm_lagg .<= dsuspf, lagg, ifelse.(dm_lagg .<= dbedf, lagg ./ 2, zeros)) + SSgrav = ifelse.( dm_gravel .<= dsuspf, gravel, ifelse.(dm_gravel .<= dbedf, gravel ./ 2, zeros), ) - SS .= SSclay .+ SSsilt .+ SSsagg .+ SSsand .+ SSlagg .+ SSgrav - Bedload .= (clay .+ silt .+ sagg .+ sand .+ lagg .+ gravel) .- SS + SS = SSclay .+ SSsilt .+ SSsagg .+ SSsand .+ SSlagg .+ SSgrav + Bedload = (clay .+ silt .+ sagg .+ sand .+ lagg .+ gravel) .- SS - suspended .= SS .* toconc - bed .= Bedload .* toconc - total .= (clay .+ silt .+ sagg .+ sand .+ lagg .+ gravel) .* toconc + @. suspended = SS .* toconc + @. bed = Bedload .* toconc + @. total = (clay .+ silt .+ sagg .+ sand .+ lagg .+ gravel) .* toconc end \ No newline at end of file diff --git a/src/sediment_transport/transport_capacity.jl b/src/sediment_transport/transport_capacity.jl index c0d4f999c..1931e4258 100644 --- a/src/sediment_transport/transport_capacity.jl +++ b/src/sediment_transport/transport_capacity.jl @@ -23,6 +23,23 @@ function transport_capacity_bc(n) return bc end +function update_boundary_conditions!( + model::AbstractTransportCapacityModel, + hydrometeo_forcing::HydrometeoForcing, + model_type::Symbol, +) + (; q, waterlevel) = model.boundary_conditions + (; q_land, waterlevel_land, q_river, waterlevel_river) = hydrometeo_forcing + + if model_type == :land + @. q = q_land + @. waterlevel = waterlevel_land + elseif model_type == :river + @. q = q_river + @. waterlevel = waterlevel_river + end +end + ##################### Overland Flow ##################### # Govers parameters for transport capacity models @@ -329,13 +346,13 @@ end function update!( model::TransportCapacityYalinDifferentiationModel, - width, + geometry::LandGeometry, waterbodies, rivers, ts, ) (; q, waterlevel) = model.boundary_conditions - (; slope, density, dm_clay, dm_silt, dm_sand, dm_sagg, dm_lagg) = model.parameters + (; density, dm_clay, dm_silt, dm_sand, dm_sagg, dm_lagg) = model.parameters (; amount, clay, silt, sand, sagg, lagg) = model.variables n = length(q) @@ -348,15 +365,15 @@ function update!( dm_sand[i], dm_sagg[i], dm_lagg[i], - slope[i], + geometry.slope[i], ) clay[i] = transport_capacity_yalin_differentiation( q[i], waterlevel[i], density[i], dm_clay[i], - slope[i], - width[i], + geometry.slope[i], + geometry.width[i], waterbodies[i], rivers[i], dtot, @@ -367,8 +384,8 @@ function update!( waterlevel[i], density[i], dm_silt[i], - slope[i], - width[i], + geometry.slope[i], + geometry.width[i], waterbodies[i], rivers[i], dtot, @@ -379,8 +396,8 @@ function update!( waterlevel[i], density[i], dm_sand[i], - slope[i], - width[i], + geometry.slope[i], + geometry.width[i], waterbodies[i], rivers[i], dtot, @@ -391,8 +408,8 @@ function update!( waterlevel[i], density[i], dm_sagg[i], - slope[i], - width[i], + geometry.slope[i], + geometry.width[i], waterbodies[i], rivers[i], dtot, @@ -403,8 +420,8 @@ function update!( waterlevel[i], density[i], dm_lagg[i], - slope[i], - width[i], + geometry.slope[i], + geometry.width[i], waterbodies[i], rivers[i], dtot, diff --git a/src/states.jl b/src/states.jl index 6d092daba..018bb9f4b 100644 --- a/src/states.jl +++ b/src/states.jl @@ -91,6 +91,30 @@ function get_soil_states(model_type::AbstractString; snow = false) return states end +function get_sediment_states() + states = ( + :leftover_clay, + :leftover_silt, + :leftover_sand, + :leftover_sagg, + :leftover_lagg, + :leftover_gravel, + :store_clay, + :store_silt, + :store_sand, + :store_sagg, + :store_lagg, + :store_gravel, + :clay, + :silt, + :sand, + :sagg, + :lagg, + :gravel, + ) + return states +end + """ add_to_required_states(required_states::Tuple, key_entry::Tuple, states::Tuple) add_to_required_states(required_states::Tuple, key_entry::Tuple, states::Nothing) @@ -182,26 +206,7 @@ function extract_required_states(config::Config) # River states if model_type == "sediment" - river_states = ( - :leftover_clay, - :leftover_silt, - :leftover_sand, - :leftover_sagg, - :leftover_lagg, - :leftover_gravel, - :store_clay, - :store_silt, - :store_sand, - :store_sagg, - :store_lagg, - :store_gravel, - :clay, - :silt, - :sand, - :sagg, - :lagg, - :gravel, - ) + river_states = get_sediment_states() elseif model_type == "sbm" || model_type == "sbm_gwf" river_states = (:q, :h, :h_av) else @@ -247,8 +252,16 @@ function extract_required_states(config::Config) required_states = add_to_required_states(required_states, (:lateral, :land), land_states) # Add river states to dict - required_states = - add_to_required_states(required_states, (:lateral, :river), river_states) + if model_type == "sediment" + required_states = add_to_required_states( + required_states, + (:lateral, :river, :sediment_flux, :variables), + river_states, + ) + else + required_states = + add_to_required_states(required_states, (:lateral, :river), river_states) + end # Add floodplain states to dict required_states = add_to_required_states( required_states, diff --git a/test/run_sediment.jl b/test/run_sediment.jl index 5bde18826..7741dec23 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -42,6 +42,7 @@ end @testset "second timestep sediment model (lateral)" begin land = model.lateral.land + river = model.lateral.river @test land.transport_capacity.parameters.dm_sand[1] == 200.0f0 @test land.transport_capacity.parameters.dm_lagg[1] == 500.0f0 @@ -52,16 +53,31 @@ end @test mean(land.transport_capacity.variables.clay) ≈ 1.0992655197016734f6 @test mean(land.to_river.variables.amount) ≈ 0.07624135182616738f0 - @test mean(land.to_river.variables.clay) ≈ 0.002285341387958068f0 - @test mean(land.to_river.variables.sand) ≈ 0.02575351604673049f0 + @test sum(land.to_river.variables.clay) ≈ 114.42704329506047f0 + @test sum(land.to_river.variables.sand) ≈ 1289.4785484597958f0 @test mean(land.sediment_flux.variables.clay) ≈ 0.006578791733506439f0 - @test mean(lat.river.concentrations.variables.suspended) ≈ 0.8259993252994058f0 - @test mean(lat.river.sediment_flux.boundary_conditions.erosion_land_clay) ≈ - 0.01980468760667709f0 - @test lat.river.hydrometeo_forcing.waterlevel_river[network.river.order[end]] ≈ + @test mean(river.hydrometeo_forcing.q_river) ≈ 0.6975180562953642f0 + @test river.hydrometeo_forcing.waterlevel_river[network.river.order[end]] ≈ 0.006103649735450745f0 - @test lat.river.sediment_flux.varibales.clay[5649] ≈ 2.359031898208781f-9 + @test mean(river.geometry.width) ≈ 22.628250814095523f0 + + @test mean(river.transport_capacity.boundary_conditions.q) ≈ 0.6975180562953642f0 + @test mean(river.transport_capacity.variables.amount) ≈ 0.4458019733090582f0 + @test mean(river.potential_erosion.variables.bed) ≈ 307.18492138827116f0 + + @test sum(river.sediment_flux.boundary_conditions.erosion_land_clay) ≈ + 114.42704329506047f0 + @test sum(river.sediment_flux.boundary_conditions.erosion_land_sand) ≈ + 1289.4785484597958f0 + @test mean(river.sediment_flux.boundary_conditions.transport_capacity) ≈ + 0.4458019733090582f0 + @test mean(river.sediment_flux.variables.amount) ≈ 0.4333483865969662f0 + @test mean(river.sediment_flux.variables.erosion) ≈ 0.019077695621351014f0 + @test mean(river.sediment_flux.variables.deposition) ≈ 0.6941274181387916f0 + @test river.sediment_flux.variables.clay[5649] ≈ 2.840979764480952f-9 + + @test mean(river.concentrations.variables.suspended) ≈ 0.8260083257660087f0 end @testset "Exchange and grid location sediment" begin diff --git a/test/sediment_config.toml b/test/sediment_config.toml index 29a5eea21..de64a3188 100644 --- a/test/sediment_config.toml +++ b/test/sediment_config.toml @@ -174,7 +174,7 @@ landtransportmethod = "yalinpart" # Overland flow transport capacity method: ["y rainerosmethod = "answers" # Rainfall erosion equation: ["answers", "eurosem"] reinit = true rivtransportmethod = "bagnold" # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] -runrivermodel = false +runrivermodel = true type = "sediment" [output] @@ -213,9 +213,12 @@ waterlevel_river = "h_riv" [output.lateral.river.sediment_flux.variables] leftover_clay = "clayload" clay = "outclay" +erosion = "erosion_riv" +deposition = "deposition_riv" [output.lateral.river.sediment_flux.boundary_conditions] erosion_land_clay = "inlandclayriv" +transport_capacity = "TCsed_riv" [csv] path = "output-moselle-sediment.csv" From 4db4d480a8f214dd00e67ec16d0d0cffa00c2320 Mon Sep 17 00:00:00 2001 From: hboisgon Date: Wed, 25 Sep 2024 18:43:36 +0800 Subject: [PATCH 25/42] renaming of functions --- src/Wflow.jl | 4 +- src/erosion.jl | 41 ++-- src/erosion/overland_flow_erosion.jl | 68 ++++-- src/erosion/rainfall_erosion.jl | 123 ++++++---- src/erosion/river_erosion.jl | 45 ++-- src/erosion/soil_erosion.jl | 76 ++++--- src/forcing.jl | 2 +- src/geometry.jl | 4 +- src/sediment_flux.jl | 49 ++-- src/sediment_transport/land_to_river.jl | 85 ++++--- .../overland_flow_transport.jl | 134 ++++++----- src/sediment_transport/river_transport.jl | 210 ++++++++++-------- src/sediment_transport/transport_capacity.jl | 191 ++++++++-------- 13 files changed, 606 insertions(+), 426 deletions(-) diff --git a/src/Wflow.jl b/src/Wflow.jl index 07a88346e..2fd0ba56b 100644 --- a/src/Wflow.jl +++ b/src/Wflow.jl @@ -158,8 +158,6 @@ include("demand/water_demand.jl") #include("sediment.jl") include("reservoir_lake.jl") include("sbm_model.jl") -include("sediment_model.jl") -include("erosion.jl") include("erosion/erosion_process.jl") include("erosion/rainfall_erosion.jl") include("erosion/overland_flow_erosion.jl") @@ -171,7 +169,9 @@ include("sediment_transport/transport_capacity.jl") include("sediment_transport/overland_flow_transport.jl") include("sediment_transport/land_to_river.jl") include("sediment_transport/river_transport.jl") +include("erosion.jl") include("sediment_flux.jl") +include("sediment_model.jl") include("groundwater/connectivity.jl") include("groundwater/aquifer.jl") include("groundwater/boundary_conditions.jl") diff --git a/src/erosion.jl b/src/erosion.jl index 696f7b79e..b91db0c83 100644 --- a/src/erosion.jl +++ b/src/erosion.jl @@ -1,21 +1,23 @@ -@get_units @with_kw struct SoilLoss{RE, OLE, SE, T} +@get_units @with_kw struct SoilLoss{RE, OFE, SE, T} hydrometeo_forcing::HydrometeoForcing | "-" geometry::LandGeometry | "-" rainfall_erosion::RE | "-" - overland_flow_erosion::OLE | "-" + overland_flow_erosion::OFE | "-" soil_erosion::SE | "-" end function initialize_soil_loss(nc, config, inds) n = length(inds) - hydrometeo_forcing = initialize_hydrometeo_forcing(n) - geometry = initialize_land_geometry(nc, config, inds) + + hydrometeo_forcing = HydrometeoForcing(n) + geometry = LandGeometry(nc, config, inds) + # Rainfall erosion rainfallerosionmodel = get(config.model, "rainfall_erosion", "answers")::String if rainfallerosionmodel == "answers" - rainfall_erosion_model = initialize_answers_rainfall_erosion_model(nc, config, inds) + rainfall_erosion_model = RainfallErosionAnswersModel(nc, config, inds) elseif rainfallerosionmodel == "eurosem" - rainfall_erosion_model = initialize_eurosem_rainfall_erosion_model(nc, config, inds) + rainfall_erosion_model = RainfallErosionEurosemModel(nc, config, inds) else error("Unknown rainfall erosion model: $rainfallerosionmodel") end @@ -23,15 +25,14 @@ function initialize_soil_loss(nc, config, inds) # Overland flow erosion overlandflowerosionmodel = get(config.model, "overland_flow_erosion", "answers")::String if overlandflowerosionmodel == "answers" - overland_flow_erosion_model = - initialize_answers_overland_flow_erosion_model(nc, config, inds) + overland_flow_erosion_model = OverlandFlowErosionAnswersModel(nc, config, inds) else error("Unknown overland flow erosion model: $overlandflowerosionmodel") # overland_flow_erosion_model = NoOverlandFlowErosionModel() end # Total soil erosion and particle differentiation - soil_erosion_model = initialize_soil_erosion_model(nc, config, inds) + soil_erosion_model = SoilErosionModel(nc, config, inds) soil_loss = SoilLoss{ typeof(rainfall_erosion_model), @@ -49,19 +50,25 @@ function initialize_soil_loss(nc, config, inds) end function update!(model::SoilLoss, dt) + (; + hydrometeo_forcing, + geometry, + rainfall_erosion, + overland_flow_erosion, + soil_erosion, + ) = model # Convert dt to integer ts = tosecond(dt) #TODO add interception/canopygapfraction calculation here for eurosem #need SBM refactor + # Rainfall erosion - update!(model.rainfall_erosion, model.hydrometeo_forcing, model.geometry, ts) + update_boundary_conditions!(rainfall_erosion, hydrometeo_forcing) + update!(rainfall_erosion, geometry, ts) # Overland flow erosion - update!(model.overland_flow_erosion, model.hydrometeo_forcing, model.geometry, ts) + update_boundary_conditions!(overland_flow_erosion, hydrometeo_forcing) + update!(overland_flow_erosion, geometry, ts) # Total soil erosion and particle differentiation - re = get_rainfall_erosion(model.rainfall_erosion) - ole = get_overland_flow_erosion(model.overland_flow_erosion) - (; rainfall_erosion, overland_flow_erosion) = model.soil_erosion.boundary_conditions - @. rainfall_erosion = re - @. overland_flow_erosion = ole - update!(model.soil_erosion) + update_boundary_conditions!(soil_erosion, rainfall_erosion, overland_flow_erosion) + update!(soil_erosion) end diff --git a/src/erosion/overland_flow_erosion.jl b/src/erosion/overland_flow_erosion.jl index 567c36757..b12805308 100644 --- a/src/erosion/overland_flow_erosion.jl +++ b/src/erosion/overland_flow_erosion.jl @@ -1,16 +1,24 @@ -abstract type AbstractOverlandFlowErosionModel end +abstract type AbstractOverlandFlowErosionModel{T} end -struct NoOverlandFlowErosionModel <: AbstractOverlandFlowErosionModel end +struct NoOverlandFlowErosionModel{T} <: AbstractOverlandFlowErosionModel{T} end ## Overland flow structs and functions -@get_units @with_kw struct OverlandFlowErosionModelVars{T} +@get_units @with_kw struct OverlandFlowErosionVariables{T} # Total soil erosion from overland flow amount::Vector{T} | "t dt-1" end -function overland_flow_erosion_model_vars(n) - vars = OverlandFlowErosionModelVars(; amount = fill(mv, n)) - return vars +function OverlandFlowErosionVariables(n; amount::Vector{T} = fill(mv, n)) where {T} + return OverlandFlowErosionVariables{T}(; amount = amount) +end + +@get_units @with_kw struct OverlandFlowErosionBC{T} + # Overland flow [m3 s-1] + q::Vector{T} +end + +function OverlandFlowErosionBC(n; q::Vector{T} = fill(mv, n)) where {T} + return OverlandFlowErosionBC{T}(; q = q) end # ANSWERS specific structs and functions for rainfall erosion @@ -23,13 +31,7 @@ end answers_k::Vector{T} | "-" end -@get_units @with_kw struct OverlandFlowErosionAnswersModel{T} <: - AbstractOverlandFlowErosionModel - parameters::OverlandFlowErosionAnswersParameters{T} | "-" - variables::OverlandFlowErosionModelVars{T} | "-" -end - -function initialize_answers_params_overland_flow(nc, config, inds) +function OverlandFlowErosionAnswersParameters(nc, config, inds) usle_k = ncread( nc, config, @@ -62,28 +64,50 @@ function initialize_answers_params_overland_flow(nc, config, inds) return answers_parameters end -function initialize_answers_overland_flow_erosion_model(nc, config, inds) +@with_kw struct OverlandFlowErosionAnswersModel{T} <: AbstractOverlandFlowErosionModel{T} + boundary_conditions::OverlandFlowErosionBC{T} + parameters::OverlandFlowErosionAnswersParameters{T} + variables::OverlandFlowErosionVariables{T} +end + +function OverlandFlowErosionAnswersModel(nc, config, inds) n = length(inds) - vars = overland_flow_erosion_model_vars(n) - params = initialize_answers_params_overland_flow(nc, config, inds) - model = OverlandFlowErosionAnswersModel(; parameters = params, variables = vars) + vars = OverlandFlowErosionVariables(n) + params = OverlandFlowErosionAnswersParameters(nc, config, inds) + bc = OverlandFlowErosionBC(n) + model = OverlandFlowErosionAnswersModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) return model end -function update!( +function update_boundary_conditions!( model::OverlandFlowErosionAnswersModel, hydrometeo_forcing::HydrometeoForcing, - geometry::LandGeometry, - ts, ) + (; q) = model.boundary_conditions (; q_land) = hydrometeo_forcing + @. q = q_land +end + +function update_boundary_conditions!( + model::NoOverlandFlowErosionModel, + hydrometeo_forcing::HydrometeoForcing, +) + return nothing +end + +function update!(model::OverlandFlowErosionAnswersModel, geometry::LandGeometry, ts) + (; q) = model.boundary_conditions (; usle_k, usle_c, answers_k) = model.parameters (; amount) = model.variables - n = length(q_land) + n = length(q) threaded_foreach(1:n; basesize = 1000) do i amount[i] = overland_flow_erosion_answers( - q_land[i], + q[i], usle_k[i], usle_c[i], answers_k[i], diff --git a/src/erosion/rainfall_erosion.jl b/src/erosion/rainfall_erosion.jl index d6e16e950..32c0de725 100644 --- a/src/erosion/rainfall_erosion.jl +++ b/src/erosion/rainfall_erosion.jl @@ -1,27 +1,38 @@ -abstract type AbstractRainfallErosionModel end +abstract type AbstractRainfallErosionModel{T} end -struct NoRainfallErosionModel <: AbstractRainfallErosionModel end +struct NoRainfallErosionModel{T} <: AbstractRainfallErosionModel{T} end ## General rainfall erosion functions and structs -@get_units @with_kw struct RainfallErosionModelVars{T} +@get_units @with_kw struct RainfallErosionModelVariables{T} # Total soil erosion from rainfall (splash) amount::Vector{T} | "t dt-1" end -function rainfall_erosion_model_vars(n) - vars = RainfallErosionModelVars(; amount = fill(mv, n)) - return vars +function RainfallErosionModelVariables(n; amount::Vector{T} = fill(mv, n)) where {T} + return RainfallErosionModelVariables{T}(; amount = amount) end ## EUROSEM specific structs and functions for rainfall erosion @get_units @with_kw struct RainfallErosionEurosemBC{T} + # precipitation + precipitation::Vector{T} | "mm dt-1" # Interception interception::Vector{T} | "mm dt-1" + # Waterlevel on land + waterlevel::Vector{T} | "m" end -function rainfall_erosion_eurosem_bc(n) - bc = RainfallErosionEurosemBC(; interception = fill(mv, n)) - return bc +function RainfallErosionEurosemBC( + n; + precipitation::Vector{T} = fill(mv, n), + interception::Vector{T} = fill(0.0, n), + waterlevel::Vector{T} = fill(mv, n), +) where {T} + return RainfallErosionEurosemBC{T}(; + precipitation = precipitation, + interception = interception, + waterlevel = waterlevel, + ) end @get_units @with_kw struct RainfallErosionEurosemParameters{T} @@ -37,13 +48,7 @@ end soilcover_fraction::Vector{T} | "-" end -@get_units @with_kw struct RainfallErosionEurosemModel{T} <: AbstractRainfallErosionModel - boundary_conditions::RainfallErosionEurosemBC{T} | "-" - parameters::RainfallErosionEurosemParameters{T} | "-" - variables::RainfallErosionModelVars{T} | "-" -end - -function initialize_eurosem_params(nc, config, inds) +function RainfallErosionEurosemParameters(nc, config, inds) soil_detachability = ncread( nc, config, @@ -94,11 +99,17 @@ function initialize_eurosem_params(nc, config, inds) return eurosem_parameters end -function initialize_eurosem_rainfall_erosion_model(nc, config, inds) +@with_kw struct RainfallErosionEurosemModel{T} <: AbstractRainfallErosionModel{T} + boundary_conditions::RainfallErosionEurosemBC{T} + parameters::RainfallErosionEurosemParameters{T} + variables::RainfallErosionModelVariables{T} +end + +function RainfallErosionEurosemModel(nc, config, inds) n = length(inds) - vars = rainfall_erosion_model_vars(n) - params = initialize_eurosem_params(nc, config, inds) - bc = rainfall_erosion_eurosem_bc(n) + vars = RainfallErosionModelVariables(n) + params = RainfallErosionEurosemParameters(nc, config, inds) + bc = RainfallErosionEurosemBC(n) model = RainfallErosionEurosemModel(; boundary_conditions = bc, parameters = params, @@ -107,14 +118,17 @@ function initialize_eurosem_rainfall_erosion_model(nc, config, inds) return model end -function update!( +function update_boundary_conditions!( model::RainfallErosionEurosemModel, hydrometeo_forcing::HydrometeoForcing, - geometry::LandGeometry, - ts, ) - (; precipitation, waterlevel_land) = hydrometeo_forcing - (; interception) = model.boundary_conditions + (; precipitation, waterlevel) = model.boundary_conditions + @. precipitation = hydrometeo_forcing.precipitation + @. waterlevel = model.boundary_conditions.waterlevel_land +end + +function update!(model::RainfallErosionEurosemModel, geometry::LandGeometry, ts) + (; precipitation, interception, waterlevel) = model.boundary_conditions (; soil_detachability, eurosem_exponent, @@ -124,12 +138,12 @@ function update!( ) = model.parameters (; amount) = model.variables - n = length(precip) + n = length(precipitation) threaded_foreach(1:n; basesize = 1000) do i amount[i] = rainfall_erosion_eurosem( precipitation[i], interception[i], - waterlevel_land[i], + waterlevel[i], soil_detachability[i], eurosem_exponent[i], canopyheight[i], @@ -141,7 +155,16 @@ function update!( end end -# ANSWERS specific structs and functions for rainfall erosion +### ANSWERS specific structs and functions for rainfall erosion +@get_units @with_kw struct RainfallErosionAnswersBC{T} + # precipitation + precipitation::Vector{T} | "mm dt-1" +end + +function RainfallErosionAnswersBC(n; precipitation::Vector{T} = fill(mv, n)) where {T} + return RainfallErosionAnswersBC{T}(; precipitation = precipitation) +end + @get_units @with_kw struct RainfallErosionAnswersParameters{T} # Soil erodibility factor usle_k::Vector{T} | "-" @@ -149,12 +172,7 @@ end usle_c::Vector{T} | "-" end -@get_units @with_kw struct RainfallErosionAnswersModel{T} <: AbstractRainfallErosionModel - parameters::RainfallErosionAnswersParameters{T} | "-" - variables::RainfallErosionModelVars{T} | "-" -end - -function initialize_answers_params_rainfall(nc, config, inds) +function RainfallErosionAnswersParameters(nc, config, inds) usle_k = ncread( nc, config, @@ -176,21 +194,35 @@ function initialize_answers_params_rainfall(nc, config, inds) return answers_parameters end -function initialize_answers_rainfall_erosion_model(nc, config, inds) +@with_kw struct RainfallErosionAnswersModel{T} <: AbstractRainfallErosionModel{T} + boundary_conditions::RainfallErosionAnswersBC{T} + parameters::RainfallErosionAnswersParameters{T} + variables::RainfallErosionModelVariables{T} +end + +function RainfallErosionAnswersModel(nc, config, inds) n = length(inds) - vars = rainfall_erosion_model_vars(n) - params = initialize_answers_params_rainfall(nc, config, inds) - model = RainfallErosionAnswersModel(; parameters = params, variables = vars) + bc = RainfallErosionAnswersBC(n) + vars = RainfallErosionModelVariables(n) + params = RainfallErosionAnswersParameters(nc, config, inds) + model = RainfallErosionAnswersModel(; + boundary_conditions = bc, + parameters = params, + variables = vars, + ) return model end -function update!( +function update_boundary_conditions!( model::RainfallErosionAnswersModel, hydrometeo_forcing::HydrometeoForcing, - geometry::LandGeometry, - ts, ) - (; precipitation) = hydrometeo_forcing + (; precipitation) = model.boundary_conditions + @. precipitation = hydrometeo_forcing.precipitation +end + +function update!(model::RainfallErosionAnswersModel, geometry::LandGeometry, ts) + (; precipitation) = model.boundary_conditions (; usle_k, usle_c) = model.parameters (; amount) = model.variables @@ -206,6 +238,13 @@ function update!( end end +function update_boundary_conditions!( + model::NoRainfallErosionModel, + hydrometeo_forcing::HydrometeoForcing, +) + return nothing +end + function update!(model::NoRainfallErosionModel) return nothing end diff --git a/src/erosion/river_erosion.jl b/src/erosion/river_erosion.jl index d7d0a52bc..2e2bfc586 100644 --- a/src/erosion/river_erosion.jl +++ b/src/erosion/river_erosion.jl @@ -1,16 +1,19 @@ -abstract type AbstractRiverErosionModel end +abstract type AbstractRiverErosionModel{T} end ## Potential direct river erosion structs and functions -@get_units @with_kw struct RiverErosionModelVars{T} +@get_units @with_kw struct RiverErosionModelVariables{T} # Potential river bed erosion bed::Vector{T} | "t dt-1" # Potential river bank erosion bank::Vector{T} | "t dt-1" end -function river_erosion_model_vars(n) - vars = RiverErosionModelVars(; bed = fill(mv, n), bank = fill(mv, n)) - return vars +function RiverErosionModelVariables( + n; + bed::Vector{T} = fill(mv, n), + bank::Vector{T} = fill(mv, n), +) where {T} + return RiverErosionModelVariables{T}(; bed = bed, bank = bank) end @get_units @with_kw struct RiverErosionBC{T} @@ -18,9 +21,8 @@ end waterlevel::Vector{T} | "t dt-1" end -function river_erosion_bc(n) - bc = RiverErosionBC(; waterlevel = fill(mv, n)) - return bc +function RiverErosionBC(n; waterlevel::Vector{T} = fill(mv, n)) where {T} + return RiverErosionBC{T}(; waterlevel = waterlevel) end # Parameters for the Julian Torres river erosion model @@ -29,13 +31,13 @@ end d50::Vector{T} | "mm" end -@get_units @with_kw struct RiverErosionJulianTorresModel{T} <: AbstractRiverErosionModel - boundary_conditions::RiverErosionBC{T} | "-" - parameters::RiverErosionParameters{T} | "-" - variables::RiverErosionModelVars{T} | "-" +@with_kw struct RiverErosionJulianTorresModel{T} <: AbstractRiverErosionModel{T} + boundary_conditions::RiverErosionBC{T} + parameters::RiverErosionParameters{T} + variables::RiverErosionModelVariables{T} end -function initialize_river_erosion_params(nc, config, inds) +function RiverErosionParameters(nc, config, inds) d50 = ncread( nc, config, @@ -49,11 +51,11 @@ function initialize_river_erosion_params(nc, config, inds) return river_parameters end -function initialize_river_erosion_julian_torres_model(nc, config, inds) +function RiverErosionJulianTorresModel(nc, config, inds) n = length(inds) - vars = river_erosion_model_vars(n) - params = initialize_river_erosion_params(nc, config, inds) - bc = river_erosion_bc(n) + vars = RiverErosionModelVariables(n) + params = RiverErosionParameters(nc, config, inds) + bc = RiverErosionBC(n) model = RiverErosionJulianTorresModel(; boundary_conditions = bc, parameters = params, @@ -62,6 +64,15 @@ function initialize_river_erosion_julian_torres_model(nc, config, inds) return model end +function update_boundary_conditions!( + model::RiverErosionJulianTorresModel, + hydrometeo_forcing::HydrometeoForcing, +) + (; waterlevel) = model.boundary_conditions + (; waterlevel_river) = hydrometeo_forcing + @. waterlevel = waterlevel_river +end + function update!(model::RiverErosionJulianTorresModel, geometry::RiverGeometry, ts) (; waterlevel) = model.boundary_conditions (; d50) = model.parameters diff --git a/src/erosion/soil_erosion.jl b/src/erosion/soil_erosion.jl index affd4caf0..66814d41c 100644 --- a/src/erosion/soil_erosion.jl +++ b/src/erosion/soil_erosion.jl @@ -1,7 +1,7 @@ -abstract type AbstractSoilErosionModel end +abstract type AbstractSoilErosionModel{T} end ## Total soil erosion and differentiation structs and functions -@get_units @with_kw struct SoilErosionModelVars{T} +@get_units @with_kw struct SoilErosionModelVariables{T} # Total soil erosion amount::Vector{T} | "t dt-1" # Total clay erosion @@ -16,16 +16,23 @@ abstract type AbstractSoilErosionModel end lagg::Vector{T} | "t dt-1" end -function soil_erosion_model_vars(n) - vars = SoilErosionModelVars(; - amount = fill(mv, n), - clay = fill(mv, n), - silt = fill(mv, n), - sand = fill(mv, n), - sagg = fill(mv, n), - lagg = fill(mv, n), +function SoilErosionModelVariables( + n; + amount::Vector{T} = fill(mv, n), + clay::Vector{T} = fill(mv, n), + silt::Vector{T} = fill(mv, n), + sand::Vector{T} = fill(mv, n), + sagg::Vector{T} = fill(mv, n), + lagg::Vector{T} = fill(mv, n), +) where {T} + return SoilErosionModelVariables{T}(; + amount = amount, + clay = clay, + silt = silt, + sand = sand, + sagg = sagg, + lagg = lagg, ) - return vars end @get_units @with_kw struct SoilErosionBC{T} @@ -35,10 +42,15 @@ end overland_flow_erosion::Vector{T} | "m dt-1" end -function soil_erosion_bc(n) - bc = - SoilErosionBC(; rainfall_erosion = fill(mv, n), overland_flow_erosion = fill(mv, n)) - return bc +function SoilErosionBC( + n; + rainfall_erosion::Vector{T} = fill(mv, n), + overland_flow_erosion::Vector{T} = fill(mv, n), +) where {T} + return SoilErosionBC{T}(; + rainfall_erosion = rainfall_erosion, + overland_flow_erosion = overland_flow_erosion, + ) end # Parameters for particle differentiation @@ -55,13 +67,7 @@ end lagg_fraction::Vector{T} | "-" end -@get_units @with_kw struct SoilErosionModel{T} <: AbstractSoilErosionModel - boundary_conditions::SoilErosionBC{T} | "-" - parameters::SoilErosionParameters{T} | "-" - variables::SoilErosionModelVars{T} | "-" -end - -function initialize_soil_erosion_params(nc, config, inds) +function SoilErosionParameters(nc, config, inds) clay_fraction = ncread( nc, config, @@ -119,16 +125,34 @@ function initialize_soil_erosion_params(nc, config, inds) return soil_parameters end -function initialize_soil_erosion_model(nc, config, inds) +@with_kw struct SoilErosionModel{T} <: AbstractSoilErosionModel{T} + boundary_conditions::SoilErosionBC{T} + parameters::SoilErosionParameters{T} + variables::SoilErosionModelVariables{T} +end + +function SoilErosionModel(nc, config, inds) n = length(inds) - vars = soil_erosion_model_vars(n) - params = initialize_soil_erosion_params(nc, config, inds) - bc = soil_erosion_bc(n) + vars = SoilErosionModelVariables(n) + params = SoilErosionParameters(nc, config, inds) + bc = SoilErosionBC(n) model = SoilErosionModel(; boundary_conditions = bc, parameters = params, variables = vars) return model end +function update_boundary_conditions!( + model::SoilErosionModel, + rainfall_erosion::AbstractRainfallErosionModel, + overland_flow_erosion::AbstractOverlandFlowErosionModel, +) + re = get_rainfall_erosion(rainfall_erosion) + ole = get_overland_flow_erosion(overland_flow_erosion) + (; rainfall_erosion, overland_flow_erosion) = model.boundary_conditions + @. rainfall_erosion = re + @. overland_flow_erosion = ole +end + function update!(model::SoilErosionModel) (; rainfall_erosion, overland_flow_erosion) = model.boundary_conditions (; clay_fraction, silt_fraction, sand_fraction, sagg_fraction, lagg_fraction) = diff --git a/src/forcing.jl b/src/forcing.jl index 0f90bff1d..d411cdd5a 100644 --- a/src/forcing.jl +++ b/src/forcing.jl @@ -31,7 +31,7 @@ end q_river::Vector{T} | "m3 s-1" end -function initialize_hydrometeo_forcing(n) +function HydrometeoForcing(n) hydrometeo_forcing = HydrometeoForcing(; precipitation = fill(mv, n), waterlevel_land = fill(mv, n), diff --git a/src/geometry.jl b/src/geometry.jl index 4695e8d4e..32614fad2 100644 --- a/src/geometry.jl +++ b/src/geometry.jl @@ -8,7 +8,7 @@ slope::Vector{T} | "-" end -function initialize_land_geometry(nc, config, inds) +function LandGeometry(nc, config, inds) # read x, y coordinates and calculate cell length [m] y_nc = read_y_axis(nc) x_nc = read_x_axis(nc) @@ -44,7 +44,7 @@ end slope::Vector{T} | "-" end -function initialize_river_geometry(nc, config, inds) +function RiverGeometry(nc, config, inds) riverwidth = ncread( nc, config, diff --git a/src/sediment_flux.jl b/src/sediment_flux.jl index fc4c6feaa..0f4ceb647 100644 --- a/src/sediment_flux.jl +++ b/src/sediment_flux.jl @@ -11,8 +11,8 @@ end function initialize_overland_flow_sediment(nc, config, inds, waterbodies, rivers) n = length(inds) - hydrometeo_forcing = initialize_hydrometeo_forcing(n) - geometry = initialize_land_geometry(nc, config, inds) + hydrometeo_forcing = HydrometeoForcing(n) + geometry = LandGeometry(nc, config, inds) # Check what transport capacity equation will be used do_river = get(config.model, "runrivermodel", false)::Bool # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] @@ -20,23 +20,21 @@ function initialize_overland_flow_sediment(nc, config, inds, waterbodies, rivers if do_river || landtransportmethod == "yalinpart" transport_capacity_model = - initialize_transport_capacity_yalin_diff_model(nc, config, inds) + TransportCapacityYalinDifferentiationModel(nc, config, inds) elseif landtransportmethod == "govers" - transport_capacity_model = - initialize_transport_capacity_govers_model(nc, config, inds) + transport_capacity_model = TransportCapacityGoversModel(nc, config, inds) elseif landtransportmethod == "yalin" - transport_capacity_model = - initialize_transport_capacity_yalin_model(nc, config, inds) + transport_capacity_model = TransportCapacityYalinModel(nc, config, inds) else error("Unknown land transport method: $landtransportmethod") end if do_river || landtransportmethod == "yalinpart" - sediment_flux_model = initialize_sediment_land_transport_differentiation_model(inds) - to_river_model = initialize_sediment_to_river_differentiation_model(inds) + sediment_flux_model = SedimentLandTransportDifferentiationModel(inds) + to_river_model = SedimentToRiverDifferentiationModel(inds) else - sediment_flux_model = initialize_sediment_land_transport_model(inds) - to_river_model = initialize_sediment_to_river_model(inds) + sediment_flux_model = SedimentLandTransportModel(inds) + to_river_model = SedimentToRiverModel(inds) end overland_flow_sediment = OverlandFlowSediment{ @@ -92,39 +90,34 @@ end function initialize_river_flow_sediment(nc, config, inds, waterbodies) n = length(inds) - hydrometeo_forcing = initialize_hydrometeo_forcing(n) - geometry = initialize_river_geometry(nc, config, inds) + hydrometeo_forcing = HydrometeoForcing(n) + geometry = RiverGeometry(nc, config, inds) # Check what transport capacity equation will be used # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] transport_method = get(config.model, "rivtransportmethod", "bagnold")::String if transport_method == "bagnold" - transport_capacity_model = - initialize_transport_capacity_bagnold_model(nc, config, inds) + transport_capacity_model = TransportCapacityBagnoldModel(nc, config, inds) elseif transport_method == "engelund" - transport_capacity_model = - initialize_transport_capacity_engelund_model(nc, config, inds) + transport_capacity_model = TransportCapacityEngelundModel(nc, config, inds) elseif transport_method == "yang" - transport_capacity_model = - initialize_transport_capacity_yang_model(nc, config, inds) + transport_capacity_model = TransportCapacityYangModel(nc, config, inds) elseif transport_method == "kodatie" - transport_capacity_model = - initialize_transport_capacity_kodatie_model(nc, config, inds) + transport_capacity_model = TransportCapacityKodatieModel(nc, config, inds) elseif transport_method == "molinas" - transport_capacity_model = - initialize_transport_capacity_molinas_model(nc, config, inds) + transport_capacity_model = TransportCapacityMolinasModel(nc, config, inds) else error("Unknown river transport method: $transport_method") end # Potential river erosion - potential_erosion_model = initialize_river_erosion_julian_torres_model(nc, config, inds) + potential_erosion_model = RiverErosionJulianTorresModel(nc, config, inds) # Sediment flux in river / mass balance - sediment_flux_model = initialize_sediment_river_transport_model(nc, config, inds) + sediment_flux_model = SedimentRiverTransportModel(nc, config, inds) # Concentrations - concentrations_model = initialize_sediment_concentrations_river_model(nc, config, inds) + concentrations_model = SedimentConcentrationsRiverModel(nc, config, inds) river_sediment = RiverSediment{ typeof(transport_capacity_model), @@ -159,9 +152,7 @@ function update!( update!(model.transport_capacity, model.geometry, ts) # Potential maximum river erosion - (; waterlevel) = model.potential_erosion.boundary_conditions - (; waterlevel_river) = model.hydrometeo_forcing - @. waterlevel = waterlevel_river + update_boundary_conditions!(model.potential_erosion, model.hydrometeo_forcing) update!(model.potential_erosion, model.geometry, ts) # River transport diff --git a/src/sediment_transport/land_to_river.jl b/src/sediment_transport/land_to_river.jl index 5ece4f514..8f196f9d5 100644 --- a/src/sediment_transport/land_to_river.jl +++ b/src/sediment_transport/land_to_river.jl @@ -1,14 +1,13 @@ -abstract type AbstractSedimentToRiverModel end +abstract type AbstractSedimentToRiverModel{T} end ## Total sediment transport in overland flow structs and functions -@get_units @with_kw struct SedimentToRiverVars{T} +@get_units @with_kw struct SedimentToRiverVariables{T} # Total sediment reaching the river amount::Vector{T} | "t dt-1" end -function sediment_to_river_vars(n) - vars = SedimentToRiverVars(; amount = fill(mv, n)) - return vars +function SedimentToRiverVariables(n; amount::Vector{T} = fill(mv, n)) where {T} + return SedimentToRiverVariables{T}(; amount = amount) end @get_units @with_kw struct SedimentToRiverBC{T} @@ -16,20 +15,19 @@ end deposition::Vector{T} | "t dt-1" end -function sediment_to_river_bc(n) - bc = SedimentToRiverBC(; deposition = fill(mv, n)) - return bc +function SedimentToRiverBC(n; deposition::Vector{T} = fill(mv, n)) where {T} + return SedimentToRiverBC{T}(; deposition = deposition) end -@get_units @with_kw struct SedimentToRiverModel{T} <: AbstractSedimentToRiverModel - boundary_conditions::SedimentToRiverBC{T} | "-" - variables::SedimentToRiverVars{T} | "-" +@with_kw struct SedimentToRiverModel{T} <: AbstractSedimentToRiverModel{T} + boundary_conditions::SedimentToRiverBC{T} + variables::SedimentToRiverVariables{T} end -function initialize_sediment_to_river_model(inds) +function SedimentToRiverModel(inds) n = length(inds) - vars = sediment_to_river_vars(n) - bc = sediment_to_river_bc(n) + vars = SedimentToRiverVariables(n) + bc = SedimentToRiverBC(n) model = SedimentToRiverModel(; boundary_conditions = bc, variables = vars) return model end @@ -51,7 +49,7 @@ function update!(model::SedimentToRiverModel, rivers) end ## Different particles reaching the river structs and functions -@get_units @with_kw struct SedimentToRiverDifferentiationVars{T} +@get_units @with_kw struct SedimentToRiverDifferentiationVariables{T} # Total sediment flux amount::Vector{T} | "t dt-1" # Clay flux @@ -66,16 +64,23 @@ end lagg::Vector{T} | "t dt-1" end -function sediment_to_river_differentiation_vars(n) - vars = SedimentToRiverDifferentiationVars(; - amount = fill(mv, n), - clay = fill(mv, n), - silt = fill(mv, n), - sand = fill(mv, n), - sagg = fill(mv, n), - lagg = fill(mv, n), +function SedimentToRiverDifferentiationVariables( + n; + amount::Vector{T} = fill(mv, n), + clay::Vector{T} = fill(mv, n), + silt::Vector{T} = fill(mv, n), + sand::Vector{T} = fill(mv, n), + sagg::Vector{T} = fill(mv, n), + lagg::Vector{T} = fill(mv, n), +) where {T} + return SedimentToRiverDifferentiationVariables{T}(; + amount = amount, + clay = clay, + silt = silt, + sand = sand, + sagg = sagg, + lagg = lagg, ) - return vars end @get_units @with_kw struct SedimentToRiverDifferentiationBC{T} @@ -91,27 +96,33 @@ end deposition_lagg::Vector{T} | "t dt-1" end -function sediment_to_river_differentiation_bc(n) - bc = SedimentToRiverDifferentiationBC(; - deposition_clay = fill(mv, n), - deposition_silt = fill(mv, n), - deposition_sand = fill(mv, n), - deposition_sagg = fill(mv, n), - deposition_lagg = fill(mv, n), +function SedimentToRiverDifferentiationBC( + n; + deposition_clay::Vector{T} = fill(mv, n), + deposition_silt::Vector{T} = fill(mv, n), + deposition_sand::Vector{T} = fill(mv, n), + deposition_sagg::Vector{T} = fill(mv, n), + deposition_lagg::Vector{T} = fill(mv, n), +) where {T} + return SedimentToRiverDifferentiationBC{T}(; + deposition_clay = deposition_clay, + deposition_silt = deposition_silt, + deposition_sand = deposition_sand, + deposition_sagg = deposition_sagg, + deposition_lagg = deposition_lagg, ) - return bc end @get_units @with_kw struct SedimentToRiverDifferentiationModel{T} <: - AbstractSedimentToRiverModel + AbstractSedimentToRiverModel{T} boundary_conditions::SedimentToRiverDifferentiationBC{T} | "-" - variables::SedimentToRiverDifferentiationVars{T} | "-" + variables::SedimentToRiverDifferentiationVariables{T} | "-" end -function initialize_sediment_to_river_differentiation_model(inds) +function SedimentToRiverDifferentiationModel(inds) n = length(inds) - vars = sediment_to_river_differentiation_vars(n) - bc = sediment_to_river_differentiation_bc(n) + vars = SedimentToRiverDifferentiationVariables(n) + bc = SedimentToRiverDifferentiationBC(n) model = SedimentToRiverDifferentiationModel(; boundary_conditions = bc, variables = vars) return model diff --git a/src/sediment_transport/overland_flow_transport.jl b/src/sediment_transport/overland_flow_transport.jl index 66eca1bde..442e62140 100644 --- a/src/sediment_transport/overland_flow_transport.jl +++ b/src/sediment_transport/overland_flow_transport.jl @@ -1,15 +1,18 @@ -abstract type AbstractSedimentLandTransportModel end +abstract type AbstractSedimentLandTransportModel{T} end ## Total sediment transport in overland flow structs and functions -@get_units @with_kw struct SedimentLandTransportVars{T} +@get_units @with_kw struct SedimentLandTransportVariables{T} # Total sediment flux amount::Vector{T} | "t dt-1" deposition::Vector{T} | "t dt-1" end -function sediment_land_transport_vars(n) - vars = SedimentLandTransportVars(; amount = fill(mv, n), deposition = fill(mv, n)) - return vars +function SedimentLandTransportVariables( + n; + amount::Vector{T} = fill(mv, n), + deposition::Vector{T} = fill(mv, n), +) where {T} + return SedimentLandTransportVariables{T}(; amount = amount, deposition = deposition) end @get_units @with_kw struct SedimentLandTransportBC{T} @@ -19,21 +22,26 @@ end transport_capacity::Vector{T} | "t dt-1" end -function sediment_land_transport_bc(n) - bc = SedimentLandTransportBC(; erosion = fill(mv, n), transport_capacity = fill(mv, n)) - return bc +function SedimentLandTransportBC( + n; + erosion::Vector{T} = fill(mv, n), + transport_capacity::Vector{T} = fill(mv, n), +) where {T} + return SedimentLandTransportBC{T}(; + erosion = erosion, + transport_capacity = transport_capacity, + ) end -@get_units @with_kw struct SedimentLandTransportModel{T} <: - AbstractSedimentLandTransportModel - boundary_conditions::SedimentLandTransportBC{T} | "-" - variables::SedimentLandTransportVars{T} | "-" +@with_kw struct SedimentLandTransportModel{T} <: AbstractSedimentLandTransportModel{T} + boundary_conditions::SedimentLandTransportBC{T} + variables::SedimentLandTransportVariables{T} end -function initialize_sediment_land_transport_model(inds) +function SedimentLandTransportModel(inds) n = length(inds) - vars = sediment_land_transport_vars(n) - bc = sediment_land_transport_bc(n) + vars = SedimentLandTransportVariables(n) + bc = SedimentLandTransportBC(n) model = SedimentLandTransportModel(; boundary_conditions = bc, variables = vars) return model end @@ -60,7 +68,7 @@ function update!(model::SedimentLandTransportModel, network) end ## Total transport capacity with particle differentiation structs and functions -@get_units @with_kw struct SedimentLandTransportDifferentiationVars{T} +@get_units @with_kw struct SedimentLandTransportDifferentiationVariables{T} # Total sediment flux amount::Vector{T} | "t dt-1" # Deposition @@ -87,22 +95,35 @@ end deposition_lagg::Vector{T} | "t dt-1" end -function sediment_land_transport_differentiation_vars(n) - vars = SedimentLandTransportDifferentiationVars(; - amount = fill(mv, n), - deposition = fill(mv, n), - clay = fill(mv, n), - deposition_clay = fill(mv, n), - silt = fill(mv, n), - deposition_silt = fill(mv, n), - sand = fill(mv, n), - deposition_sand = fill(mv, n), - sagg = fill(mv, n), - deposition_sagg = fill(mv, n), - lagg = fill(mv, n), - deposition_lagg = fill(mv, n), +function SedimentLandTransportDifferentiationVariables( + n; + amount::Vector{T} = fill(mv, n), + deposition::Vector{T} = fill(mv, n), + clay::Vector{T} = fill(mv, n), + deposition_clay::Vector{T} = fill(mv, n), + silt::Vector{T} = fill(mv, n), + deposition_silt::Vector{T} = fill(mv, n), + sand::Vector{T} = fill(mv, n), + deposition_sand::Vector{T} = fill(mv, n), + sagg::Vector{T} = fill(mv, n), + deposition_sagg::Vector{T} = fill(mv, n), + lagg::Vector{T} = fill(mv, n), + deposition_lagg::Vector{T} = fill(mv, n), +) where {T} + return SedimentLandTransportDifferentiationVariables{T}(; + amount = amount, + deposition = deposition, + clay = clay, + deposition_clay = deposition_clay, + silt = silt, + deposition_silt = deposition_silt, + sand = sand, + deposition_sand = deposition_sand, + sagg = sagg, + deposition_sagg = deposition_sagg, + lagg = lagg, + deposition_lagg = deposition_lagg, ) - return vars end @get_units @with_kw struct SedimentLandTransportDifferentiationBC{T} @@ -128,32 +149,43 @@ end transport_capacity_lagg::Vector{T} | "t dt-1" end -function sediment_land_transport_differentiation_bc(n) - bc = SedimentLandTransportDifferentiationBC(; - erosion_clay = fill(mv, n), - erosion_silt = fill(mv, n), - erosion_sand = fill(mv, n), - erosion_sagg = fill(mv, n), - erosion_lagg = fill(mv, n), - transport_capacity_clay = fill(mv, n), - transport_capacity_silt = fill(mv, n), - transport_capacity_sand = fill(mv, n), - transport_capacity_sagg = fill(mv, n), - transport_capacity_lagg = fill(mv, n), +function SedimentLandTransportDifferentiationBC( + n; + erosion_clay::Vector{T} = fill(mv, n), + erosion_silt::Vector{T} = fill(mv, n), + erosion_sand::Vector{T} = fill(mv, n), + erosion_sagg::Vector{T} = fill(mv, n), + erosion_lagg::Vector{T} = fill(mv, n), + transport_capacity_clay::Vector{T} = fill(mv, n), + transport_capacity_silt::Vector{T} = fill(mv, n), + transport_capacity_sand::Vector{T} = fill(mv, n), + transport_capacity_sagg::Vector{T} = fill(mv, n), + transport_capacity_lagg::Vector{T} = fill(mv, n), +) where {T} + return SedimentLandTransportDifferentiationBC{T}(; + erosion_clay = erosion_clay, + erosion_silt = erosion_silt, + erosion_sand = erosion_sand, + erosion_sagg = erosion_sagg, + erosion_lagg = erosion_lagg, + transport_capacity_clay = transport_capacity_clay, + transport_capacity_silt = transport_capacity_silt, + transport_capacity_sand = transport_capacity_sand, + transport_capacity_sagg = transport_capacity_sagg, + transport_capacity_lagg = transport_capacity_lagg, ) - return bc end -@get_units @with_kw struct SedimentLandTransportDifferentiationModel{T} <: - AbstractSedimentLandTransportModel - boundary_conditions::SedimentLandTransportDifferentiationBC{T} | "-" - variables::SedimentLandTransportDifferentiationVars{T} | "-" +@with_kw struct SedimentLandTransportDifferentiationModel{T} <: + AbstractSedimentLandTransportModel{T} + boundary_conditions::SedimentLandTransportDifferentiationBC{T} + variables::SedimentLandTransportDifferentiationVariables{T} end -function initialize_sediment_land_transport_differentiation_model(inds) +function SedimentLandTransportDifferentiationModel(inds) n = length(inds) - vars = sediment_land_transport_differentiation_vars(n) - bc = sediment_land_transport_differentiation_bc(n) + vars = SedimentLandTransportDifferentiationVariables(n) + bc = SedimentLandTransportDifferentiationBC(n) model = SedimentLandTransportDifferentiationModel(; boundary_conditions = bc, variables = vars, diff --git a/src/sediment_transport/river_transport.jl b/src/sediment_transport/river_transport.jl index a658bb497..74b11bd5b 100644 --- a/src/sediment_transport/river_transport.jl +++ b/src/sediment_transport/river_transport.jl @@ -1,7 +1,7 @@ -abstract type AbstractSedimentRiverTransportModel end +abstract type AbstractSedimentRiverTransportModel{T} end ## Total sediment transport in overland flow structs and functions -@get_units @with_kw struct SedimentRiverTransportVars{T} +@get_units @with_kw struct SedimentRiverTransportVariables{T} # Sediment flux [ton] amount::Vector{T} | "t dt-1" clay::Vector{T} | "t dt-1" @@ -30,31 +30,53 @@ abstract type AbstractSedimentRiverTransportModel end store_gravel::Vector{T} | "t dt-1" end -function sediment_river_transport_vars(n) - vars = SedimentRiverTransportVars(; - amount = fill(mv, n), - clay = fill(0.0, n), - silt = fill(0.0, n), - sand = fill(0.0, n), - sagg = fill(0.0, n), - lagg = fill(0.0, n), - gravel = fill(0.0, n), - deposition = fill(mv, n), - erosion = fill(mv, n), - leftover_clay = fill(0.0, n), - leftover_silt = fill(0.0, n), - leftover_sand = fill(0.0, n), - leftover_sagg = fill(0.0, n), - leftover_lagg = fill(0.0, n), - leftover_gravel = fill(0.0, n), - store_clay = fill(0.0, n), - store_silt = fill(0.0, n), - store_sand = fill(0.0, n), - store_sagg = fill(0.0, n), - store_lagg = fill(0.0, n), - store_gravel = fill(0.0, n), +function SedimentRiverTransportVariables( + n; + amount::Vector{T} = fill(mv, n), + clay::Vector{T} = fill(0.0, n), + silt::Vector{T} = fill(0.0, n), + sand::Vector{T} = fill(0.0, n), + sagg::Vector{T} = fill(0.0, n), + lagg::Vector{T} = fill(0.0, n), + gravel::Vector{T} = fill(0.0, n), + deposition::Vector{T} = fill(mv, n), + erosion::Vector{T} = fill(mv, n), + leftover_clay::Vector{T} = fill(0.0, n), + leftover_silt::Vector{T} = fill(0.0, n), + leftover_sand::Vector{T} = fill(0.0, n), + leftover_sagg::Vector{T} = fill(0.0, n), + leftover_lagg::Vector{T} = fill(0.0, n), + leftover_gravel::Vector{T} = fill(0.0, n), + store_clay::Vector{T} = fill(0.0, n), + store_silt::Vector{T} = fill(0.0, n), + store_sand::Vector{T} = fill(0.0, n), + store_sagg::Vector{T} = fill(0.0, n), + store_lagg::Vector{T} = fill(0.0, n), + store_gravel::Vector{T} = fill(0.0, n), +) where {T} + return SedimentRiverTransportVariables{T}(; + amount = amount, + clay = clay, + silt = silt, + sand = sand, + sagg = sagg, + lagg = lagg, + gravel = gravel, + deposition = deposition, + erosion = erosion, + leftover_clay = leftover_clay, + leftover_silt = leftover_silt, + leftover_sand = leftover_sand, + leftover_sagg = leftover_sagg, + leftover_lagg = leftover_lagg, + leftover_gravel = leftover_gravel, + store_clay = store_clay, + store_silt = store_silt, + store_sand = store_sand, + store_sagg = store_sagg, + store_lagg = store_lagg, + store_gravel = store_gravel, ) - return vars end @get_units @with_kw struct SedimentRiverTransportBC{T} @@ -75,20 +97,31 @@ end potential_erosion_river_bank::Vector{T} | "t dt-1" end -function sediment_river_transport_bc(n) - bc = SedimentRiverTransportBC(; - waterlevel = fill(mv, n), - q = fill(mv, n), - transport_capacity = fill(mv, n), - erosion_land_clay = fill(mv, n), - erosion_land_silt = fill(mv, n), - erosion_land_sand = fill(mv, n), - erosion_land_sagg = fill(mv, n), - erosion_land_lagg = fill(mv, n), - potential_erosion_river_bed = fill(mv, n), - potential_erosion_river_bank = fill(mv, n), +function SedimentRiverTransportBC( + n; + waterlevel::Vector{T} = fill(mv, n), + q::Vector{T} = fill(mv, n), + transport_capacity::Vector{T} = fill(mv, n), + erosion_land_clay::Vector{T} = fill(mv, n), + erosion_land_silt::Vector{T} = fill(mv, n), + erosion_land_sand::Vector{T} = fill(mv, n), + erosion_land_sagg::Vector{T} = fill(mv, n), + erosion_land_lagg::Vector{T} = fill(mv, n), + potential_erosion_river_bed::Vector{T} = fill(mv, n), + potential_erosion_river_bank::Vector{T} = fill(mv, n), +) where {T} + return SedimentRiverTransportBC{T}(; + waterlevel = waterlevel, + q = q, + transport_capacity = transport_capacity, + erosion_land_clay = erosion_land_clay, + erosion_land_silt = erosion_land_silt, + erosion_land_sand = erosion_land_sand, + erosion_land_sagg = erosion_land_sagg, + erosion_land_lagg = erosion_land_lagg, + potential_erosion_river_bed = potential_erosion_river_bed, + potential_erosion_river_bank = potential_erosion_river_bank, ) - return bc end # Parameters for river transport @@ -121,7 +154,7 @@ end waterbodies_trapping_efficiency::Vector{T} | "-" end -function initialize_sediment_river_transport_params(nc, config, inds) +function SedimentRiverTransportParameters(nc, config, inds) n = length(inds) clay_fraction = ncread( nc, @@ -291,18 +324,17 @@ function initialize_sediment_river_transport_params(nc, config, inds) return river_parameters end -@get_units @with_kw struct SedimentRiverTransportModel{T} <: - AbstractSedimentRiverTransportModel - boundary_conditions::SedimentRiverTransportBC{T} | "-" - parameters::SedimentRiverTransportParameters{T} | "-" - variables::SedimentRiverTransportVars{T} | "-" +@with_kw struct SedimentRiverTransportModel{T} <: AbstractSedimentRiverTransportModel{T} + boundary_conditions::SedimentRiverTransportBC{T} + parameters::SedimentRiverTransportParameters{T} + variables::SedimentRiverTransportVariables{T} end -function initialize_sediment_river_transport_model(nc, config, inds) +function SedimentRiverTransportModel(nc, config, inds) n = length(inds) - vars = sediment_river_transport_vars(n) - params = initialize_sediment_river_transport_params(nc, config, inds) - bc = sediment_river_transport_bc(n) + vars = SedimentRiverTransportVariables(n) + params = SedimentRiverTransportParameters(nc, config, inds) + bc = SedimentRiverTransportBC(n) model = SedimentRiverTransportModel(; boundary_conditions = bc, parameters = params, @@ -545,15 +577,6 @@ function update!( erosion_sagg + erosion_lagg + erosion_gravel - #if erosion[v] > transport_capacity[v] - # println("Erosion exceeds transport capacity for node $v") - #end - if v == 5642 - println("Erosion at node 5642: ", erosion[v]) - println("Transport capacity at node 5642: ", transport_capacity[v]) - println("Sediment stored at node 5642: ", store_sediment) - println("Sediemnt need: ", sediment_need) - end ### Deposition / settling ### # Different deposition if waterbody outlet or river @@ -747,10 +770,10 @@ function update!( end end -abstract type AbstractSedimentConcentrationsRiverModel end +abstract type AbstractSedimentConcentrationsRiverModel{T} end ## Total sediment transport in overland flow structs and functions -@get_units @with_kw struct SedimentConcentrationsRiverVars{T} +@get_units @with_kw struct SedimentConcentrationsRiverVariables{T} # Total sediment concentration in the river total::Vector{T} | "g m-3" # suspended sediemnt concentration in the river @@ -759,13 +782,17 @@ abstract type AbstractSedimentConcentrationsRiverModel end bed::Vector{T} | "g m-3" end -function sediment_concentrations_river_vars(n) - vars = SedimentConcentrationsRiverVars(; - total = fill(mv, n), - suspended = fill(mv, n), - bed = fill(mv, n), +function SedimentConcentrationsRiverVariables( + n; + total::Vector{T} = fill(mv, n), + suspended::Vector{T} = fill(mv, n), + bed::Vector{T} = fill(mv, n), +) where {T} + return SedimentConcentrationsRiverVariables{T}(; + total = total, + suspended = suspended, + bed = bed, ) - return vars end @get_units @with_kw struct SedimentConcentrationsRiverBC{T} @@ -786,18 +813,27 @@ end gravel::Vector{T} | "g m-3" end -function sediment_concentrations_river_bc(n) - bc = SedimentConcentrationsRiverBC(; - q = fill(mv, n), - waterlevel = fill(mv, n), - clay = fill(mv, n), - silt = fill(mv, n), - sand = fill(mv, n), - sagg = fill(mv, n), - lagg = fill(mv, n), - gravel = fill(mv, n), +function SedimentConcentrationsRiverBC( + n; + q::Vector{T} = fill(mv, n), + waterlevel::Vector{T} = fill(mv, n), + clay::Vector{T} = fill(mv, n), + silt::Vector{T} = fill(mv, n), + sand::Vector{T} = fill(mv, n), + sagg::Vector{T} = fill(mv, n), + lagg::Vector{T} = fill(mv, n), + gravel::Vector{T} = fill(mv, n), +) where {T} + return SedimentConcentrationsRiverBC{T}(; + q = q, + waterlevel = waterlevel, + clay = clay, + silt = silt, + sand = sand, + sagg = sagg, + lagg = lagg, + gravel = gravel, ) - return bc end # Common parameters for transport capacity models @@ -816,7 +852,7 @@ end dm_gravel::Vector{T} | "µm" end -function initialize_sediment_concentrations_river_params(nc, config, inds) +function SedimentConcentrationsRiverParameters(nc, config, inds) dm_clay = ncread( nc, config, @@ -877,18 +913,18 @@ function initialize_sediment_concentrations_river_params(nc, config, inds) return conc_parameters end -@get_units @with_kw struct SedimentConcentrationsRiverModel{T} <: - AbstractSedimentConcentrationsRiverModel - boundary_conditions::SedimentConcentrationsRiverBC{T} | "-" - parameters::SedimentConcentrationsRiverParameters{T} | "-" - variables::SedimentConcentrationsRiverVars{T} | "-" +@with_kw struct SedimentConcentrationsRiverModel{T} <: + AbstractSedimentConcentrationsRiverModel{T} + boundary_conditions::SedimentConcentrationsRiverBC{T} + parameters::SedimentConcentrationsRiverParameters{T} + variables::SedimentConcentrationsRiverVariables{T} end -function initialize_sediment_concentrations_river_model(nc, config, inds) +function SedimentConcentrationsRiverModel(nc, config, inds) n = length(inds) - vars = sediment_concentrations_river_vars(n) - params = initialize_sediment_concentrations_river_params(nc, config, inds) - bc = sediment_concentrations_river_bc(n) + vars = SedimentConcentrationsRiverVariables(n) + params = SedimentConcentrationsRiverParameters(nc, config, inds) + bc = SedimentConcentrationsRiverBC(n) model = SedimentConcentrationsRiverModel(; boundary_conditions = bc, parameters = params, diff --git a/src/sediment_transport/transport_capacity.jl b/src/sediment_transport/transport_capacity.jl index 1931e4258..58eaf29e1 100644 --- a/src/sediment_transport/transport_capacity.jl +++ b/src/sediment_transport/transport_capacity.jl @@ -1,14 +1,13 @@ -abstract type AbstractTransportCapacityModel end +abstract type AbstractTransportCapacityModel{T} end ## Total sediment transport capacity structs and functions -@get_units @with_kw struct TransportCapacityModelVars{T} +@get_units @with_kw struct TransportCapacityModelVariables{T} # Total sediment transport capacity amount::Vector{T} | "t dt-1" end -function transport_capacity_model_vars(n) - vars = TransportCapacityModelVars(; amount = fill(mv, n)) - return vars +function TransportCapacityModelVariables(n; amount::Vector{T} = fill(mv, n)) where {T} + return TransportCapacityModelVariables{T}(; amount = amount) end @get_units @with_kw struct TransportCapacityBC{T} @@ -18,9 +17,12 @@ end waterlevel::Vector{T} | "m" end -function transport_capacity_bc(n) - bc = TransportCapacityBC(; q = fill(mv, n), waterlevel = fill(mv, n)) - return bc +function TransportCapacityBC( + n; + q::Vector{T} = fill(mv, n), + waterlevel::Vector{T} = fill(mv, n), +) where {T} + return TransportCapacityBC{T}(; q = q, waterlevel = waterlevel) end function update_boundary_conditions!( @@ -54,7 +56,7 @@ end n_govers::Vector{T} | "-" end -function initialize_transport_capacity_govers_params(nc, config, inds) +function TransportCapacityGoversParameters(nc, config, inds) slope = ncread( nc, config, @@ -97,17 +99,17 @@ function initialize_transport_capacity_govers_params(nc, config, inds) return tc_parameters end -@get_units @with_kw struct TransportCapacityGoversModel{T} <: AbstractTransportCapacityModel - boundary_conditions::TransportCapacityBC{T} | "-" - parameters::TransportCapacityGoversParameters{T} | "-" - variables::TransportCapacityModelVars{T} | "-" +@with_kw struct TransportCapacityGoversModel{T} <: AbstractTransportCapacityModel{T} + boundary_conditions::TransportCapacityBC{T} + parameters::TransportCapacityGoversParameters{T} + variables::TransportCapacityModelVariables{T} end -function initialize_transport_capacity_govers_model(nc, config, inds) +function TransportCapacityGoversModel(nc, config, inds) n = length(inds) - vars = transport_capacity_model_vars(n) - params = initialize_transport_capacity_govers_params(nc, config, inds) - bc = transport_capacity_bc(n) + vars = TransportCapacityModelVariables(n) + params = TransportCapacityGoversParameters(nc, config, inds) + bc = TransportCapacityBC(n) model = TransportCapacityGoversModel(; boundary_conditions = bc, parameters = params, @@ -148,7 +150,7 @@ end d50::Vector{T} | "mm" end -function initialize_transport_capacity_yalin_params(nc, config, inds) +function TransportCapacityYalinParameters(nc, config, inds) slope = ncread( nc, config, @@ -179,17 +181,17 @@ function initialize_transport_capacity_yalin_params(nc, config, inds) return tc_parameters end -@get_units @with_kw struct TransportCapacityYalinModel{T} <: AbstractTransportCapacityModel - boundary_conditions::TransportCapacityBC{T} | "-" - parameters::TransportCapacityYalinParameters{T} | "-" - variables::TransportCapacityModelVars{T} | "-" +@with_kw struct TransportCapacityYalinModel{T} <: AbstractTransportCapacityModel{T} + boundary_conditions::TransportCapacityBC{T} + parameters::TransportCapacityYalinParameters{T} + variables::TransportCapacityModelVariables{T} end -function initialize_transport_capacity_yalin_model(nc, config, inds) +function TransportCapacityYalinModel(nc, config, inds) n = length(inds) - vars = transport_capacity_model_vars(n) - params = initialize_transport_capacity_yalin_params(nc, config, inds) - bc = transport_capacity_bc(n) + vars = TransportCapacityModelVariables(n) + params = TransportCapacityYalinParameters(nc, config, inds) + bc = TransportCapacityBC(n) model = TransportCapacityYalinModel(; boundary_conditions = bc, parameters = params, @@ -220,7 +222,7 @@ function update!(model::TransportCapacityYalinModel, width, waterbodies, rivers, end ## Total transport capacity with particle differentiation structs and functions -@get_units @with_kw struct TransportCapacityYalinDifferentiationModelVars{T} +@get_units @with_kw struct TransportCapacityYalinDifferentiationModelVariables{T} # Total sediment transport capacity amount::Vector{T} | "t dt-1" # Transport capacity clay @@ -235,16 +237,23 @@ end lagg::Vector{T} | "t dt-1" end -function transport_capacity_yalin_differentiation_model_vars(n) - vars = TransportCapacityYalinDifferentiationModelVars(; - amount = fill(mv, n), - clay = fill(mv, n), - silt = fill(mv, n), - sand = fill(mv, n), - sagg = fill(mv, n), - lagg = fill(mv, n), +function TransportCapacityYalinDifferentiationModelVariables( + n; + amount::Vector{T} = fill(mv, n), + clay::Vector{T} = fill(mv, n), + silt::Vector{T} = fill(mv, n), + sand::Vector{T} = fill(mv, n), + sagg::Vector{T} = fill(mv, n), + lagg::Vector{T} = fill(mv, n), +) where {T} + return TransportCapacityYalinDifferentiationModelVariables{T}(; + amount = amount, + clay = clay, + silt = silt, + sand = sand, + sagg = sagg, + lagg = lagg, ) - return vars end # Common parameters for transport capacity models @@ -263,7 +272,7 @@ end dm_lagg::Vector{T} | "µm" end -function initialize_transport_capacity_yalin_diff_params(nc, config, inds) +function TransportCapacityYalinDifferentiationParameters(nc, config, inds) density = ncread( nc, config, @@ -324,18 +333,18 @@ function initialize_transport_capacity_yalin_diff_params(nc, config, inds) return tc_parameters end -@get_units @with_kw struct TransportCapacityYalinDifferentiationModel{T} <: - AbstractTransportCapacityModel - boundary_conditions::TransportCapacityBC{T} | "-" - parameters::TransportCapacityYalinDifferentiationParameters{T} | "-" - variables::TransportCapacityYalinDifferentiationModelVars{T} | "-" +@with_kw struct TransportCapacityYalinDifferentiationModel{T} <: + AbstractTransportCapacityModel{T} + boundary_conditions::TransportCapacityBC{T} + parameters::TransportCapacityYalinDifferentiationParameters{T} + variables::TransportCapacityYalinDifferentiationModelVariables{T} end -function initialize_transport_capacity_yalin_diff_model(nc, config, inds) +function TransportCapacityYalinDifferentiationModel(nc, config, inds) n = length(inds) - vars = transport_capacity_yalin_differentiation_model_vars(n) - params = initialize_transport_capacity_yalin_diff_params(nc, config, inds) - bc = transport_capacity_bc(n) + vars = TransportCapacityYalinDifferentiationModelVariables(n) + params = TransportCapacityYalinDifferentiationParameters(nc, config, inds) + bc = TransportCapacityBC(n) model = TransportCapacityYalinDifferentiationModel(; boundary_conditions = bc, parameters = params, @@ -439,7 +448,7 @@ end d50::Vector{T} | "mm" end -function initialize_transport_capacity_river_params(nc, config, inds) +function TransportCapacityRiverParameters(nc, config, inds) density = ncread( nc, config, @@ -469,7 +478,7 @@ end e_bagnold::Vector{T} | "-" end -function initialize_transport_capacity_bagnold_params(nc, config, inds) +function TransportCapacityBagnoldParameters(nc, config, inds) c_bagnold = ncread( nc, config, @@ -492,18 +501,17 @@ function initialize_transport_capacity_bagnold_params(nc, config, inds) return tc_parameters end -@get_units @with_kw struct TransportCapacityBagnoldModel{T} <: - AbstractTransportCapacityModel - boundary_conditions::TransportCapacityBC{T} | "-" - parameters::TransportCapacityBagnoldParameters{T} | "-" - variables::TransportCapacityModelVars{T} | "-" +@with_kw struct TransportCapacityBagnoldModel{T} <: AbstractTransportCapacityModel{T} + boundary_conditions::TransportCapacityBC{T} + parameters::TransportCapacityBagnoldParameters{T} + variables::TransportCapacityModelVariables{T} end -function initialize_transport_capacity_bagnold_model(nc, config, inds) +function TransportCapacityBagnoldModel(nc, config, inds) n = length(inds) - vars = transport_capacity_model_vars(n) - params = initialize_transport_capacity_bagnold_params(nc, config, inds) - bc = transport_capacity_bc(n) + vars = TransportCapacityModelVariables(n) + params = TransportCapacityBagnoldParameters(nc, config, inds) + bc = TransportCapacityBC(n) model = TransportCapacityBagnoldModel(; boundary_conditions = bc, parameters = params, @@ -534,18 +542,17 @@ function update!(model::TransportCapacityBagnoldModel, geometry::RiverGeometry, end # Engelund and Hansen parameters for transport capacity models -@get_units @with_kw struct TransportCapacityEngelundModel{T} <: - AbstractTransportCapacityModel - boundary_conditions::TransportCapacityBC{T} | "-" - parameters::TransportCapacityRiverParameters{T} | "-" - variables::TransportCapacityModelVars{T} | "-" +@with_kw struct TransportCapacityEngelundModel{T} <: AbstractTransportCapacityModel{T} + boundary_conditions::TransportCapacityBC{T} + parameters::TransportCapacityRiverParameters{T} + variables::TransportCapacityModelVariables{T} end -function initialize_transport_capacity_engelund_model(nc, config, inds) +function TransportCapacityEngelundModel(nc, config, inds) n = length(inds) - vars = transport_capacity_model_vars(n) - params = initialize_transport_capacity_river_params(nc, config, inds) - bc = transport_capacity_bc(n) + vars = TransportCapacityModelVariables(n) + params = TransportCapacityRiverParameters(nc, config, inds) + bc = TransportCapacityBC(n) model = TransportCapacityEngelundModel(; boundary_conditions = bc, parameters = params, @@ -586,7 +593,7 @@ end d_kodatie::Vector{T} | "-" end -function initialize_transport_capacity_kodatie_params(nc, config, inds) +function TransportCapacityKodatieParameters(nc, config, inds) a_kodatie = ncread( nc, config, @@ -629,18 +636,17 @@ function initialize_transport_capacity_kodatie_params(nc, config, inds) return tc_parameters end -@get_units @with_kw struct TransportCapacityKodatieModel{T} <: - AbstractTransportCapacityModel - boundary_conditions::TransportCapacityBC{T} | "-" - parameters::TransportCapacityKodatieParameters{T} | "-" - variables::TransportCapacityModelVars{T} | "-" +@with_kw struct TransportCapacityKodatieModel{T} <: AbstractTransportCapacityModel{T} + boundary_conditions::TransportCapacityBC{T} + parameters::TransportCapacityKodatieParameters{T} + variables::TransportCapacityModelVariables{T} end -function initialize_transport_capacity_kodatie_model(nc, config, inds) +function TransportCapacityKodatieModel(nc, config, inds) n = length(inds) - vars = transport_capacity_model_vars(n) - params = initialize_transport_capacity_kodatie_params(nc, config, inds) - bc = transport_capacity_bc(n) + vars = TransportCapacityModelVariables(n) + params = TransportCapacityKodatieParameters(nc, config, inds) + bc = TransportCapacityBC(n) model = TransportCapacityKodatieModel(; boundary_conditions = bc, parameters = params, @@ -672,17 +678,17 @@ function update!(model::TransportCapacityKodatieModel, geometry::RiverGeometry, end # Yang parameters for transport capacity models -@get_units @with_kw struct TransportCapacityYangModel{T} <: AbstractTransportCapacityModel - boundary_conditions::TransportCapacityBC{T} | "-" - parameters::TransportCapacityRiverParameters{T} | "-" - variables::TransportCapacityModelVars{T} | "-" +@with_kw struct TransportCapacityYangModel{T} <: AbstractTransportCapacityModel{T} + boundary_conditions::TransportCapacityBC{T} + parameters::TransportCapacityRiverParameters{T} + variables::TransportCapacityModelVariables{T} end -function initialize_transport_capacity_yang_model(nc, config, inds) +function TransportCapacityYangModel(nc, config, inds) n = length(inds) - vars = transport_capacity_model_vars(n) - params = initialize_transport_capacity_river_params(nc, config, inds) - bc = transport_capacity_bc(n) + vars = TransportCapacityModelVariables(n) + params = TransportCapacityRiverParameters(nc, config, inds) + bc = TransportCapacityBC(n) model = TransportCapacityYangModel(; boundary_conditions = bc, parameters = params, @@ -712,18 +718,17 @@ function update!(model::TransportCapacityYangModel, geometry::RiverGeometry, ts) end # Molinas and Wu parameters for transport capacity models -@get_units @with_kw struct TransportCapacityMolinasModel{T} <: - AbstractTransportCapacityModel - boundary_conditions::TransportCapacityBC{T} | "-" - parameters::TransportCapacityRiverParameters{T} | "-" - variables::TransportCapacityModelVars{T} | "-" +@with_kw struct TransportCapacityMolinasModel{T} <: AbstractTransportCapacityModel{T} + boundary_conditions::TransportCapacityBC{T} + parameters::TransportCapacityRiverParameters{T} + variables::TransportCapacityModelVariables{T} end -function initialize_transport_capacity_molinas_model(nc, config, inds) +function TransportCapacityMolinasModel(nc, config, inds) n = length(inds) - vars = transport_capacity_model_vars(n) - params = initialize_transport_capacity_river_params(nc, config, inds) - bc = transport_capacity_bc(n) + vars = TransportCapacityModelVariables(n) + params = TransportCapacityRiverParameters(nc, config, inds) + bc = TransportCapacityBC(n) model = TransportCapacityMolinasModel(; boundary_conditions = bc, parameters = params, From f5191c2ce45a9daead8299b9487adbf8bd5b869b Mon Sep 17 00:00:00 2001 From: hboisgon Date: Wed, 20 Nov 2024 14:47:19 +0800 Subject: [PATCH 26/42] undo wrong changes from rebase --- docs/developments/julia_structs.qmd | 6 +- docs/user_guide/toml_file.qmd | 42 +- src/Wflow.jl | 1 - src/bmi.jl | 2 +- src/flow.jl | 37 +- src/sbm.jl | 2 +- src/sediment.jl | 896 ---------------------------- src/states.jl | 23 +- src/utils.jl | 25 +- test/reservoir_lake.jl | 9 - test/run_sbm.jl | 37 -- 11 files changed, 72 insertions(+), 1008 deletions(-) delete mode 100644 src/sediment.jl diff --git a/docs/developments/julia_structs.qmd b/docs/developments/julia_structs.qmd index dba35eec2..c4827cffb 100644 --- a/docs/developments/julia_structs.qmd +++ b/docs/developments/julia_structs.qmd @@ -21,9 +21,9 @@ 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 vertical concept. +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 vertical concept. Below an example how lateral concepts are mapped for the SBM model through a `NamedTuple`: diff --git a/docs/user_guide/toml_file.qmd b/docs/user_guide/toml_file.qmd index e57bc80aa..d67f46b7a 100644 --- a/docs/user_guide/toml_file.qmd +++ b/docs/user_guide/toml_file.qmd @@ -234,23 +234,25 @@ 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). If multiple layers 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](#output-csv-section) 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](#output-csv-section), 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](./fews.qmd). ```toml [netcdf] @@ -299,8 +301,10 @@ extract data for certain locations (e.g. `gauges`) or areas (e.g. `subcatchment` 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. If multiple layers are -desired, this can be specified in separate `[[csv.column]]` entries. +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. diff --git a/src/Wflow.jl b/src/Wflow.jl index 2fd0ba56b..767c1e9b0 100644 --- a/src/Wflow.jl +++ b/src/Wflow.jl @@ -155,7 +155,6 @@ include("soil/soil.jl") include("soil/soil_process.jl") include("sbm.jl") include("demand/water_demand.jl") -#include("sediment.jl") include("reservoir_lake.jl") include("sbm_model.jl") include("erosion/erosion_process.jl") diff --git a/src/bmi.jl b/src/bmi.jl index 42f833428..4063baae7 100644 --- a/src/bmi.jl +++ b/src/bmi.jl @@ -420,4 +420,4 @@ function get_start_unix_time(model::Model) end exchange(t::Vector) = true -exchange(t) = false +exchange(t) = false \ No newline at end of file diff --git a/src/flow.jl b/src/flow.jl index 067a78ba2..dab39db52 100644 --- a/src/flow.jl +++ b/src/flow.jl @@ -1161,9 +1161,11 @@ end function stable_timestep(sw::ShallowWaterLand{T})::T where {T} dt_min = T(Inf) @batch per = thread reduction = ((min, dt_min),) for i in 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) + @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 @@ -1612,13 +1614,14 @@ function initialize_floodplain_1d( end """ - set_river_inwater(model, ssf_toriver) + set_river_inwater!(model::Model, ssf_toriver) -Set `inwater` of the lateral river component for a model `ssf_toriver` is the subsurface +Set `inwater` of the lateral river component for a `Model`. `ssf_toriver` is the subsurface flow to the river. """ -function set_river_inwater(model, ssf_toriver) - @unpack lateral, vertical, network = model +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 @@ -1645,7 +1648,7 @@ function set_river_inwater(model, ssf_toriver) 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. """ @@ -1678,13 +1681,21 @@ end Set `inwater` of the lateral land component for the `SbmModel` type. """ -function set_land_inwater( +function set_land_inwater!( model::Model{N, L, V, R, W, T}, ) where {N, L, V, R, W, T <: SbmModel} - @unpack lateral, vertical, network = model - return lateral.land.inwater .= - (vertical.net_runoff .* network.land.xl .* network.land.yl .* 0.001) ./ - lateral.land.dt + (; 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 = + (net_runoff + vertical.allocation.variables.nonirri_returnflow) * + network.land.area * + 0.001 / lateral.land.dt + else + @. lateral.land.inwater = (net_runoff * network.land.area * 0.001) / lateral.land.dt + end + return nothing end # Computation of inflow from the lateral components `land` and `subsurface` to water bodies diff --git a/src/sbm.jl b/src/sbm.jl index 70101c57e..7293ec082 100644 --- a/src/sbm.jl +++ b/src/sbm.jl @@ -185,4 +185,4 @@ function update_total_water_storage!( total_storage[i] += (sub_surface + lateral) end return nothing -end +end \ No newline at end of file diff --git a/src/sediment.jl b/src/sediment.jl deleted file mode 100644 index cff02b4d3..000000000 --- a/src/sediment.jl +++ /dev/null @@ -1,896 +0,0 @@ -### River transport and processes ### -@get_units @grid_loc @with_kw struct RiverSediment{T} - # number of cells - n::Int | "-" | "none" - # Timestep [s] - dt::T | "s" - # River geometry (slope [-], length [m], width [m]) - sl::Vector{T} | "m" - dl::Vector{T} | "m" - width::Vector{T} | "m" - # Sediment mean diameter in the river bed [mm] - d50::Vector{T} | "mm" - # Particle mean diameter [mm] - dmclay::Vector{T} | "mm" - dmsilt::Vector{T} | "mm" - dmsand::Vector{T} | "mm" - dmsagg::Vector{T} | "mm" - dmlagg::Vector{T} | "mm" - dmgrav::Vector{T} | "mm" - # River bed and bank particle fraction composition [-] - fclayriv::Vector{T} | "-" - fsiltriv::Vector{T} | "-" - fsandriv::Vector{T} | "-" - fgravriv::Vector{T} | "-" - # Sediment mean diameter for Engelund and Hansen transport equation [mm] - d50engelund::Vector{T} | "mm" - # Parameters for Bagnold transport equation - cbagnold::Vector{T} | "-" - ebagnold::Vector{T} | "-" - # Parameters for Kodatie transport equation - ak::Vector{T} | "-" - bk::Vector{T} | "-" - ck::Vector{T} | "-" - dk::Vector{T} | "-" - # Critical bed and bank shear stress [N/m2] - TCrbank::Vector{T} | "N m-2" - TCrbed::Vector{T} | "N m-2" - # Bed and bank erodibilities [m3/N.s] - kdbank::Vector{T} | "m3 N-1 s-1" - kdbed::Vector{T} | "m3 N-1 s-1" - # Sediment density [kg/m3] - rhos::Vector{T} | "kg m-3" - # River water level [m] - h_riv::Vector{T} | "m" - # River discharge [m3/s] - q_riv::Vector{T} | "m3 s-1" - # Sediment input from land erosion [ton Δt⁻¹] - inlandclay::Vector{T} | "t dt-1" - inlandsilt::Vector{T} | "t dt-1" - inlandsand::Vector{T} | "t dt-1" - inlandsagg::Vector{T} | "t dt-1" - inlandlagg::Vector{T} | "t dt-1" - inlandsed::Vector{T} | "t dt-1" - # Sediment / particle left in the cell [ton] - sedload::Vector{T} | "t" - clayload::Vector{T} | "t" - siltload::Vector{T} | "t" - sandload::Vector{T} | "t" - saggload::Vector{T} | "t" - laggload::Vector{T} | "t" - gravload::Vector{T} | "t" - # Sediment / particle stored on the river bed after deposition [ton Δt⁻¹] - sedstore::Vector{T} | "t dt-1" - claystore::Vector{T} | "t dt-1" - siltstore::Vector{T} | "t dt-1" - sandstore::Vector{T} | "t dt-1" - saggstore::Vector{T} | "t dt-1" - laggstore::Vector{T} | "t dt-1" - gravstore::Vector{T} | "t dt-1" - # Sediment / particle flux [ton Δt⁻¹] - outsed::Vector{T} | "t dt-1" - outclay::Vector{T} | "t dt-1" - outsilt::Vector{T} | "t dt-1" - outsand::Vector{T} | "t dt-1" - outsagg::Vector{T} | "t dt-1" - outlagg::Vector{T} | "t dt-1" - outgrav::Vector{T} | "t dt-1" - # Total sediment concentrations (SSconc + Bedconc) [g/m3] - Sedconc::Vector{T} | "g m-3" - # Suspended load concentration [g/m3] - SSconc::Vector{T} | "g m-3" - # Bed load concentration [g/m3] - Bedconc::Vector{T} | "g m-3" - # River transport capacity - maxsed::Vector{T} | "t dt-1" - # Eroded sediment (total, bank and bed) - erodsed::Vector{T} | "t dt-1" - erodsedbank::Vector{T} | "t dt-1" - erodsedbed::Vector{T} | "t dt-1" - # Deposited sediment - depsed::Vector{T} | "t dt-1" - # Sediment in - insed::Vector{T} | "t dt-1" - # Reservoir and lakes - wbcover::Vector{T} | "-" - wblocs::Vector{T} | "-" - wbarea::Vector{T} | "m2" - wbtrap::Vector{T} | "-" - - # function RiverSediment{T}(args...) where {T} - # equal_size_vectors(args) - # return new(args...) - # end -end - -function initialize_riversed(nc, config, riverwidth, riverlength, inds_riv) - # Initialize river parameters - nriv = length(inds_riv) - # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] - tcmethodriv = get(config.model, "rivtransportmethod", "bagnold")::String - dt = Second(config.timestepsecs) - # Reservoir / lakes - do_reservoirs = get(config.model, "doreservoir", false)::Bool - do_lakes = get(config.model, "dolake", false)::Bool - wbcover = zeros(Float, nriv) - wblocs = zeros(Float, nriv) - wbarea = zeros(Float, nriv) - wbtrap = zeros(Float, nriv) - - if do_reservoirs - reslocs = ncread( - nc, - config, - "lateral.river.reslocs"; - optional = false, - sel = inds_riv, - type = Float, - fill = 0, - ) - rescoverage_2d = ncread( - nc, - config, - "lateral.river.resareas"; - optional = false, - sel = inds_riv, - type = Float, - fill = 0, - ) - resarea = ncread( - nc, - config, - "lateral.river.resarea"; - optional = false, - sel = inds_riv, - type = Float, - fill = 0.0, - ) - restrapefficiency = ncread( - nc, - config, - "lateral.river.restrapeff"; - optional = false, - sel = inds_riv, - type = Float, - defaults = 1.0, - fill = 0.0, - ) - - wbcover = wbcover .+ rescoverage_2d - wblocs = wblocs .+ reslocs - wbarea = wbarea .+ resarea - wbtrap = wbtrap .+ restrapefficiency - end - - if do_lakes - lakelocs = ncread( - nc, - config, - "lateral.river.lakelocs"; - optional = false, - sel = inds_riv, - type = Float, - fill = 0, - ) - lakecoverage_2d = ncread( - nc, - config, - "lateral.river.lakeareas"; - optional = false, - sel = inds_riv, - type = Float, - fill = 0, - ) - lakearea = ncread( - nc, - config, - "lateral.river.lakearea"; - optional = false, - sel = inds_riv, - type = Float, - fill = 0.0, - ) - - wbcover = wbcover .+ lakecoverage_2d - wblocs = wblocs .+ lakelocs - wbarea = wbarea .+ lakearea - end - - riverslope = ncread( - nc, - config, - "lateral.river.slope"; - optional = false, - sel = inds_riv, - type = Float, - ) - clamp!(riverslope, 0.00001, Inf) - rhos = ncread( - nc, - config, - "lateral.river.rhosed"; - sel = inds_riv, - defaults = 2650.0, - type = Float, - ) - dmclay = ncread( - nc, - config, - "lateral.river.dmclay"; - sel = inds_riv, - defaults = 2.0, - type = Float, - ) - dmsilt = ncread( - nc, - config, - "lateral.river.dmsilt"; - sel = inds_riv, - defaults = 10.0, - type = Float, - ) - dmsand = ncread( - nc, - config, - "lateral.river.dmsand"; - sel = inds_riv, - defaults = 200.0, - type = Float, - ) - dmsagg = ncread( - nc, - config, - "lateral.river.dmsagg"; - sel = inds_riv, - defaults = 30.0, - type = Float, - ) - dmlagg = ncread( - nc, - config, - "lateral.river.dmlagg"; - sel = inds_riv, - defaults = 500.0, - type = Float, - ) - dmgrav = ncread( - nc, - config, - "lateral.river.dmgrav"; - sel = inds_riv, - defaults = 2000.0, - type = Float, - ) - fclayriv = ncread( - nc, - config, - "lateral.river.fclayriv"; - optional = false, - sel = inds_riv, - type = Float, - ) - fsiltriv = ncread( - nc, - config, - "lateral.river.fsiltriv"; - optional = false, - sel = inds_riv, - type = Float, - ) - fsandriv = ncread( - nc, - config, - "lateral.river.fsandriv"; - optional = false, - sel = inds_riv, - type = Float, - ) - fgravriv = ncread( - nc, - config, - "lateral.river.fgravriv"; - optional = false, - sel = inds_riv, - type = Float, - ) - d50riv = ncread( - nc, - config, - "lateral.river.d50"; - optional = false, - sel = inds_riv, - type = Float, - ) - d50engelund = ncread( - nc, - config, - "lateral.river.d50engelund"; - optional = false, - sel = inds_riv, - type = Float, - ) - cbagnold = ncread( - nc, - config, - "lateral.river.cbagnold"; - optional = false, - sel = inds_riv, - type = Float, - ) - ebagnold = ncread( - nc, - config, - "lateral.river.ebagnold"; - optional = false, - sel = inds_riv, - type = Float, - ) - - # Initialisation of parameters for Kodatie transport capacity - ak = zeros(Float, nriv) - bk = zeros(Float, nriv) - ck = zeros(Float, nriv) - dk = zeros(Float, nriv) - if tcmethodriv == "kodatie" - for i in 1:nriv - if d50riv[i] <= 0.05 - ak[i] = 281.4 - bk[i] = 2.622 - ck[i] = 0.182 - dk[i] = 0.0 - elseif d50riv[i] <= 0.25 - ak[i] = 2829.6 - bk[i] = 3.646 - ck[i] = 0.406 - dk[i] = 0.412 - elseif d50riv[i] <= 2.0 - ak[i] = 2123.4 - bk[i] = 3.3 - ck[i] = 0.468 - dk[i] = 0.613 - else - ak[i] = 431884.8 - bk[i] = 1.0 - ck[i] = 1.0 - dk[i] = 2.0 - end - end - end - # Initialisation of parameters for river erosion - # Bed and Bank from Shields diagram, Da Silva & Yalin (2017) - E_ = (2.65 - 1) * 9.81 - E = (E_ .* (d50riv .* 1e-3) .^ 3 ./ 1e-12) .^ 0.33 - TCrbed = @. Float( - E_ * - d50riv * - (0.13 * E^(-0.392) * exp(-0.015 * E^2) + 0.045 * (1 - exp(-0.068 * E))), - ) - TCrbank = TCrbed - # kd from Hanson & Simon 2001 - kdbank = @. Float(0.2 * TCrbank^(-0.5) * 1e-6) - kdbed = @. Float(0.2 * TCrbed^(-0.5) * 1e-6) - - rs = RiverSediment(; - n = nriv, - dt = Float(dt.value), - # Parameters - sl = riverslope, - dl = riverlength, - width = riverwidth, - dmclay = dmclay, - dmsilt = dmsilt, - dmsand = dmsand, - dmsagg = dmsagg, - dmlagg = dmlagg, - dmgrav = dmgrav, - fclayriv = fclayriv, - fsiltriv = fsiltriv, - fsandriv = fsandriv, - fgravriv = fgravriv, - d50 = d50riv, - d50engelund = d50engelund, - cbagnold = cbagnold, - ebagnold = ebagnold, - ak = ak, - bk = bk, - ck = ck, - dk = dk, - kdbank = kdbank, - kdbed = kdbed, - TCrbank = TCrbank, - TCrbed = TCrbed, - rhos = rhos, - # Forcing - h_riv = fill(mv, nriv), - q_riv = fill(mv, nriv), - # Input from land - inlandclay = zeros(Float, nriv), - inlandsilt = zeros(Float, nriv), - inlandsand = zeros(Float, nriv), - inlandsagg = zeros(Float, nriv), - inlandlagg = zeros(Float, nriv), - inlandsed = zeros(Float, nriv), - # States - sedload = zeros(Float, nriv), - clayload = zeros(Float, nriv), - siltload = zeros(Float, nriv), - sandload = zeros(Float, nriv), - saggload = zeros(Float, nriv), - laggload = zeros(Float, nriv), - gravload = zeros(Float, nriv), - sedstore = zeros(Float, nriv), - claystore = zeros(Float, nriv), - siltstore = zeros(Float, nriv), - sandstore = zeros(Float, nriv), - saggstore = zeros(Float, nriv), - laggstore = zeros(Float, nriv), - gravstore = zeros(Float, nriv), - outsed = zeros(Float, nriv), - outclay = zeros(Float, nriv), - outsilt = zeros(Float, nriv), - outsand = zeros(Float, nriv), - outsagg = zeros(Float, nriv), - outlagg = zeros(Float, nriv), - outgrav = zeros(Float, nriv), - # Outputs - Sedconc = zeros(Float, nriv), - SSconc = zeros(Float, nriv), - Bedconc = zeros(Float, nriv), - maxsed = zeros(Float, nriv), - erodsed = zeros(Float, nriv), - erodsedbank = zeros(Float, nriv), - erodsedbed = zeros(Float, nriv), - depsed = zeros(Float, nriv), - insed = zeros(Float, nriv), - # Reservoir / lake - wbcover = wbcover, - wblocs = wblocs, - wbarea = wbarea, - wbtrap = wbtrap, - ) - - return rs -end - -function update!(rs::RiverSediment, network, config) - (; graph, order) = network - tcmethod = get(config.model, "rivtransportmethod", "bagnold")::String - - # River sediment loads are separated into different particle class. - # Clay, silt and sand can both come from land, resuspension or river channel erosion. - # Small and large aggregates only come from land erosion or resuspension. - # Gravel only comes from resuspension or river channel erosion. - - for v in order - ### Sediment input in the cell (left from previous timestep + from land + from upstream outflux) ### - upstream_nodes = inneighbors(graph, v) - - inrivclay = 0.0 - inrivsilt = 0.0 - inrivsand = 0.0 - inrivsagg = 0.0 - inrivlagg = 0.0 - inrivgrav = 0.0 - if !isempty(upstream_nodes) - for i in upstream_nodes - if rs.outclay[i] >= 0.0 # avoid NaN from upstream non-river cells - inrivclay += rs.outclay[i] - inrivsilt += rs.outsilt[i] - inrivsand += rs.outsand[i] - inrivsagg += rs.outsagg[i] - inrivlagg += rs.outlagg[i] - inrivgrav += rs.outgrav[i] - end - end - end - - inclay = rs.clayload[v] + rs.inlandclay[v] + inrivclay - insilt = rs.siltload[v] + rs.inlandsilt[v] + inrivsilt - insand = rs.sandload[v] + rs.inlandsand[v] + inrivsand - insagg = rs.saggload[v] + rs.inlandsagg[v] + inrivsagg - inlagg = rs.laggload[v] + rs.inlandlagg[v] + inrivlagg - ingrav = rs.gravload[v] + inrivgrav - - insed = inclay + insilt + insand + insagg + inlagg + ingrav - rs.insed[v] = insed - rs.inlandsed[v] = - rs.inlandclay[v] + - rs.inlandsilt[v] + - rs.inlandsand[v] + - rs.inlandsagg[v] + - rs.inlandlagg[v] - - ### Transport capacity of the flow ### - # Hydraulic radius of the river [m] (rectangular channel) - hydrad = rs.h_riv[v] * rs.width[v] / (rs.width[v] + 2 * rs.h_riv[v]) - - # Engelund and Hansen transport formula - if tcmethod == "engelund" - vmean = - ifelse(rs.h_riv[v] > 0.0, rs.q_riv[v] / (rs.width[v] * rs.h_riv[v]), 0.0) - vshear = (9.81 * hydrad * rs.sl[v])^0.5 - # Concentration by weight - cw = ifelse( - hydrad > 0.0, - ( - rs.rhos[v] / 1000 * 0.05 * vmean * vshear^3 / - ((rs.rhos[v] / 1000 - 1)^2 * 9.81^2 * rs.d50engelund[v] * hydrad) - ), - 0.0, - ) - cw = min(1.0, cw) - # Transport capacity [tons/m3] - maxsed = max(cw / (cw + (1 - cw) * rs.rhos[v] / 1000) * rs.rhos[v] / 1000, 0.0) - elseif tcmethod == "bagnold" - maxsed = - rs.cbagnold[v] * (rs.q_riv[v] / (rs.h_riv[v] * rs.width[v]))^rs.ebagnold[v] - elseif tcmethod == "kodatie" - vmean = - ifelse(rs.h_riv[v] > 0.0, rs.q_riv[v] / (rs.width[v] * rs.h_riv[v]), 0.0) - maxsed = rs.ak[v] * vmean^rs.bk[v] * rs.h_riv[v]^rs.ck[v] * rs.sl[v]^rs.dk[v] - # Transport capacity [tons/m3] - maxsed = - ifelse(rs.q_riv[v] > 0.0, maxsed * rs.width[v] / (rs.q_riv[v] * rs.dt), 0.0) - elseif tcmethod == "yang" - ws = 411 * rs.d50[v]^2 / 3600 - vshear = (9.81 * hydrad * rs.sl[v])^0.5 - var1 = vshear * rs.d50[v] / 1000 / (1.16 * 1e-6) - var2 = ws * rs.d50[v] / 1000 / (1.16 * 1e-6) - vcr = min( - 0.0, - ifelse(var1 >= 70.0, 2.05 * ws, ws * (2.5 / (log10(var1) - 0.06) + 0.66)), - ) - # Sand equation - if (rs.width[v] * rs.h_riv[v]) >= vcr && rs.d50[v] < 2.0 - logcppm = ( - 5.435 - 0.286 * log10(var2) - 0.457 * log10(vshear / ws) + 1.799 - - 0.409 * log10(var2) - - 0.314 * - log10(vshear / ws) * - log10((rs.q_riv[v] / (rs.width[v] * rs.h_riv[v]) - vcr) * rs.sl[v] / ws) - ) - # Gravel equation - elseif (rs.width[v] * rs.h_riv[v]) >= vcr && rs.d50[v] < 2.0 - logcppm = ( - 6.681 - 0.633 * log10(var2) - 4.816 * log10(vshear / ws) + 2.784 - - 0.305 * log10(var2) - - 0.282 * - log10(vshear / ws) * - log10((rs.q_riv[v] / (rs.width[v] * rs.h_riv[v]) - vcr) * rs.sl[v] / ws) - ) - else - logcppm = 0.0 - end - # Sediment concentration by weight - cw = 10^logcppm * 1e-6 - # Transport capacity [ton/m3] - maxsed = max(cw / (cw + (1 - cw) * rs.rhos[v] / 1000) * rs.rhos[v] / 1000, 0.0) - elseif tcmethod == "molinas" - ws = 411 * rs.d50[v]^2 / 3600 - vmean = - ifelse(rs.h_riv[v] > 0.0, rs.q_riv[v] / (rs.width[v] * rs.h_riv[v]), 0.0) - if rs.h_riv[v] > 0.0 - psi = ( - vmean^3 / ( - (rs.rhos[v] / 1000 - 1) * - 9.81 * - rs.h_riv[v] * - ws * - log10(1000 * rs.h_riv[v] / rs.d50[v])^2 - ) - ) - else - psi = 0.0 - end - # Concentration by weight - cw = 1430 * (0.86 + psi^0.5) * psi^1.5 / (0.016 + psi) * 1e-6 - # Transport capacity [ton/m3] - maxsed = max(cw / (cw + (1 - cw) * rs.rhos[v] / 1000) * rs.rhos[v] / 1000, 0.0) - end - - # 1285 g/L: boundary between streamflow and debris flow (Costa, 1988) - maxsed = min(maxsed, 1.285) - # Transport capacity [ton] - maxsed = maxsed * (rs.h_riv[v] * rs.width[v] * rs.dl[v] + rs.q_riv[v] * rs.dt) - rs.maxsed[v] = maxsed - - ### River erosion ### - # Erosion only if the load is below the transport capacity of the flow. - sedex = max(maxsed - insed, 0.0) - # No erosion in lake and reservoir cells - if rs.wbcover[v] > 0.0 - sedex = 0.0 - end - # Bed and bank are eroded only if the previously deposited material is not enough - rs.sedstore[v] = - rs.claystore[v] + - rs.siltstore[v] + - rs.sandstore[v] + - rs.saggstore[v] + - rs.laggstore[v] + - rs.gravstore[v] - if sedex > 0.0 && sedex > rs.sedstore[v] - # Effective sediment needed fom river bed and bank erosion [ton] - effsedex = sedex - rs.sedstore[v] - - # Repartition of the effective shear stress between the bank and the bed from Knight et al. 1984 [%] - SFbank = ifelse( - rs.h_riv[v] > 0.0, - exp(-3.23 * log10(rs.width[v] / rs.h_riv[v] + 3) + 6.146), - 0.0, - ) - # Effective shear stress on river bed and banks [N/m2] - TEffbank = ifelse( - rs.h_riv[v] > 0.0, - 1000 * 9.81 * hydrad * rs.sl[v] * SFbank / 100 * - (1 + rs.width[v] / (2 * rs.h_riv[v])), - 0.0, - ) - TEffbed = - 1000 * - 9.81 * - hydrad * - rs.sl[v] * - (1 - SFbank / 100) * - (1 + 2 * rs.h_riv[v] / rs.width[v]) - # Potential erosion rates of the bed and bank [t/cell/timestep] - #(assuming only one bank is eroding) - Tex = max(TEffbank - rs.TCrbank[v], 0.0) - # 1.4 is bank default bulk density - ERbank = max(0.0, rs.kdbank[v] * Tex * rs.dl[v] * rs.h_riv[v] * 1.4 * rs.dt) - # 1.5 is bed default bulk density - ERbed = max( - 0.0, - rs.kdbed[v] * - (TEffbed - rs.TCrbed[v]) * - rs.dl[v] * - rs.width[v] * - 1.5 * - rs.dt, - ) - # Relative potential erosion rates of the bed and the bank [-] - RTEbank = ifelse(ERbank + ERbed > 0.0, ERbank / (ERbank + ERbed), 0.0) - RTEbed = 1.0 - RTEbank - - # Bank erosion (difference between effective and potential erosion) [ton] - sedbank = ifelse(effsedex * RTEbank <= ERbank, effsedex * RTEbank, ERbank) - claybank = rs.fclayriv[v] * sedbank - siltbank = rs.fsiltriv[v] * sedbank - sandbank = rs.fsandriv[v] * sedbank - gravbank = rs.fgravriv[v] * sedbank - - # Bed erosion [ton] - sedbed = ifelse(effsedex * RTEbed <= ERbed, effsedex * RTEbed, ERbed) - claybed = rs.fclayriv[v] * sedbed - siltbed = rs.fsiltriv[v] * sedbed - sandbed = rs.fsandriv[v] * sedbed - gravbed = rs.fgravriv[v] * sedbed - else - sedbank = 0.0 - claybank = 0.0 - siltbank = 0.0 - sandbank = 0.0 - gravbank = 0.0 - - sedbed = 0.0 - claybed = 0.0 - siltbed = 0.0 - sandbed = 0.0 - gravbed = 0.0 - end - - # Erosion/degradation of the previously deposited sediment (from clay to gravel) [ton] - if sedex > 0.0 - degstoreclay = ifelse(rs.claystore[v] >= sedex, sedex, rs.claystore[v]) - rs.claystore[v] = rs.claystore[v] - degstoreclay - #Update amount of sediment that need to be degraded - sedex = sedex - degstoreclay - degstoresilt = ifelse(rs.siltstore[v] >= sedex, sedex, rs.siltstore[v]) - rs.siltstore[v] = rs.siltstore[v] - degstoresilt - sedex = max(0.0, sedex - degstoresilt) - degstoresagg = ifelse(rs.saggstore[v] >= sedex, sedex, rs.saggstore[v]) - rs.saggstore[v] = rs.saggstore[v] - degstoresagg - sedex = max(0.0, sedex - degstoresagg) - degstoresand = ifelse(rs.sandstore[v] >= sedex, sedex, rs.sandstore[v]) - rs.sandstore[v] = rs.sandstore[v] - degstoresand - sedex = max(0.0, sedex - degstoresand) - degstorelagg = ifelse(rs.laggstore[v] >= sedex, sedex, rs.laggstore[v]) - rs.laggstore[v] = rs.laggstore[v] - degstorelagg - sedex = max(0.0, sedex - degstorelagg) - degstoregrav = ifelse(rs.gravstore[v] >= sedex, sedex, rs.gravstore[v]) - rs.gravstore[v] = rs.gravstore[v] - degstoregrav - sedex = max(0.0, sedex - degstoregrav) - degstoresed = - degstoreclay + - degstoresilt + - degstoresagg + - degstoresand + - degstorelagg + - degstoregrav - else - degstoreclay = 0.0 - degstoresilt = 0.0 - degstoresagg = 0.0 - degstoresand = 0.0 - degstorelagg = 0.0 - degstoregrav = 0.0 - degstoresed = 0.0 - end - - # Sum all erosion sources per particle class - erodsed = sedbank + sedbed + degstoresed - erodclay = claybank + claybed + degstoreclay - erodsilt = siltbank + siltbed + degstoresilt - erodsand = sandbank + sandbed + degstoresand - erodsagg = degstoresagg - erodlagg = degstorelagg - erodgrav = gravbank + gravbed + degstoregrav - - rs.erodsed[v] = erodsed - rs.erodsedbank[v] = sedbank - rs.erodsedbed[v] = sedbed - - ### Deposition / settling ### - # Fractions of deposited particles in river cells from the Einstein formula [-] - # Particle fall velocity [m/s] from Stokes - xs = ifelse(rs.q_riv[v] > 0.0, 1.055 * rs.dl[v] / (rs.q_riv[v] / rs.width[v]), 0.0) - xclay = min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (rs.dmclay[v] / 1000)^2 / 3600))) - xsilt = min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (rs.dmsilt[v] / 1000)^2 / 3600))) - xsand = min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (rs.dmsand[v] / 1000)^2 / 3600))) - xsagg = min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (rs.dmsagg[v] / 1000)^2 / 3600))) - xlagg = min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (rs.dmlagg[v] / 1000)^2 / 3600))) - xgrav = min(1.0, 1.0 - 1.0 / exp(xs * (411.0 * (rs.dmgrav[v] / 1000)^2 / 3600))) - - # Sediment deposited in the channel [ton] - # From natural settling with Einstein formula (density controlled). - settclay = xclay * (inclay + erodclay) - settsilt = xsilt * (insilt + erodsilt) - settsand = xsand * (insand + erodsand) - settsagg = xsagg * (insagg + erodsagg) - settlagg = xlagg * (inlagg + erodlagg) - settgrav = xgrav * (ingrav + erodgrav) - - # Sediment deposited in the channel (from gravel to clay) [ton] - # From transport capacity exceedance (insed > maxsed) - insedex = max(insed - maxsed, 0.0) - if insedex > 0.0 - depgrav = ifelse(ingrav >= insedex, insedex, ingrav) - insedex = max(insedex - depgrav, 0.0) - deplagg = ifelse(inlagg >= insedex, insedex, inlagg) - insedex = max(insedex - deplagg, 0.0) - depsand = ifelse(insand >= insedex, insedex, insand) - insedex = max(insedex - depsand, 0.0) - depsagg = ifelse(insagg >= insedex, insedex, insagg) - insedex = max(insedex - depsagg, 0.0) - depsilt = ifelse(insilt >= insedex, insedex, insilt) - insedex = max(insedex - depsilt, 0.0) - depclay = ifelse(inclay >= insedex, insedex, inclay) - insedex = max(insedex - depclay, 0.0) - else - depclay = settclay - depsilt = settsilt - depsand = settsand - depsagg = settsagg - deplagg = settlagg - depgrav = settgrav - end - - # No deposition in regular lake and reservoir cells, only at the outlet - # Deposition in lake/reservoir from Camp 1945 - # Extra trapping of large particles for dams - if rs.wbcover[v] > 0.0 && rs.wblocs[v] > 0.0 - # Compute deposition - vcres = rs.q_riv[v] / rs.wbarea[v] - DCres = 411 / 3600 / vcres - depclay = (inclay + erodclay) * min(1.0, (DCres * (rs.dmclay[v] / 1000)^2)) - depsilt = (insilt + erodsilt) * min(1.0, (DCres * (rs.dmsilt[v] / 1000)^2)) - depsand = (insand + erodsand) * min(1.0, (DCres * (rs.dmsand[v] / 1000)^2)) - depsagg = (insagg + erodsagg) * min(1.0, (DCres * (rs.dmsagg[v] / 1000)^2)) - deplagg = (inlagg + erodlagg) * min(1.0, (DCres * (rs.dmlagg[v] / 1000)^2)) - depgrav = (ingrav + erodgrav) * min(1.0, (DCres * (rs.dmgrav[v] / 1000)^2)) - # Trapping of large particles - # Use the rouse number for sagg (suspension or bedload) - depsand = max(depsand, rs.wbtrap[v] * (insand + erodsand)) - deplagg = max(deplagg, rs.wbtrap[v] * (inlagg + erodlagg)) - depgrav = max(depgrav, rs.wbtrap[v] * (ingrav + erodgrav)) - # threshold diameter between suspended load and mixed load using Rouse number - dsuspf = - 1e3 * (1.2 * 3600 * 0.41 / 411 * (9.81 * rs.h_riv[v] * rs.sl[v])^0.5)^0.5 - depsagg = ifelse( - rs.dmsagg[v] > dsuspf, - depsagg, - max(depsagg, rs.wbtrap[v] * (insagg + erodsagg)), - ) - elseif rs.wbcover[v] > 0.0 - depsed = 0.0 - depclay = 0.0 - depsilt = 0.0 - depsand = 0.0 - depsagg = 0.0 - deplagg = 0.0 - depgrav = 0.0 - end - - depsed = depclay + depsilt + depsand + depsagg + deplagg + depgrav - rs.depsed[v] = depsed - - # Update the river deposited sediment storage - rs.sedstore[v] = rs.sedstore[v] + depsed - rs.claystore[v] = rs.claystore[v] + depclay - rs.siltstore[v] = rs.siltstore[v] + depsilt - rs.sandstore[v] = rs.sandstore[v] + depsand - rs.saggstore[v] = rs.saggstore[v] + depsagg - rs.laggstore[v] = rs.laggstore[v] + deplagg - rs.gravstore[v] = rs.gravstore[v] + depgrav - - ### Ouput loads ### - # Sediment transported out of the cell during the timestep [ton] - # 0 in case all sediment are deposited in the cell - # Reduce the fraction so that there is still some sediment staying in the river cell - fwaterout = min(rs.q_riv[v] * rs.dt / (rs.h_riv[v] * rs.width[v] * rs.dl[v]), 1.0) - rs.outsed[v] = fwaterout * (insed + erodsed - depsed) - rs.outclay[v] = fwaterout * (inclay + erodclay - depclay) - rs.outsilt[v] = fwaterout * (insilt + erodsilt - depsilt) - rs.outsand[v] = fwaterout * (insand + erodsand - depsand) - rs.outsagg[v] = fwaterout * (insagg + erodsagg - depsagg) - rs.outlagg[v] = fwaterout * (inlagg + erodlagg - deplagg) - rs.outgrav[v] = fwaterout * (ingrav + erodgrav - depgrav) - - ### Mass balance ### - # Sediment left in the cell [ton] - rs.sedload[v] = insed + erodsed - depsed - rs.outsed[v] - rs.clayload[v] = inclay + erodclay - depclay - rs.outclay[v] - rs.siltload[v] = insilt + erodsilt - depsilt - rs.outsilt[v] - rs.sandload[v] = insand + erodsand - depsand - rs.outsand[v] - rs.saggload[v] = insagg + erodsagg - depsagg - rs.outsagg[v] - rs.gravload[v] = ingrav + erodgrav - depgrav - rs.outgrav[v] - - ### Concentrations and suspended sediments ### - # Conversion from load [ton] to concentration for rivers [mg/L] - toconc = ifelse(rs.q_riv[v] > 0.0, 1e6 / (rs.q_riv[v] * rs.dt), 0.0) - rs.Sedconc[v] = rs.outsed[v] * toconc - - # Differentiation of bed and suspended load using Rouse number for suspension - # threshold diameter between bed load and mixed load using Rouse number - dbedf = 1e3 * (2.5 * 3600 * 0.41 / 411 * (9.81 * rs.h_riv[v] * rs.sl[v])^0.5)^0.5 - # threshold diameter between suspended load and mixed load using Rouse number - dsuspf = 1e3 * (1.2 * 3600 * 0.41 / 411 * (9.81 * rs.h_riv[v] * rs.sl[v])^0.5)^0.5 - # Rouse with diameter - SSclay = ifelse( - rs.dmclay[v] <= dsuspf, - rs.outclay[v], - ifelse(rs.dmclay[v] <= dbedf, rs.outclay[v] / 2, 0.0), - ) - SSsilt = ifelse( - rs.dmsilt[v] <= dsuspf, - rs.outsilt[v], - ifelse(rs.dmsilt[v] <= dbedf, rs.outsilt[v] / 2, 0.0), - ) - SSsagg = ifelse( - rs.dmsagg[v] <= dsuspf, - rs.outsagg[v], - ifelse(rs.dmsagg[v] <= dbedf, rs.outsagg[v] / 2, 0.0), - ) - SSsand = ifelse( - rs.dmsand[v] <= dsuspf, - rs.outsand[v], - ifelse(rs.dmsand[v] <= dbedf, rs.outsand[v] / 2, 0.0), - ) - SSlagg = ifelse( - rs.dmlagg[v] <= dsuspf, - rs.outlagg[v], - ifelse(rs.dmlagg[v] <= dbedf, rs.outlagg[v] / 2, 0.0), - ) - SSgrav = ifelse( - rs.dmgrav[v] <= dsuspf, - rs.outgrav[v], - ifelse(rs.dmgrav[v] <= dbedf, rs.outgrav[v] / 2, 0.0), - ) - - SS = SSclay + SSsilt + SSsagg + SSsand + SSlagg + SSgrav - Bed = rs.outsed[v] - SS - - rs.SSconc[v] = SS * toconc - rs.Bedconc[v] = Bed * toconc - end -end diff --git a/src/states.jl b/src/states.jl index c77673735..619018ffc 100644 --- a/src/states.jl +++ b/src/states.jl @@ -6,28 +6,7 @@ required states (internal names as symbols). """ 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, - ) - else - vertical_states = (:satwaterdepth, :ustorelayerdepth, :canopystorage) - end + states = (:snow_storage, :snow_water) elseif model_type == "sediment" states = () else diff --git a/src/utils.jl b/src/utils.jl index 80ddfbb85..40c514ec1 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -863,12 +863,25 @@ function initialize_lateralssf!(model::LateralSSF, kh_profile::KhExponentialCons 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] +""" + 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] diff --git a/test/reservoir_lake.jl b/test/reservoir_lake.jl index 5e61f99d8..261021927 100644 --- a/test/reservoir_lake.jl +++ b/test/reservoir_lake.jl @@ -62,15 +62,6 @@ lake = Wflow.Lake{Float64}(; @test lake.actevap[1] ≈ 3.2 end -@testset "Exchange and grid location lake" begin - @test Wflow.exchange(lake, :dt) == 0 - @test Wflow.exchange(lake, :storage) == 1 - @test Wflow.exchange(lake, :outflow) == 1 - @test Wflow.grid_location(lake, :dt) == "none" - @test Wflow.grid_location(lake, :storage) == "node" - @test Wflow.grid_location(lake, :outflow) == "node" -end - datadir = joinpath(@__DIR__, "data") sh = [ Wflow.read_sh_csv(joinpath(datadir, "input", "lake_sh_1.csv")), diff --git a/test/run_sbm.jl b/test/run_sbm.jl index c00583d8b..2d03bed96 100644 --- a/test/run_sbm.jl +++ b/test/run_sbm.jl @@ -112,7 +112,6 @@ end @test ssf[network.land.order[1]] ≈ 718.2802566393531f0 @test ssf[network.land.order[end - 100]] ≈ 2337.771227118579f0 @test ssf[network.land.order[end]] ≈ 288.19428729403984f0 - @test sum(ssf) ≈ 6.370399148012509f7 end @testset "overland flow" begin @@ -140,42 +139,6 @@ end @test res.evaporation[1] ≈ 0.5400000810623169f0 end -@testset "Exchange and grid location SBM" begin - sbm = model.vertical - @test Wflow.exchange(sbm, :n) == 0 - @test Wflow.exchange(sbm, :rootingdepth) == 1 - @test Wflow.grid_location(sbm, :dt) == "none" - @test Wflow.grid_location(sbm, :rootingdepth) == "node" - @test Wflow.grid_location(sbm, :runoff) == "node" -end - -@testset "Exchange and grid location subsurface flow" begin - ssf = model.lateral.subsurface - @test Wflow.exchange(ssf, :dt) == 0 - @test Wflow.exchange(ssf, :ssf) == 1 - @test Wflow.exchange(ssf, :slope) == 1 - @test Wflow.grid_location(ssf, :dt) == "none" - @test Wflow.grid_location(ssf, :ssf) == "node" - @test Wflow.grid_location(ssf, :slope) == "node" -end - -@testset "Exchange and grid location kinematic wave" begin - land = model.lateral.land - @test Wflow.exchange(land, :dt) == 0 - @test Wflow.exchange(land, :q_av) == 1 - @test Wflow.exchange(land, :inwater) == 1 - @test Wflow.grid_location(land, :dt) == "none" - @test Wflow.grid_location(land, :q_av) == "node" - @test Wflow.grid_location(land, :inwater) == "node" - river = model.lateral.river - @test Wflow.exchange(river, :dt) == 0 - @test Wflow.exchange(river, :q_av) == 1 - @test Wflow.exchange(river, :inwater) == 1 - @test Wflow.grid_location(river, :dt) == "none" - @test Wflow.grid_location(river, :q_av) == "node" - @test Wflow.grid_location(river, :inwater) == "node" -end - # set these variables for comparison in "changed dynamic parameters" precip = copy(model.vertical.atmospheric_forcing.precipitation) evap = copy(model.vertical.atmospheric_forcing.potential_evaporation) From c7fa59b3f554e1972740e4d6ca1731a65aea1414 Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Tue, 26 Nov 2024 08:14:15 +0100 Subject: [PATCH 27/42] Update BMI Sediment Add `@grid_loc` and remove units of non-vector fields. --- src/erosion.jl | 12 ++++----- src/forcing.jl | 4 +-- src/geometry.jl | 4 +-- src/sediment_flux.jl | 26 +++++++++---------- src/sediment_transport/land_to_river.jl | 15 +++++------ .../overland_flow_transport.jl | 8 +++--- src/sediment_transport/river_transport.jl | 12 ++++----- src/sediment_transport/transport_capacity.jl | 18 ++++++------- 8 files changed, 49 insertions(+), 50 deletions(-) diff --git a/src/erosion.jl b/src/erosion.jl index b91db0c83..e61d95559 100644 --- a/src/erosion.jl +++ b/src/erosion.jl @@ -1,9 +1,9 @@ -@get_units @with_kw struct SoilLoss{RE, OFE, SE, T} - hydrometeo_forcing::HydrometeoForcing | "-" - geometry::LandGeometry | "-" - rainfall_erosion::RE | "-" - overland_flow_erosion::OFE | "-" - soil_erosion::SE | "-" +@with_kw struct SoilLoss{RE, OFE, SE, T} + hydrometeo_forcing::HydrometeoForcing + geometry::LandGeometry + rainfall_erosion::RE + overland_flow_erosion::OFE + soil_erosion::SE end function initialize_soil_loss(nc, config, inds) diff --git a/src/forcing.jl b/src/forcing.jl index d411cdd5a..dc98d27ab 100644 --- a/src/forcing.jl +++ b/src/forcing.jl @@ -1,5 +1,5 @@ "Struct to store atmospheric forcing variables" -@get_units @with_kw struct AtmosphericForcing{T} +@get_units @grid_loc @with_kw struct AtmosphericForcing{T} # Precipitation [mm Δt⁻¹] precipitation::Vector{T} # Potential reference evapotranspiration [mm Δt⁻¹] @@ -18,7 +18,7 @@ function AtmosphericForcing( return AtmosphericForcing{T}(; precipitation, potential_evaporation, temperature) end -@get_units @with_kw struct HydrometeoForcing{T} +@get_units @grid_loc @with_kw struct HydrometeoForcing{T} # Precipitation [mm Δt⁻¹] precipitation::Vector{T} # Overland flow depth [m] diff --git a/src/geometry.jl b/src/geometry.jl index 32614fad2..476731a7e 100644 --- a/src/geometry.jl +++ b/src/geometry.jl @@ -1,5 +1,5 @@ -@get_units @with_kw struct LandGeometry{T} +@get_units @grid_loc @with_kw struct LandGeometry{T} # cell area [m^2] area::Vector{T} | "m^2" # drain width [m] @@ -35,7 +35,7 @@ function LandGeometry(nc, config, inds) return land_geometry end -@get_units @with_kw struct RiverGeometry{T} +@get_units @grid_loc @with_kw struct RiverGeometry{T} # drain width [m] width::Vector{T} | "m" # drain length diff --git a/src/sediment_flux.jl b/src/sediment_flux.jl index 0f4ceb647..d62321dd9 100644 --- a/src/sediment_flux.jl +++ b/src/sediment_flux.jl @@ -1,10 +1,10 @@ ### Overland flow ### -@get_units @with_kw struct OverlandFlowSediment{TT, SF, TR, T} - hydrometeo_forcing::HydrometeoForcing | "-" - geometry::LandGeometry | "-" - transport_capacity::TT | "-" - sediment_flux::SF | "-" - to_river::TR | "-" +@get_units @grid_loc @with_kw struct OverlandFlowSediment{TT, SF, TR, T} + hydrometeo_forcing::HydrometeoForcing + geometry::LandGeometry + transport_capacity::TT + sediment_flux::SF + to_river::TR waterbodies::Vector{Bool} | "-" rivers::Vector{Bool} | "-" end @@ -78,13 +78,13 @@ function update!(model::OverlandFlowSediment, erosion_model::SoilErosionModel, n end ### River ### -@get_units @with_kw struct RiverSediment{TTR, ER, SFR, CR, T} - hydrometeo_forcing::HydrometeoForcing | "-" - geometry::RiverGeometry | "-" - transport_capacity::TTR | "-" - potential_erosion::ER | "-" - sediment_flux::SFR | "-" - concentrations::CR | "-" +@get_units @grid_loc @with_kw struct RiverSediment{TTR, ER, SFR, CR, T} + hydrometeo_forcing::HydrometeoForcing + geometry::RiverGeometry + transport_capacity::TTR + potential_erosion::ER + sediment_flux::SFR + concentrations::CR waterbodies::Vector{Bool} | "-" end diff --git a/src/sediment_transport/land_to_river.jl b/src/sediment_transport/land_to_river.jl index 8f196f9d5..8973a01b6 100644 --- a/src/sediment_transport/land_to_river.jl +++ b/src/sediment_transport/land_to_river.jl @@ -1,7 +1,7 @@ abstract type AbstractSedimentToRiverModel{T} end ## Total sediment transport in overland flow structs and functions -@get_units @with_kw struct SedimentToRiverVariables{T} +@get_units @grid_loc @with_kw struct SedimentToRiverVariables{T} # Total sediment reaching the river amount::Vector{T} | "t dt-1" end @@ -10,7 +10,7 @@ function SedimentToRiverVariables(n; amount::Vector{T} = fill(mv, n)) where {T} return SedimentToRiverVariables{T}(; amount = amount) end -@get_units @with_kw struct SedimentToRiverBC{T} +@get_units @grid_loc @with_kw struct SedimentToRiverBC{T} # Deposited material deposition::Vector{T} | "t dt-1" end @@ -49,7 +49,7 @@ function update!(model::SedimentToRiverModel, rivers) end ## Different particles reaching the river structs and functions -@get_units @with_kw struct SedimentToRiverDifferentiationVariables{T} +@get_units @grid_loc @with_kw struct SedimentToRiverDifferentiationVariables{T} # Total sediment flux amount::Vector{T} | "t dt-1" # Clay flux @@ -83,7 +83,7 @@ function SedimentToRiverDifferentiationVariables( ) end -@get_units @with_kw struct SedimentToRiverDifferentiationBC{T} +@get_units @grid_loc @with_kw struct SedimentToRiverDifferentiationBC{T} # Deposited clay deposition_clay::Vector{T} | "t dt-1" # Deposited silt @@ -113,10 +113,9 @@ function SedimentToRiverDifferentiationBC( ) end -@get_units @with_kw struct SedimentToRiverDifferentiationModel{T} <: - AbstractSedimentToRiverModel{T} - boundary_conditions::SedimentToRiverDifferentiationBC{T} | "-" - variables::SedimentToRiverDifferentiationVariables{T} | "-" +@with_kw struct SedimentToRiverDifferentiationModel{T} <: AbstractSedimentToRiverModel{T} + boundary_conditions::SedimentToRiverDifferentiationBC{T} + variables::SedimentToRiverDifferentiationVariables{T} end function SedimentToRiverDifferentiationModel(inds) diff --git a/src/sediment_transport/overland_flow_transport.jl b/src/sediment_transport/overland_flow_transport.jl index 442e62140..162415236 100644 --- a/src/sediment_transport/overland_flow_transport.jl +++ b/src/sediment_transport/overland_flow_transport.jl @@ -1,7 +1,7 @@ abstract type AbstractSedimentLandTransportModel{T} end ## Total sediment transport in overland flow structs and functions -@get_units @with_kw struct SedimentLandTransportVariables{T} +@get_units @grid_loc @with_kw struct SedimentLandTransportVariables{T} # Total sediment flux amount::Vector{T} | "t dt-1" deposition::Vector{T} | "t dt-1" @@ -15,7 +15,7 @@ function SedimentLandTransportVariables( return SedimentLandTransportVariables{T}(; amount = amount, deposition = deposition) end -@get_units @with_kw struct SedimentLandTransportBC{T} +@get_units @grid_loc @with_kw struct SedimentLandTransportBC{T} # Eroded material erosion::Vector{T} | "t dt-1" # Transport capacity @@ -68,7 +68,7 @@ function update!(model::SedimentLandTransportModel, network) end ## Total transport capacity with particle differentiation structs and functions -@get_units @with_kw struct SedimentLandTransportDifferentiationVariables{T} +@get_units @grid_loc @with_kw struct SedimentLandTransportDifferentiationVariables{T} # Total sediment flux amount::Vector{T} | "t dt-1" # Deposition @@ -126,7 +126,7 @@ function SedimentLandTransportDifferentiationVariables( ) end -@get_units @with_kw struct SedimentLandTransportDifferentiationBC{T} +@get_units @grid_loc @with_kw struct SedimentLandTransportDifferentiationBC{T} # Eroded clay erosion_clay::Vector{T} | "t dt-1" # Eroded silt diff --git a/src/sediment_transport/river_transport.jl b/src/sediment_transport/river_transport.jl index 74b11bd5b..b988273e8 100644 --- a/src/sediment_transport/river_transport.jl +++ b/src/sediment_transport/river_transport.jl @@ -1,7 +1,7 @@ abstract type AbstractSedimentRiverTransportModel{T} end ## Total sediment transport in overland flow structs and functions -@get_units @with_kw struct SedimentRiverTransportVariables{T} +@get_units @grid_loc @with_kw struct SedimentRiverTransportVariables{T} # Sediment flux [ton] amount::Vector{T} | "t dt-1" clay::Vector{T} | "t dt-1" @@ -79,7 +79,7 @@ function SedimentRiverTransportVariables( ) end -@get_units @with_kw struct SedimentRiverTransportBC{T} +@get_units @grid_loc @with_kw struct SedimentRiverTransportBC{T} # Waterlevel waterlevel::Vector{T} | "t dt-1" # Discharge @@ -125,7 +125,7 @@ function SedimentRiverTransportBC( end # Parameters for river transport -@get_units @with_kw struct SedimentRiverTransportParameters{T} +@get_units @grid_loc @with_kw struct SedimentRiverTransportParameters{T} # River bed/bank content clay clay_fraction::Vector{T} | "-" # River bed/bank content silt @@ -773,7 +773,7 @@ end abstract type AbstractSedimentConcentrationsRiverModel{T} end ## Total sediment transport in overland flow structs and functions -@get_units @with_kw struct SedimentConcentrationsRiverVariables{T} +@get_units @grid_loc @with_kw struct SedimentConcentrationsRiverVariables{T} # Total sediment concentration in the river total::Vector{T} | "g m-3" # suspended sediemnt concentration in the river @@ -795,7 +795,7 @@ function SedimentConcentrationsRiverVariables( ) end -@get_units @with_kw struct SedimentConcentrationsRiverBC{T} +@get_units @grid_loc @with_kw struct SedimentConcentrationsRiverBC{T} # Discharge q::Vector{T} | "m3 s-1" waterlevel::Vector{T} | "m" @@ -837,7 +837,7 @@ function SedimentConcentrationsRiverBC( end # Common parameters for transport capacity models -@get_units @with_kw struct SedimentConcentrationsRiverParameters{T} +@get_units @grid_loc @with_kw struct SedimentConcentrationsRiverParameters{T} # Clay mean diameter dm_clay::Vector{T} | "µm" # Silt mean diameter diff --git a/src/sediment_transport/transport_capacity.jl b/src/sediment_transport/transport_capacity.jl index 58eaf29e1..e1f625dfa 100644 --- a/src/sediment_transport/transport_capacity.jl +++ b/src/sediment_transport/transport_capacity.jl @@ -1,7 +1,7 @@ abstract type AbstractTransportCapacityModel{T} end ## Total sediment transport capacity structs and functions -@get_units @with_kw struct TransportCapacityModelVariables{T} +@get_units @grid_loc @with_kw struct TransportCapacityModelVariables{T} # Total sediment transport capacity amount::Vector{T} | "t dt-1" end @@ -10,7 +10,7 @@ function TransportCapacityModelVariables(n; amount::Vector{T} = fill(mv, n)) whe return TransportCapacityModelVariables{T}(; amount = amount) end -@get_units @with_kw struct TransportCapacityBC{T} +@get_units @grid_loc @with_kw struct TransportCapacityBC{T} # Discharge q::Vector{T} | "m3 s-1" # Flow depth @@ -45,7 +45,7 @@ end ##################### Overland Flow ##################### # Govers parameters for transport capacity models -@get_units @with_kw struct TransportCapacityGoversParameters{T} +@get_units @grid_loc @with_kw struct TransportCapacityGoversParameters{T} # Drain slope slope::Vector{T} | "m m-1" # Particle density @@ -141,7 +141,7 @@ function update!(model::TransportCapacityGoversModel, width, waterbodies, rivers end # Common parameters for transport capacity models -@get_units @with_kw struct TransportCapacityYalinParameters{T} +@get_units @grid_loc @with_kw struct TransportCapacityYalinParameters{T} # Drain slope slope::Vector{T} | "m m-1" # Particle density @@ -222,7 +222,7 @@ function update!(model::TransportCapacityYalinModel, width, waterbodies, rivers, end ## Total transport capacity with particle differentiation structs and functions -@get_units @with_kw struct TransportCapacityYalinDifferentiationModelVariables{T} +@get_units @grid_loc @with_kw struct TransportCapacityYalinDifferentiationModelVariables{T} # Total sediment transport capacity amount::Vector{T} | "t dt-1" # Transport capacity clay @@ -257,7 +257,7 @@ function TransportCapacityYalinDifferentiationModelVariables( end # Common parameters for transport capacity models -@get_units @with_kw struct TransportCapacityYalinDifferentiationParameters{T} +@get_units @grid_loc @with_kw struct TransportCapacityYalinDifferentiationParameters{T} # Particle density density::Vector{T} | "kg m-3" # Clay mean diameter @@ -441,7 +441,7 @@ function update!( end ##################### River Flow ##################### -@get_units @with_kw struct TransportCapacityRiverParameters{T} +@get_units @grid_loc @with_kw struct TransportCapacityRiverParameters{T} # Particle density density::Vector{T} | "kg m-3" # Particle mean diameter @@ -471,7 +471,7 @@ function TransportCapacityRiverParameters(nc, config, inds) end # Bagnold parameters for transport capacity models -@get_units @with_kw struct TransportCapacityBagnoldParameters{T} +@get_units @grid_loc @with_kw struct TransportCapacityBagnoldParameters{T} # Bagnold transport capacity coefficient c_bagnold::Vector{T} | "-" # Bagnold transport capacity exponent @@ -582,7 +582,7 @@ function update!(model::TransportCapacityEngelundModel, geometry::RiverGeometry, end # Kodatie parameters for transport capacity models -@get_units @with_kw struct TransportCapacityKodatieParameters{T} +@get_units @grid_loc @with_kw struct TransportCapacityKodatieParameters{T} # Kodatie transport capacity coefficient a a_kodatie::Vector{T} | "-" # Kodatie transport capacity coefficient b From a3ffec756243506caf2e1bf2d2646e6f9f55f3b6 Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Tue, 26 Nov 2024 08:27:21 +0100 Subject: [PATCH 28/42] Remove unpack --- src/sediment_transport/river_transport.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sediment_transport/river_transport.jl b/src/sediment_transport/river_transport.jl index b988273e8..867ebbc38 100644 --- a/src/sediment_transport/river_transport.jl +++ b/src/sediment_transport/river_transport.jl @@ -440,7 +440,7 @@ function update!( store_gravel, ) = model.variables - @unpack graph, order = network + (; graph, order) = network # Sediment transport - water balance in the river for v in order From c15cf6a12c5078b6cfcb48a6e9ad3413ab434c62 Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Tue, 26 Nov 2024 08:32:38 +0100 Subject: [PATCH 29/42] Remove duplicate `get_sediment_states()` --- src/states.jl | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/states.jl b/src/states.jl index 619018ffc..6ff0a8ec1 100644 --- a/src/states.jl +++ b/src/states.jl @@ -94,30 +94,6 @@ function get_sediment_states() return states end -function get_sediment_states() - states = ( - :leftover_clay, - :leftover_silt, - :leftover_sand, - :leftover_sagg, - :leftover_lagg, - :leftover_gravel, - :store_clay, - :store_silt, - :store_sand, - :store_sagg, - :store_lagg, - :store_gravel, - :clay, - :silt, - :sand, - :sagg, - :lagg, - :gravel, - ) - return states -end - """ add_to_required_states(required_states::Tuple, key_entry::Tuple, states::Tuple) add_to_required_states(required_states::Tuple, key_entry::Tuple, states::Nothing) From c53fa36e38b461e79b6a648beedba424c50763a6 Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Tue, 26 Nov 2024 08:47:30 +0100 Subject: [PATCH 30/42] Update version testdata `staticmaps-moselle-sed.nc` --- build/create_binaries/download_test_data.jl | 5 +++-- test/run_sbm_gwf.jl | 16 ---------------- test/run_sediment.jl | 18 +----------------- test/runtests.jl | 2 +- 4 files changed, 5 insertions(+), 36 deletions(-) diff --git a/build/create_binaries/download_test_data.jl b/build/create_binaries/download_test_data.jl index 339e19b91..4624d8d57 100644 --- a/build/create_binaries/download_test_data.jl +++ b/build/create_binaries/download_test_data.jl @@ -24,7 +24,7 @@ forcing_moselle_path = testdata(v"0.2.6", "forcing-moselle.nc", "forcing-moselle forcing_moselle_sed_path = testdata(v"0.2.3", "forcing-moselle-sed.nc", "forcing-moselle-sed.nc") staticmaps_moselle_sed_path = - testdata(v"0.2.3", "staticmaps-moselle-sed.nc", "staticmaps-moselle-sed.nc") + testdata(v"0.3.0", "staticmaps-moselle-sed.nc", "staticmaps-moselle-sed.nc") instates_moselle_sed_path = testdata(v"0.2", "instates-moselle-sed.nc", "instates-moselle-sed.nc") instates_moselle_path = testdata(v"0.2.6", "instates-moselle.nc", "instates-moselle.nc") @@ -50,4 +50,5 @@ 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") diff --git a/test/run_sbm_gwf.jl b/test/run_sbm_gwf.jl index 60b77a8de..6b9749994 100644 --- a/test/run_sbm_gwf.jl +++ b/test/run_sbm_gwf.jl @@ -187,20 +187,4 @@ end @test gw.recharge.rate[19] ≈ -0.0014241196552847502f0 end -@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_sediment.jl b/test/run_sediment.jl index 7741dec23..4d0336d41 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -27,7 +27,7 @@ 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 @@ -80,20 +80,4 @@ end @test mean(river.concentrations.variables.suspended) ≈ 0.8260083257660087f0 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 6981af08d..05518f8b2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -41,7 +41,7 @@ forcing_moselle_path = testdata(v"0.2.6", "forcing-moselle.nc", "forcing-moselle forcing_moselle_sed_path = testdata(v"0.2.3", "forcing-moselle-sed.nc", "forcing-moselle-sed.nc") staticmaps_moselle_sed_path = - testdata(v"0.2.3", "staticmaps-moselle-sed.nc", "staticmaps-moselle-sed.nc") + testdata(v"0.3.0", "staticmaps-moselle-sed.nc", "staticmaps-moselle-sed.nc") instates_moselle_sed_path = testdata(v"0.2", "instates-moselle-sed.nc", "instates-moselle-sed.nc") instates_moselle_path = testdata(v"0.2.6", "instates-moselle.nc", "instates-moselle.nc") From b2ff488c696d55f2241715b998efe1ee55d548a3 Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Tue, 26 Nov 2024 09:43:33 +0100 Subject: [PATCH 31/42] Rename couple of initialize functions Sediment For structs `SoilLoss`, `LandSediment` and `RiverSediment`. --- src/erosion.jl | 2 +- src/sediment_flux.jl | 4 ++-- src/sediment_model.jl | 7 +++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/erosion.jl b/src/erosion.jl index e61d95559..c6951587c 100644 --- a/src/erosion.jl +++ b/src/erosion.jl @@ -6,7 +6,7 @@ soil_erosion::SE end -function initialize_soil_loss(nc, config, inds) +function SoilLoss(nc, config, inds) n = length(inds) hydrometeo_forcing = HydrometeoForcing(n) diff --git a/src/sediment_flux.jl b/src/sediment_flux.jl index d62321dd9..bcb710170 100644 --- a/src/sediment_flux.jl +++ b/src/sediment_flux.jl @@ -9,7 +9,7 @@ rivers::Vector{Bool} | "-" end -function initialize_overland_flow_sediment(nc, config, inds, waterbodies, rivers) +function OverlandFlowSediment(nc, config, inds, waterbodies, rivers) n = length(inds) hydrometeo_forcing = HydrometeoForcing(n) geometry = LandGeometry(nc, config, inds) @@ -88,7 +88,7 @@ end waterbodies::Vector{Bool} | "-" end -function initialize_river_flow_sediment(nc, config, inds, waterbodies) +function RiverSediment(nc, config, inds, waterbodies) n = length(inds) hydrometeo_forcing = HydrometeoForcing(n) geometry = RiverGeometry(nc, config, inds) diff --git a/src/sediment_model.jl b/src/sediment_model.jl index 0db933ef3..3143b190a 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -28,7 +28,7 @@ function initialize_sediment_model(config::Config) reservoir = () lake = () - soilloss = initialize_soil_loss(nc, config, inds) + soilloss = SoilLoss(nc, config, inds) # Get waterbodies mask do_reservoirs = get(config.model, "doreservoir", false)::Bool @@ -64,8 +64,7 @@ function initialize_sediment_model(config::Config) ldd = ldd_2d[inds] # # lateral part sediment in overland flow - overland_flow_sediment = - initialize_overland_flow_sediment(nc, config, inds, waterbodies, river) + overland_flow_sediment = OverlandFlowSediment(nc, config, inds, waterbodies, river) graph = flowgraph(ldd, inds, pcr_dir) @@ -93,7 +92,7 @@ function initialize_sediment_model(config::Config) index_river = filter(i -> !isequal(river[i], 0), 1:n) frac_toriver = fraction_runoff_toriver(graph, ldd, index_river, landslope, n) - river_sediment = initialize_river_flow_sediment(nc, config, inds_riv, waterbodies) + river_sediment = RiverSediment(nc, config, inds_riv, waterbodies) modelmap = ( vertical = soilloss, From 6b62c34fa424439d85ba5aa86dca4f7ef2d93765 Mon Sep 17 00:00:00 2001 From: Willem van Verseveld Date: Tue, 26 Nov 2024 10:03:06 +0100 Subject: [PATCH 32/42] Add BMI `@grid_loc` macro to erosion files --- src/erosion/overland_flow_erosion.jl | 6 +++--- src/erosion/rainfall_erosion.jl | 10 +++++----- src/erosion/river_erosion.jl | 6 +++--- src/erosion/soil_erosion.jl | 6 +++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/erosion/overland_flow_erosion.jl b/src/erosion/overland_flow_erosion.jl index b12805308..494dc2ff2 100644 --- a/src/erosion/overland_flow_erosion.jl +++ b/src/erosion/overland_flow_erosion.jl @@ -3,7 +3,7 @@ abstract type AbstractOverlandFlowErosionModel{T} end struct NoOverlandFlowErosionModel{T} <: AbstractOverlandFlowErosionModel{T} end ## Overland flow structs and functions -@get_units @with_kw struct OverlandFlowErosionVariables{T} +@get_units @grid_loc @with_kw struct OverlandFlowErosionVariables{T} # Total soil erosion from overland flow amount::Vector{T} | "t dt-1" end @@ -12,7 +12,7 @@ function OverlandFlowErosionVariables(n; amount::Vector{T} = fill(mv, n)) where return OverlandFlowErosionVariables{T}(; amount = amount) end -@get_units @with_kw struct OverlandFlowErosionBC{T} +@get_units @grid_loc @with_kw struct OverlandFlowErosionBC{T} # Overland flow [m3 s-1] q::Vector{T} end @@ -22,7 +22,7 @@ function OverlandFlowErosionBC(n; q::Vector{T} = fill(mv, n)) where {T} end # ANSWERS specific structs and functions for rainfall erosion -@get_units @with_kw struct OverlandFlowErosionAnswersParameters{T} +@get_units @grid_loc @with_kw struct OverlandFlowErosionAnswersParameters{T} # Soil erodibility factor usle_k::Vector{T} | "-" # Crop management factor diff --git a/src/erosion/rainfall_erosion.jl b/src/erosion/rainfall_erosion.jl index 32c0de725..5e70a5aaa 100644 --- a/src/erosion/rainfall_erosion.jl +++ b/src/erosion/rainfall_erosion.jl @@ -3,7 +3,7 @@ abstract type AbstractRainfallErosionModel{T} end struct NoRainfallErosionModel{T} <: AbstractRainfallErosionModel{T} end ## General rainfall erosion functions and structs -@get_units @with_kw struct RainfallErosionModelVariables{T} +@get_units @grid_loc @with_kw struct RainfallErosionModelVariables{T} # Total soil erosion from rainfall (splash) amount::Vector{T} | "t dt-1" end @@ -13,7 +13,7 @@ function RainfallErosionModelVariables(n; amount::Vector{T} = fill(mv, n)) where end ## EUROSEM specific structs and functions for rainfall erosion -@get_units @with_kw struct RainfallErosionEurosemBC{T} +@get_units @grid_loc @with_kw struct RainfallErosionEurosemBC{T} # precipitation precipitation::Vector{T} | "mm dt-1" # Interception @@ -35,7 +35,7 @@ function RainfallErosionEurosemBC( ) end -@get_units @with_kw struct RainfallErosionEurosemParameters{T} +@get_units @grid_loc @with_kw struct RainfallErosionEurosemParameters{T} # Soil detachability factor soil_detachability::Vector{T} | "g J-1" # Exponent EUROSEM @@ -156,7 +156,7 @@ function update!(model::RainfallErosionEurosemModel, geometry::LandGeometry, ts) end ### ANSWERS specific structs and functions for rainfall erosion -@get_units @with_kw struct RainfallErosionAnswersBC{T} +@get_units @grid_loc @with_kw struct RainfallErosionAnswersBC{T} # precipitation precipitation::Vector{T} | "mm dt-1" end @@ -165,7 +165,7 @@ function RainfallErosionAnswersBC(n; precipitation::Vector{T} = fill(mv, n)) whe return RainfallErosionAnswersBC{T}(; precipitation = precipitation) end -@get_units @with_kw struct RainfallErosionAnswersParameters{T} +@get_units @grid_loc @with_kw struct RainfallErosionAnswersParameters{T} # Soil erodibility factor usle_k::Vector{T} | "-" # Crop management factor diff --git a/src/erosion/river_erosion.jl b/src/erosion/river_erosion.jl index 2e2bfc586..b6f99ffcf 100644 --- a/src/erosion/river_erosion.jl +++ b/src/erosion/river_erosion.jl @@ -1,7 +1,7 @@ abstract type AbstractRiverErosionModel{T} end ## Potential direct river erosion structs and functions -@get_units @with_kw struct RiverErosionModelVariables{T} +@get_units @grid_loc @with_kw struct RiverErosionModelVariables{T} # Potential river bed erosion bed::Vector{T} | "t dt-1" # Potential river bank erosion @@ -16,7 +16,7 @@ function RiverErosionModelVariables( return RiverErosionModelVariables{T}(; bed = bed, bank = bank) end -@get_units @with_kw struct RiverErosionBC{T} +@get_units @grid_loc @with_kw struct RiverErosionBC{T} # Waterlevel waterlevel::Vector{T} | "t dt-1" end @@ -26,7 +26,7 @@ function RiverErosionBC(n; waterlevel::Vector{T} = fill(mv, n)) where {T} end # Parameters for the Julian Torres river erosion model -@get_units @with_kw struct RiverErosionParameters{T} +@get_units @grid_loc @with_kw struct RiverErosionParameters{T} # Mean diameter in the river bed/bank d50::Vector{T} | "mm" end diff --git a/src/erosion/soil_erosion.jl b/src/erosion/soil_erosion.jl index 66814d41c..7692c1f4d 100644 --- a/src/erosion/soil_erosion.jl +++ b/src/erosion/soil_erosion.jl @@ -1,7 +1,7 @@ abstract type AbstractSoilErosionModel{T} end ## Total soil erosion and differentiation structs and functions -@get_units @with_kw struct SoilErosionModelVariables{T} +@get_units @grid_loc @with_kw struct SoilErosionModelVariables{T} # Total soil erosion amount::Vector{T} | "t dt-1" # Total clay erosion @@ -35,7 +35,7 @@ function SoilErosionModelVariables( ) end -@get_units @with_kw struct SoilErosionBC{T} +@get_units @grid_loc @with_kw struct SoilErosionBC{T} # Rainfall erosion rainfall_erosion::Vector{T} | "t dt-1" # Overland flow erosion @@ -54,7 +54,7 @@ function SoilErosionBC( end # Parameters for particle differentiation -@get_units @with_kw struct SoilErosionParameters{T} +@get_units @grid_loc @with_kw struct SoilErosionParameters{T} # Soil content clay clay_fraction::Vector{T} | "-" # Soil content silt From 03c57b0767a7d6cba9580c1dd8b6b714d7ffc9d9 Mon Sep 17 00:00:00 2001 From: hboisgon Date: Wed, 4 Dec 2024 13:30:54 +0800 Subject: [PATCH 33/42] move geometry to parameters --- src/erosion.jl | 4 +- src/erosion/overland_flow_erosion.jl | 2 +- src/erosion/rainfall_erosion.jl | 4 +- src/erosion/river_erosion.jl | 2 +- src/geometry.jl | 79 ------------------- src/parameters.jl | 82 ++++++++++++++++++++ src/sediment_flux.jl | 8 +- src/sediment_transport/river_transport.jl | 4 +- src/sediment_transport/transport_capacity.jl | 12 +-- test/sediment_config.toml | 4 +- 10 files changed, 102 insertions(+), 99 deletions(-) delete mode 100644 src/geometry.jl diff --git a/src/erosion.jl b/src/erosion.jl index c6951587c..c3721132c 100644 --- a/src/erosion.jl +++ b/src/erosion.jl @@ -1,6 +1,6 @@ @with_kw struct SoilLoss{RE, OFE, SE, T} hydrometeo_forcing::HydrometeoForcing - geometry::LandGeometry + geometry::LandParameters rainfall_erosion::RE overland_flow_erosion::OFE soil_erosion::SE @@ -10,7 +10,7 @@ function SoilLoss(nc, config, inds) n = length(inds) hydrometeo_forcing = HydrometeoForcing(n) - geometry = LandGeometry(nc, config, inds) + geometry = LandParameters(nc, config, inds) # Rainfall erosion rainfallerosionmodel = get(config.model, "rainfall_erosion", "answers")::String diff --git a/src/erosion/overland_flow_erosion.jl b/src/erosion/overland_flow_erosion.jl index 494dc2ff2..1b82968d5 100644 --- a/src/erosion/overland_flow_erosion.jl +++ b/src/erosion/overland_flow_erosion.jl @@ -99,7 +99,7 @@ function update_boundary_conditions!( return nothing end -function update!(model::OverlandFlowErosionAnswersModel, geometry::LandGeometry, ts) +function update!(model::OverlandFlowErosionAnswersModel, geometry::LandParameters, ts) (; q) = model.boundary_conditions (; usle_k, usle_c, answers_k) = model.parameters (; amount) = model.variables diff --git a/src/erosion/rainfall_erosion.jl b/src/erosion/rainfall_erosion.jl index 5e70a5aaa..66ec3479c 100644 --- a/src/erosion/rainfall_erosion.jl +++ b/src/erosion/rainfall_erosion.jl @@ -127,7 +127,7 @@ function update_boundary_conditions!( @. waterlevel = model.boundary_conditions.waterlevel_land end -function update!(model::RainfallErosionEurosemModel, geometry::LandGeometry, ts) +function update!(model::RainfallErosionEurosemModel, geometry::LandParameters, ts) (; precipitation, interception, waterlevel) = model.boundary_conditions (; soil_detachability, @@ -221,7 +221,7 @@ function update_boundary_conditions!( @. precipitation = hydrometeo_forcing.precipitation end -function update!(model::RainfallErosionAnswersModel, geometry::LandGeometry, ts) +function update!(model::RainfallErosionAnswersModel, geometry::LandParameters, ts) (; precipitation) = model.boundary_conditions (; usle_k, usle_c) = model.parameters (; amount) = model.variables diff --git a/src/erosion/river_erosion.jl b/src/erosion/river_erosion.jl index b6f99ffcf..655bed9b2 100644 --- a/src/erosion/river_erosion.jl +++ b/src/erosion/river_erosion.jl @@ -73,7 +73,7 @@ function update_boundary_conditions!( @. waterlevel = waterlevel_river end -function update!(model::RiverErosionJulianTorresModel, geometry::RiverGeometry, ts) +function update!(model::RiverErosionJulianTorresModel, geometry::RiverParameters, ts) (; waterlevel) = model.boundary_conditions (; d50) = model.parameters (; bed, bank) = model.variables diff --git a/src/geometry.jl b/src/geometry.jl deleted file mode 100644 index 476731a7e..000000000 --- a/src/geometry.jl +++ /dev/null @@ -1,79 +0,0 @@ - -@get_units @grid_loc @with_kw struct LandGeometry{T} - # cell area [m^2] - area::Vector{T} | "m^2" - # drain width [m] - width::Vector{T} | "m" - # drain slope - slope::Vector{T} | "-" -end - -function LandGeometry(nc, config, inds) - # 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) - area = xl .* yl - ldd = ncread(nc, config, "ldd"; optional = false, sel = inds, allow_missing = true) - drain_width = map(detdrainwidth, ldd, xl, yl) - landslope = ncread( - nc, - config, - "vertical.geometry.slope"; - optional = false, - sel = inds, - type = Float, - ) - clamp!(landslope, 0.00001, Inf) - - land_geometry = - LandGeometry{Float}(; area = area, width = drain_width, slope = landslope) - return land_geometry -end - -@get_units @grid_loc @with_kw struct RiverGeometry{T} - # drain width [m] - width::Vector{T} | "m" - # drain length - length::Vector{T} | "m" - # slope - slope::Vector{T} | "-" -end - -function RiverGeometry(nc, config, inds) - riverwidth = ncread( - nc, - config, - "lateral.river.geometry.width"; - optional = false, - sel = inds, - type = Float, - ) - riverlength = ncread( - nc, - config, - "lateral.river.geometry.length"; - optional = false, - sel = inds, - type = Float, - ) - riverslope = ncread( - nc, - config, - "lateral.river.geometry.slope"; - optional = false, - sel = inds, - type = Float, - ) - minimum(riverlength) > 0 || error("river length must be positive on river cells") - minimum(riverwidth) > 0 || error("river width must be positive on river cells") - clamp!(riverslope, 0.00001, Inf) - - river_geometry = - RiverGeometry{Float}(; width = riverwidth, length = riverlength, slope = riverslope) - return river_geometry -end \ No newline at end of file diff --git a/src/parameters.jl b/src/parameters.jl index f2d2d582a..b6f1d085e 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -101,4 +101,86 @@ function VegetationParameters(nc, config, inds) ) end return vegetation_parameter_set +end + +@get_units @grid_loc @with_kw struct LandParameters{T} + # cell area [m^2] + area::Vector{T} | "m^2" + # drain width [m] + width::Vector{T} | "m" + # drain slope + slope::Vector{T} | "-" +end + +function LandParameters(nc, config, inds) + # 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) + area = xl .* yl + ldd = ncread(nc, config, "ldd"; optional = false, sel = inds, allow_missing = true) + drain_width = map(detdrainwidth, ldd, xl, yl) + landslope = ncread( + nc, + config, + "vertical.land_parameter_set.slope"; + optional = false, + sel = inds, + type = Float, + ) + clamp!(landslope, 0.00001, Inf) + + land_parameter_set = + LandParameters{Float}(; area = area, width = drain_width, slope = landslope) + return land_parameter_set +end + +@get_units @grid_loc @with_kw struct RiverParameters{T} + # river width [m] + width::Vector{T} | "m" + # river length + length::Vector{T} | "m" + # slope + slope::Vector{T} | "-" +end + +function RiverParameters(nc, config, inds) + riverwidth = ncread( + nc, + config, + "lateral.river_parameter_set.width"; + optional = false, + sel = inds, + type = Float, + ) + riverlength = ncread( + nc, + config, + "lateral.river_parameter_set.length"; + optional = false, + sel = inds, + type = Float, + ) + riverslope = ncread( + nc, + config, + "lateral.river_parameter_set.slope"; + optional = false, + sel = inds, + type = Float, + ) + minimum(riverlength) > 0 || error("river length must be positive on river cells") + minimum(riverwidth) > 0 || error("river width must be positive on river cells") + clamp!(riverslope, 0.00001, Inf) + + river_parameter_set = RiverParameters{Float}(; + width = riverwidth, + length = riverlength, + slope = riverslope, + ) + return river_parameter_set end \ No newline at end of file diff --git a/src/sediment_flux.jl b/src/sediment_flux.jl index bcb710170..2560ebd23 100644 --- a/src/sediment_flux.jl +++ b/src/sediment_flux.jl @@ -1,7 +1,7 @@ ### Overland flow ### @get_units @grid_loc @with_kw struct OverlandFlowSediment{TT, SF, TR, T} hydrometeo_forcing::HydrometeoForcing - geometry::LandGeometry + geometry::LandParameters transport_capacity::TT sediment_flux::SF to_river::TR @@ -12,7 +12,7 @@ end function OverlandFlowSediment(nc, config, inds, waterbodies, rivers) n = length(inds) hydrometeo_forcing = HydrometeoForcing(n) - geometry = LandGeometry(nc, config, inds) + geometry = LandParameters(nc, config, inds) # Check what transport capacity equation will be used do_river = get(config.model, "runrivermodel", false)::Bool # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] @@ -80,7 +80,7 @@ end ### River ### @get_units @grid_loc @with_kw struct RiverSediment{TTR, ER, SFR, CR, T} hydrometeo_forcing::HydrometeoForcing - geometry::RiverGeometry + geometry::RiverParameters transport_capacity::TTR potential_erosion::ER sediment_flux::SFR @@ -91,7 +91,7 @@ end function RiverSediment(nc, config, inds, waterbodies) n = length(inds) hydrometeo_forcing = HydrometeoForcing(n) - geometry = RiverGeometry(nc, config, inds) + geometry = RiverParameters(nc, config, inds) # Check what transport capacity equation will be used # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] diff --git a/src/sediment_transport/river_transport.jl b/src/sediment_transport/river_transport.jl index 867ebbc38..73abfc9a1 100644 --- a/src/sediment_transport/river_transport.jl +++ b/src/sediment_transport/river_transport.jl @@ -385,7 +385,7 @@ end function update!( model::SedimentRiverTransportModel, network, - geometry::RiverGeometry, + geometry::RiverParameters, waterbodies, ts, ) @@ -952,7 +952,7 @@ function update_boundary_conditions!( @. gravel = sediment_flux_model.variables.gravel end -function update!(model::SedimentConcentrationsRiverModel, geometry::RiverGeometry, ts) +function update!(model::SedimentConcentrationsRiverModel, geometry::RiverParameters, ts) (; q, waterlevel, clay, silt, sand, sagg, lagg, gravel) = model.boundary_conditions (; dm_clay, dm_silt, dm_sand, dm_sagg, dm_lagg, dm_gravel) = model.parameters (; total, suspended, bed) = model.variables diff --git a/src/sediment_transport/transport_capacity.jl b/src/sediment_transport/transport_capacity.jl index e1f625dfa..e5fdfb228 100644 --- a/src/sediment_transport/transport_capacity.jl +++ b/src/sediment_transport/transport_capacity.jl @@ -355,7 +355,7 @@ end function update!( model::TransportCapacityYalinDifferentiationModel, - geometry::LandGeometry, + geometry::LandParameters, waterbodies, rivers, ts, @@ -520,7 +520,7 @@ function TransportCapacityBagnoldModel(nc, config, inds) return model end -function update!(model::TransportCapacityBagnoldModel, geometry::RiverGeometry, ts) +function update!(model::TransportCapacityBagnoldModel, geometry::RiverParameters, ts) (; q, waterlevel) = model.boundary_conditions (; c_bagnold, e_bagnold) = model.parameters (; amount) = model.variables @@ -561,7 +561,7 @@ function TransportCapacityEngelundModel(nc, config, inds) return model end -function update!(model::TransportCapacityEngelundModel, geometry::RiverGeometry, ts) +function update!(model::TransportCapacityEngelundModel, geometry::RiverParameters, ts) (; q, waterlevel) = model.boundary_conditions (; density, d50) = model.parameters (; amount) = model.variables @@ -655,7 +655,7 @@ function TransportCapacityKodatieModel(nc, config, inds) return model end -function update!(model::TransportCapacityKodatieModel, geometry::RiverGeometry, ts) +function update!(model::TransportCapacityKodatieModel, geometry::RiverParameters, ts) (; q, waterlevel) = model.boundary_conditions (; a_kodatie, b_kodatie, c_kodatie, d_kodatie) = model.parameters (; amount) = model.variables @@ -697,7 +697,7 @@ function TransportCapacityYangModel(nc, config, inds) return model end -function update!(model::TransportCapacityYangModel, geometry::RiverGeometry, ts) +function update!(model::TransportCapacityYangModel, geometry::RiverParameters, ts) (; q, waterlevel) = model.boundary_conditions (; density, d50) = model.parameters (; amount) = model.variables @@ -737,7 +737,7 @@ function TransportCapacityMolinasModel(nc, config, inds) return model end -function update!(model::TransportCapacityMolinasModel, geometry::RiverGeometry, ts) +function update!(model::TransportCapacityMolinasModel, geometry::RiverParameters, ts) (; q, waterlevel) = model.boundary_conditions (; density, d50) = model.parameters (; amount) = model.variables diff --git a/test/sediment_config.toml b/test/sediment_config.toml index de64a3188..0efaf68ab 100644 --- a/test/sediment_config.toml +++ b/test/sediment_config.toml @@ -79,7 +79,7 @@ waterlevel_land = "levKinL" #interception = "int" q_land = "runL" -[input.vertical.geometry] +[input.vertical.land_parameter_set] slope = "Slope" [input.vertical.rainfall_erosion.parameters] @@ -122,7 +122,7 @@ dm_lagg = "dm_lagg" waterlevel_river = "h" q_river = "q" -[input.lateral.river.geometry] +[input.lateral.river_parameter_set] length = "wflow_riverlength" slope = "RiverSlope" width = "wflow_riverwidth" From faaf49b88ab74ad68a27c3d30c1bbf60f2d73372 Mon Sep 17 00:00:00 2001 From: hboisgon Date: Wed, 4 Dec 2024 13:51:57 +0800 Subject: [PATCH 34/42] move sediment files to a sediment folder --- src/Wflow.jl | 23 +++++++++---------- src/{ => sediment}/erosion/erosion_process.jl | 0 .../erosion/overland_flow_erosion.jl | 0 .../erosion/rainfall_erosion.jl | 0 src/{ => sediment}/erosion/river_erosion.jl | 0 src/{ => sediment}/erosion/soil_erosion.jl | 0 .../sediment_transport/deposition.jl | 0 .../sediment_transport/land_to_river.jl | 0 .../overland_flow_transport.jl | 0 .../sediment_transport/river_transport.jl | 0 .../sediment_transport/transport_capacity.jl | 0 .../transport_capacity_process.jl | 0 src/sediment_model.jl | 2 +- 13 files changed, 12 insertions(+), 13 deletions(-) rename src/{ => sediment}/erosion/erosion_process.jl (100%) rename src/{ => sediment}/erosion/overland_flow_erosion.jl (100%) rename src/{ => sediment}/erosion/rainfall_erosion.jl (100%) rename src/{ => sediment}/erosion/river_erosion.jl (100%) rename src/{ => sediment}/erosion/soil_erosion.jl (100%) rename src/{ => sediment}/sediment_transport/deposition.jl (100%) rename src/{ => sediment}/sediment_transport/land_to_river.jl (100%) rename src/{ => sediment}/sediment_transport/overland_flow_transport.jl (100%) rename src/{ => sediment}/sediment_transport/river_transport.jl (100%) rename src/{ => sediment}/sediment_transport/transport_capacity.jl (100%) rename src/{ => sediment}/sediment_transport/transport_capacity_process.jl (100%) diff --git a/src/Wflow.jl b/src/Wflow.jl index 767c1e9b0..22318901d 100644 --- a/src/Wflow.jl +++ b/src/Wflow.jl @@ -141,7 +141,6 @@ Base.show(io::IO, m::Model) = print(io, "model of type ", typeof(m)) include("forcing.jl") include("parameters.jl") -include("geometry.jl") include("flow.jl") include("horizontal_process.jl") include("vegetation/rainfall_interception.jl") @@ -157,17 +156,17 @@ include("sbm.jl") include("demand/water_demand.jl") include("reservoir_lake.jl") include("sbm_model.jl") -include("erosion/erosion_process.jl") -include("erosion/rainfall_erosion.jl") -include("erosion/overland_flow_erosion.jl") -include("erosion/soil_erosion.jl") -include("erosion/river_erosion.jl") -include("sediment_transport/deposition.jl") -include("sediment_transport/transport_capacity_process.jl") -include("sediment_transport/transport_capacity.jl") -include("sediment_transport/overland_flow_transport.jl") -include("sediment_transport/land_to_river.jl") -include("sediment_transport/river_transport.jl") +include("sediment/erosion/erosion_process.jl") +include("sediment/erosion/rainfall_erosion.jl") +include("sediment/erosion/overland_flow_erosion.jl") +include("sediment/erosion/soil_erosion.jl") +include("sediment/erosion/river_erosion.jl") +include("sediment/sediment_transport/deposition.jl") +include("sediment/sediment_transport/transport_capacity_process.jl") +include("sediment/sediment_transport/transport_capacity.jl") +include("sediment/sediment_transport/overland_flow_transport.jl") +include("sediment/sediment_transport/land_to_river.jl") +include("sediment/sediment_transport/river_transport.jl") include("erosion.jl") include("sediment_flux.jl") include("sediment_model.jl") diff --git a/src/erosion/erosion_process.jl b/src/sediment/erosion/erosion_process.jl similarity index 100% rename from src/erosion/erosion_process.jl rename to src/sediment/erosion/erosion_process.jl diff --git a/src/erosion/overland_flow_erosion.jl b/src/sediment/erosion/overland_flow_erosion.jl similarity index 100% rename from src/erosion/overland_flow_erosion.jl rename to src/sediment/erosion/overland_flow_erosion.jl diff --git a/src/erosion/rainfall_erosion.jl b/src/sediment/erosion/rainfall_erosion.jl similarity index 100% rename from src/erosion/rainfall_erosion.jl rename to src/sediment/erosion/rainfall_erosion.jl diff --git a/src/erosion/river_erosion.jl b/src/sediment/erosion/river_erosion.jl similarity index 100% rename from src/erosion/river_erosion.jl rename to src/sediment/erosion/river_erosion.jl diff --git a/src/erosion/soil_erosion.jl b/src/sediment/erosion/soil_erosion.jl similarity index 100% rename from src/erosion/soil_erosion.jl rename to src/sediment/erosion/soil_erosion.jl diff --git a/src/sediment_transport/deposition.jl b/src/sediment/sediment_transport/deposition.jl similarity index 100% rename from src/sediment_transport/deposition.jl rename to src/sediment/sediment_transport/deposition.jl diff --git a/src/sediment_transport/land_to_river.jl b/src/sediment/sediment_transport/land_to_river.jl similarity index 100% rename from src/sediment_transport/land_to_river.jl rename to src/sediment/sediment_transport/land_to_river.jl diff --git a/src/sediment_transport/overland_flow_transport.jl b/src/sediment/sediment_transport/overland_flow_transport.jl similarity index 100% rename from src/sediment_transport/overland_flow_transport.jl rename to src/sediment/sediment_transport/overland_flow_transport.jl diff --git a/src/sediment_transport/river_transport.jl b/src/sediment/sediment_transport/river_transport.jl similarity index 100% rename from src/sediment_transport/river_transport.jl rename to src/sediment/sediment_transport/river_transport.jl diff --git a/src/sediment_transport/transport_capacity.jl b/src/sediment/sediment_transport/transport_capacity.jl similarity index 100% rename from src/sediment_transport/transport_capacity.jl rename to src/sediment/sediment_transport/transport_capacity.jl diff --git a/src/sediment_transport/transport_capacity_process.jl b/src/sediment/sediment_transport/transport_capacity_process.jl similarity index 100% rename from src/sediment_transport/transport_capacity_process.jl rename to src/sediment/sediment_transport/transport_capacity_process.jl diff --git a/src/sediment_model.jl b/src/sediment_model.jl index 3143b190a..084846763 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -82,7 +82,7 @@ function initialize_sediment_model(config::Config) landslope = ncread( nc, config, - "vertical.geometry.slope"; + "vertical.land_parameter_set.slope"; optional = false, sel = inds, type = Float, From 70e351608de01855a99ce258e19aad4c918670ec Mon Sep 17 00:00:00 2001 From: hboisgon Date: Wed, 4 Dec 2024 14:46:08 +0800 Subject: [PATCH 35/42] split atmospheric and hydrological forcing --- src/erosion.jl | 19 +++++--- src/forcing.jl | 23 +++++---- src/sediment/erosion/overland_flow_erosion.jl | 22 ++------- src/sediment/erosion/rainfall_erosion.jl | 48 ++++++++++--------- src/sediment/erosion/river_erosion.jl | 4 +- src/sediment/erosion/soil_erosion.jl | 4 +- .../sediment_transport/river_transport.jl | 12 ++--- .../sediment_transport/transport_capacity.jl | 4 +- src/sediment_flux.jl | 26 +++++----- test/run_sediment.jl | 8 ++-- test/runtests.jl | 26 +++++----- test/sediment_config.toml | 28 ++++++----- 12 files changed, 111 insertions(+), 113 deletions(-) diff --git a/src/erosion.jl b/src/erosion.jl index c3721132c..6fb17ea42 100644 --- a/src/erosion.jl +++ b/src/erosion.jl @@ -1,6 +1,8 @@ +"Total soil erosion model" @with_kw struct SoilLoss{RE, OFE, SE, T} - hydrometeo_forcing::HydrometeoForcing - geometry::LandParameters + atmospheric_forcing::AtmosphericForcing{T} + hydrological_forcing::HydrologicalForcing{T} + geometry::LandParameters{T} rainfall_erosion::RE overland_flow_erosion::OFE soil_erosion::SE @@ -9,7 +11,8 @@ end function SoilLoss(nc, config, inds) n = length(inds) - hydrometeo_forcing = HydrometeoForcing(n) + atmospheric_forcing = AtmosphericForcing(n) + hydrological_forcing = HydrologicalForcing(n) geometry = LandParameters(nc, config, inds) # Rainfall erosion @@ -40,7 +43,8 @@ function SoilLoss(nc, config, inds) typeof(soil_erosion_model), Float, }(; - hydrometeo_forcing = hydrometeo_forcing, + atmospheric_forcing = atmospheric_forcing, + hydrological_forcing = hydrological_forcing, geometry = geometry, rainfall_erosion = rainfall_erosion_model, overland_flow_erosion = overland_flow_erosion_model, @@ -51,7 +55,8 @@ end function update!(model::SoilLoss, dt) (; - hydrometeo_forcing, + atmospheric_forcing, + hydrological_forcing, geometry, rainfall_erosion, overland_flow_erosion, @@ -63,10 +68,10 @@ function update!(model::SoilLoss, dt) #need SBM refactor # Rainfall erosion - update_boundary_conditions!(rainfall_erosion, hydrometeo_forcing) + update_boundary_conditions!(rainfall_erosion, atmospheric_forcing, hydrological_forcing) update!(rainfall_erosion, geometry, ts) # Overland flow erosion - update_boundary_conditions!(overland_flow_erosion, hydrometeo_forcing) + update_boundary_conditions!(overland_flow_erosion, hydrological_forcing) update!(overland_flow_erosion, geometry, ts) # Total soil erosion and particle differentiation update_boundary_conditions!(soil_erosion, rainfall_erosion, overland_flow_erosion) diff --git a/src/forcing.jl b/src/forcing.jl index dc98d27ab..eb7241df6 100644 --- a/src/forcing.jl +++ b/src/forcing.jl @@ -18,9 +18,8 @@ function AtmosphericForcing( return AtmosphericForcing{T}(; precipitation, potential_evaporation, temperature) end -@get_units @grid_loc @with_kw struct HydrometeoForcing{T} - # Precipitation [mm Δt⁻¹] - precipitation::Vector{T} +"Struct to store hydrological forcing variables" +@get_units @grid_loc @with_kw struct HydrologicalForcing{T} # Overland flow depth [m] waterlevel_land::Vector{T} | "m" # Overland flow discharge [m3 s-1] @@ -31,13 +30,13 @@ end q_river::Vector{T} | "m3 s-1" end -function HydrometeoForcing(n) - hydrometeo_forcing = HydrometeoForcing(; - precipitation = fill(mv, n), - waterlevel_land = fill(mv, n), - q_land = fill(mv, n), - waterlevel_river = fill(mv, n), - q_river = fill(mv, n), - ) - return hydrometeo_forcing +"Initialize hydrological forcing" +function HydrologicalForcing( + n; + waterlevel_land::Vector{T} = fill(mv, n), + q_land::Vector{T} = fill(mv, n), + waterlevel_river::Vector{T} = fill(mv, n), + q_river::Vector{T} = fill(mv, n), +) where {T} + return HydrologicalForcing{T}(; waterlevel_land, q_land, waterlevel_river, q_river) end \ No newline at end of file diff --git a/src/sediment/erosion/overland_flow_erosion.jl b/src/sediment/erosion/overland_flow_erosion.jl index 1b82968d5..35167f161 100644 --- a/src/sediment/erosion/overland_flow_erosion.jl +++ b/src/sediment/erosion/overland_flow_erosion.jl @@ -1,7 +1,5 @@ abstract type AbstractOverlandFlowErosionModel{T} end -struct NoOverlandFlowErosionModel{T} <: AbstractOverlandFlowErosionModel{T} end - ## Overland flow structs and functions @get_units @grid_loc @with_kw struct OverlandFlowErosionVariables{T} # Total soil erosion from overland flow @@ -85,20 +83,13 @@ end function update_boundary_conditions!( model::OverlandFlowErosionAnswersModel, - hydrometeo_forcing::HydrometeoForcing, + hydrological_forcing::HydrologicalForcing, ) (; q) = model.boundary_conditions - (; q_land) = hydrometeo_forcing + (; q_land) = hydrological_forcing @. q = q_land end -function update_boundary_conditions!( - model::NoOverlandFlowErosionModel, - hydrometeo_forcing::HydrometeoForcing, -) - return nothing -end - function update!(model::OverlandFlowErosionAnswersModel, geometry::LandParameters, ts) (; q) = model.boundary_conditions (; usle_k, usle_c, answers_k) = model.parameters @@ -116,11 +107,4 @@ function update!(model::OverlandFlowErosionAnswersModel, geometry::LandParameter ts, ) end -end - -function update!(model::NoOverlandFlowErosionModel) - return nothing -end - -get_overland_flow_erosion(model::NoOverlandFlowErosionModel) = 0.0 -get_overland_flow_erosion(model::OverlandFlowErosionAnswersModel) = model.variables.amount \ No newline at end of file +end \ No newline at end of file diff --git a/src/sediment/erosion/rainfall_erosion.jl b/src/sediment/erosion/rainfall_erosion.jl index 66ec3479c..b246b2b19 100644 --- a/src/sediment/erosion/rainfall_erosion.jl +++ b/src/sediment/erosion/rainfall_erosion.jl @@ -1,18 +1,19 @@ abstract type AbstractRainfallErosionModel{T} end -struct NoRainfallErosionModel{T} <: AbstractRainfallErosionModel{T} end - ## General rainfall erosion functions and structs +"Struct for storing rainfall erosion model variables" @get_units @grid_loc @with_kw struct RainfallErosionModelVariables{T} # Total soil erosion from rainfall (splash) amount::Vector{T} | "t dt-1" end +"Initialize rainfall erosion model variables" function RainfallErosionModelVariables(n; amount::Vector{T} = fill(mv, n)) where {T} return RainfallErosionModelVariables{T}(; amount = amount) end ## EUROSEM specific structs and functions for rainfall erosion +"Struct for storing EUROSEM rainfall erosion model boundary conditions" @get_units @grid_loc @with_kw struct RainfallErosionEurosemBC{T} # precipitation precipitation::Vector{T} | "mm dt-1" @@ -22,6 +23,7 @@ end waterlevel::Vector{T} | "m" end +"Initialize EUROSEM rainfall erosion model boundary conditions" function RainfallErosionEurosemBC( n; precipitation::Vector{T} = fill(mv, n), @@ -35,6 +37,7 @@ function RainfallErosionEurosemBC( ) end +"Struct for storing EUROSEM rainfall erosion model parameters" @get_units @grid_loc @with_kw struct RainfallErosionEurosemParameters{T} # Soil detachability factor soil_detachability::Vector{T} | "g J-1" @@ -48,6 +51,7 @@ end soilcover_fraction::Vector{T} | "-" end +"Initialize EUROSEM rainfall erosion model parameters" function RainfallErosionEurosemParameters(nc, config, inds) soil_detachability = ncread( nc, @@ -99,12 +103,14 @@ function RainfallErosionEurosemParameters(nc, config, inds) return eurosem_parameters end +"EUROSEM rainfall erosion model" @with_kw struct RainfallErosionEurosemModel{T} <: AbstractRainfallErosionModel{T} boundary_conditions::RainfallErosionEurosemBC{T} parameters::RainfallErosionEurosemParameters{T} variables::RainfallErosionModelVariables{T} end +"Initialize EUROSEM rainfall erosion model" function RainfallErosionEurosemModel(nc, config, inds) n = length(inds) vars = RainfallErosionModelVariables(n) @@ -118,15 +124,18 @@ function RainfallErosionEurosemModel(nc, config, inds) return model end +"Update EUROSEM rainfall erosion model boundary conditions for a single timestep" function update_boundary_conditions!( model::RainfallErosionEurosemModel, - hydrometeo_forcing::HydrometeoForcing, + atmospheric_forcing::AtmosphericForcing, + hydrological_forcing::HydrologicalForcing, ) (; precipitation, waterlevel) = model.boundary_conditions - @. precipitation = hydrometeo_forcing.precipitation - @. waterlevel = model.boundary_conditions.waterlevel_land + @. precipitation = atmospheric_forcing.precipitation + @. waterlevel = hydrological_forcing.waterlevel_land end +"Update EUROSEM rainfall erosion model for a single timestep" function update!(model::RainfallErosionEurosemModel, geometry::LandParameters, ts) (; precipitation, interception, waterlevel) = model.boundary_conditions (; @@ -156,15 +165,18 @@ function update!(model::RainfallErosionEurosemModel, geometry::LandParameters, t end ### ANSWERS specific structs and functions for rainfall erosion +"Struct for storing ANSWERS rainfall erosion model boundary conditions" @get_units @grid_loc @with_kw struct RainfallErosionAnswersBC{T} # precipitation precipitation::Vector{T} | "mm dt-1" end +"Initialize ANSWERS rainfall erosion model boundary conditions" function RainfallErosionAnswersBC(n; precipitation::Vector{T} = fill(mv, n)) where {T} return RainfallErosionAnswersBC{T}(; precipitation = precipitation) end +"Struct for storing ANSWERS rainfall erosion model parameters" @get_units @grid_loc @with_kw struct RainfallErosionAnswersParameters{T} # Soil erodibility factor usle_k::Vector{T} | "-" @@ -172,6 +184,7 @@ end usle_c::Vector{T} | "-" end +"Initialize ANSWERS rainfall erosion model parameters" function RainfallErosionAnswersParameters(nc, config, inds) usle_k = ncread( nc, @@ -194,12 +207,14 @@ function RainfallErosionAnswersParameters(nc, config, inds) return answers_parameters end +"ANSWERS rainfall erosion model" @with_kw struct RainfallErosionAnswersModel{T} <: AbstractRainfallErosionModel{T} boundary_conditions::RainfallErosionAnswersBC{T} parameters::RainfallErosionAnswersParameters{T} variables::RainfallErosionModelVariables{T} end +"Initialize ANSWERS rainfall erosion model" function RainfallErosionAnswersModel(nc, config, inds) n = length(inds) bc = RainfallErosionAnswersBC(n) @@ -213,14 +228,17 @@ function RainfallErosionAnswersModel(nc, config, inds) return model end +"Update ANSWERS rainfall erosion model boundary conditions for a single timestep" function update_boundary_conditions!( model::RainfallErosionAnswersModel, - hydrometeo_forcing::HydrometeoForcing, + atmospheric_forcing::AtmosphericForcing, + hydrological_forcing::HydrologicalForcing, ) (; precipitation) = model.boundary_conditions - @. precipitation = hydrometeo_forcing.precipitation + @. precipitation = atmospheric_forcing.precipitation end +"Update ANSWERS rainfall erosion model for a single timestep" function update!(model::RainfallErosionAnswersModel, geometry::LandParameters, ts) (; precipitation) = model.boundary_conditions (; usle_k, usle_c) = model.parameters @@ -236,18 +254,4 @@ function update!(model::RainfallErosionAnswersModel, geometry::LandParameters, t ts, ) end -end - -function update_boundary_conditions!( - model::NoRainfallErosionModel, - hydrometeo_forcing::HydrometeoForcing, -) - return nothing -end - -function update!(model::NoRainfallErosionModel) - return nothing -end - -get_rainfall_erosion(model::NoRainfallErosionModel) = 0.0 -get_rainfall_erosion(model::AbstractRainfallErosionModel) = model.variables.amount \ No newline at end of file +end \ No newline at end of file diff --git a/src/sediment/erosion/river_erosion.jl b/src/sediment/erosion/river_erosion.jl index 655bed9b2..3031643f1 100644 --- a/src/sediment/erosion/river_erosion.jl +++ b/src/sediment/erosion/river_erosion.jl @@ -66,10 +66,10 @@ end function update_boundary_conditions!( model::RiverErosionJulianTorresModel, - hydrometeo_forcing::HydrometeoForcing, + hydrological_forcing::HydrologicalForcing, ) (; waterlevel) = model.boundary_conditions - (; waterlevel_river) = hydrometeo_forcing + (; waterlevel_river) = hydrological_forcing @. waterlevel = waterlevel_river end diff --git a/src/sediment/erosion/soil_erosion.jl b/src/sediment/erosion/soil_erosion.jl index 7692c1f4d..5c0d8ad5d 100644 --- a/src/sediment/erosion/soil_erosion.jl +++ b/src/sediment/erosion/soil_erosion.jl @@ -146,8 +146,8 @@ function update_boundary_conditions!( rainfall_erosion::AbstractRainfallErosionModel, overland_flow_erosion::AbstractOverlandFlowErosionModel, ) - re = get_rainfall_erosion(rainfall_erosion) - ole = get_overland_flow_erosion(overland_flow_erosion) + re = rainfall_erosion.variables.amount + ole = overland_flow_erosion.variables.amount (; rainfall_erosion, overland_flow_erosion) = model.boundary_conditions @. rainfall_erosion = re @. overland_flow_erosion = ole diff --git a/src/sediment/sediment_transport/river_transport.jl b/src/sediment/sediment_transport/river_transport.jl index 73abfc9a1..d71b54c3e 100644 --- a/src/sediment/sediment_transport/river_transport.jl +++ b/src/sediment/sediment_transport/river_transport.jl @@ -345,7 +345,7 @@ end function update_boundary_conditions!( model::SedimentRiverTransportModel, - hydrometeo_forcing::HydrometeoForcing, + hydrological_forcing::HydrologicalForcing, transport_capacity_model::AbstractTransportCapacityModel, to_river_model::SedimentToRiverDifferentiationModel, potential_erosion_model::AbstractRiverErosionModel, @@ -364,8 +364,8 @@ function update_boundary_conditions!( potential_erosion_river_bank, ) = model.boundary_conditions - # HydroMeteo forcing - (; q_river, waterlevel_river) = hydrometeo_forcing + # Hydrological forcing + (; q_river, waterlevel_river) = hydrological_forcing @. q = q_river @. waterlevel = waterlevel_river # Transport capacity @@ -935,12 +935,12 @@ end function update_boundary_conditions!( model::SedimentConcentrationsRiverModel, - hydrometeo_forcing::HydrometeoForcing, + hydrological_forcing::HydrologicalForcing, sediment_flux_model::AbstractSedimentRiverTransportModel, ) (; q, waterlevel, clay, silt, sand, sagg, lagg, gravel) = model.boundary_conditions - # Hydrometeo forcing - (; q_river, waterlevel_river) = hydrometeo_forcing + # Hydrological forcing + (; q_river, waterlevel_river) = hydrological_forcing @. q = q_river @. waterlevel = waterlevel_river # Sediment flux per particle diff --git a/src/sediment/sediment_transport/transport_capacity.jl b/src/sediment/sediment_transport/transport_capacity.jl index e5fdfb228..f8150772a 100644 --- a/src/sediment/sediment_transport/transport_capacity.jl +++ b/src/sediment/sediment_transport/transport_capacity.jl @@ -27,11 +27,11 @@ end function update_boundary_conditions!( model::AbstractTransportCapacityModel, - hydrometeo_forcing::HydrometeoForcing, + hydrological_forcing::HydrologicalForcing, model_type::Symbol, ) (; q, waterlevel) = model.boundary_conditions - (; q_land, waterlevel_land, q_river, waterlevel_river) = hydrometeo_forcing + (; q_land, waterlevel_land, q_river, waterlevel_river) = hydrological_forcing if model_type == :land @. q = q_land diff --git a/src/sediment_flux.jl b/src/sediment_flux.jl index 2560ebd23..616953405 100644 --- a/src/sediment_flux.jl +++ b/src/sediment_flux.jl @@ -1,6 +1,6 @@ ### Overland flow ### @get_units @grid_loc @with_kw struct OverlandFlowSediment{TT, SF, TR, T} - hydrometeo_forcing::HydrometeoForcing + hydrological_forcing::HydrologicalForcing geometry::LandParameters transport_capacity::TT sediment_flux::SF @@ -11,7 +11,7 @@ end function OverlandFlowSediment(nc, config, inds, waterbodies, rivers) n = length(inds) - hydrometeo_forcing = HydrometeoForcing(n) + hydrological_forcing = HydrologicalForcing(n) geometry = LandParameters(nc, config, inds) # Check what transport capacity equation will be used do_river = get(config.model, "runrivermodel", false)::Bool @@ -43,7 +43,7 @@ function OverlandFlowSediment(nc, config, inds, waterbodies, rivers) typeof(to_river_model), Float, }(; - hydrometeo_forcing = hydrometeo_forcing, + hydrological_forcing = hydrological_forcing, geometry = geometry, transport_capacity = transport_capacity_model, sediment_flux = sediment_flux_model, @@ -59,7 +59,7 @@ function update!(model::OverlandFlowSediment, erosion_model::SoilErosionModel, n ts = tosecond(dt) # Transport capacity - update_boundary_conditions!(model.transport_capacity, model.hydrometeo_forcing, :land) + update_boundary_conditions!(model.transport_capacity, model.hydrological_forcing, :land) update!(model.transport_capacity, model.geometry, model.waterbodies, model.rivers, ts) # Update boundary conditions before transport @@ -79,7 +79,7 @@ end ### River ### @get_units @grid_loc @with_kw struct RiverSediment{TTR, ER, SFR, CR, T} - hydrometeo_forcing::HydrometeoForcing + hydrological_forcing::HydrologicalForcing geometry::RiverParameters transport_capacity::TTR potential_erosion::ER @@ -90,7 +90,7 @@ end function RiverSediment(nc, config, inds, waterbodies) n = length(inds) - hydrometeo_forcing = HydrometeoForcing(n) + hydrological_forcing = HydrologicalForcing(n) geometry = RiverParameters(nc, config, inds) # Check what transport capacity equation will be used @@ -126,7 +126,7 @@ function RiverSediment(nc, config, inds, waterbodies) typeof(concentrations_model), Float, }(; - hydrometeo_forcing = hydrometeo_forcing, + hydrological_forcing = hydrological_forcing, geometry = geometry, transport_capacity = transport_capacity_model, potential_erosion = potential_erosion_model, @@ -148,17 +148,21 @@ function update!( ts = tosecond(dt) # Transport capacity - update_boundary_conditions!(model.transport_capacity, model.hydrometeo_forcing, :river) + update_boundary_conditions!( + model.transport_capacity, + model.hydrological_forcing, + :river, + ) update!(model.transport_capacity, model.geometry, ts) # Potential maximum river erosion - update_boundary_conditions!(model.potential_erosion, model.hydrometeo_forcing) + update_boundary_conditions!(model.potential_erosion, model.hydrological_forcing) update!(model.potential_erosion, model.geometry, ts) # River transport update_boundary_conditions!( model.sediment_flux, - model.hydrometeo_forcing, + model.hydrological_forcing, model.transport_capacity, to_river_model, model.potential_erosion, @@ -169,7 +173,7 @@ function update!( # Concentrations update_boundary_conditions!( model.concentrations, - model.hydrometeo_forcing, + model.hydrological_forcing, model.sediment_flux, ) update!(model.concentrations, model.geometry, ts) diff --git a/test/run_sediment.jl b/test/run_sediment.jl index 4d0336d41..a2c9f5805 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -11,8 +11,8 @@ Wflow.run_timestep!(model) @testset "first timestep sediment model (vertical)" begin eros = model.vertical - @test eros.hydrometeo_forcing.precipitation[1] ≈ 4.086122035980225f0 - @test eros.hydrometeo_forcing.q_land[1] ≈ 0.0f0 + @test eros.atmospheric_forcing.precipitation[1] ≈ 4.086122035980225f0 + @test eros.hydrological_forcing.q_land[1] ≈ 0.0f0 @test eros.overland_flow_erosion.parameters.usle_k[1] ≈ 0.026510488241910934f0 @test eros.overland_flow_erosion.parameters.usle_c[1] ≈ 0.014194443821907043f0 @test eros.overland_flow_erosion.parameters.answers_k[1] ≈ 0.9f0 @@ -57,8 +57,8 @@ end @test sum(land.to_river.variables.sand) ≈ 1289.4785484597958f0 @test mean(land.sediment_flux.variables.clay) ≈ 0.006578791733506439f0 - @test mean(river.hydrometeo_forcing.q_river) ≈ 0.6975180562953642f0 - @test river.hydrometeo_forcing.waterlevel_river[network.river.order[end]] ≈ + @test mean(river.hydrological_forcing.q_river) ≈ 0.6975180562953642f0 + @test river.hydrological_forcing.waterlevel_river[network.river.order[end]] ≈ 0.006103649735450745f0 @test mean(river.geometry.width) ≈ 22.628250814095523f0 diff --git a/test/runtests.jl b/test/runtests.jl index 05518f8b2..fa8511d6e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -78,20 +78,20 @@ include("testing_utils.jl") with_logger(NullLogger()) do ## run all tests @testset "Wflow.jl" begin - include("horizontal_process.jl") - include("io.jl") - include("vertical_process.jl") - include("reservoir_lake.jl") - include("run_sbm.jl") - include("run_sbm_piave.jl") - include("run_sbm_gwf_piave.jl") - include("run_sbm_gwf.jl") - include("run.jl") - include("groundwater.jl") - include("utils.jl") - include("bmi.jl") + #include("horizontal_process.jl") + #include("io.jl") + #include("vertical_process.jl") + #include("reservoir_lake.jl") + #include("run_sbm.jl") + #include("run_sbm_piave.jl") + #include("run_sbm_gwf_piave.jl") + #include("run_sbm_gwf.jl") + #include("run.jl") + #include("groundwater.jl") + #include("utils.jl") + #include("bmi.jl") include("run_sediment.jl") - include("subdomains.jl") + #include("subdomains.jl") Aqua.test_all(Wflow; ambiguities = false, persistent_tasks = false) end diff --git a/test/sediment_config.toml b/test/sediment_config.toml index 0efaf68ab..095752069 100644 --- a/test/sediment_config.toml +++ b/test/sediment_config.toml @@ -54,13 +54,13 @@ lake_areas = "wflow_lakeareas" # the external name mapping needs to be below together with the other mappings forcing = [ #"vertical.interception", - "vertical.hydrometeo_forcing.precipitation", - "vertical.hydrometeo_forcing.waterlevel_land", - "lateral.land.hydrometeo_forcing.waterlevel_land", - "vertical.hydrometeo_forcing.q_land", - "lateral.land.hydrometeo_forcing.q_land", - "lateral.river.hydrometeo_forcing.waterlevel_river", - "lateral.river.hydrometeo_forcing.q_river", + "vertical.atmospheric_forcing.precipitation", + "vertical.hydrological_forcing.waterlevel_land", + "lateral.land.hydrological_forcing.waterlevel_land", + "vertical.hydrological_forcing.q_land", + "lateral.land.hydrological_forcing.q_land", + "lateral.river.hydrological_forcing.waterlevel_river", + "lateral.river.hydrological_forcing.q_river", ] #cyclic = ["vertical.leaf_area_index"] @@ -73,8 +73,10 @@ forcing = [ #storage_wood = "Swood" ## other parameters -[input.vertical.hydrometeo_forcing] +[input.vertical.atmospheric_forcing] precipitation = "P" + +[input.vertical.hydrological_forcing] waterlevel_land = "levKinL" #interception = "int" q_land = "runL" @@ -103,7 +105,7 @@ sand_fraction = "fsand_soil" sagg_fraction = "fsagg_soil" lagg_fraction = "flagg_soil" -[input.lateral.land.hydrometeo_forcing] +[input.lateral.land.hydrological_forcing] waterlevel_land = "levKinL" q_land = "runL" @@ -118,7 +120,7 @@ dm_sand = "dm_sand" dm_sagg = "dm_sagg" dm_lagg = "dm_lagg" -[input.lateral.river.hydrometeo_forcing] +[input.lateral.river.hydrological_forcing] waterlevel_river = "h" q_river = "q" @@ -207,7 +209,7 @@ bed = "Bedconc" suspended = "SSconc" total = "Sedconc" -[output.lateral.river.hydrometeo_forcing] +[output.lateral.river.hydrological_forcing] waterlevel_river = "h_riv" [output.lateral.river.sediment_flux.variables] @@ -245,13 +247,13 @@ parameter = "vertical.overland_flow_erosion.variables.amount" coordinate.x = 6.931 coordinate.y = 48.085 header = "P" -parameter = "vertical.hydrometeo_forcing.precipitation" +parameter = "vertical.atmospheric_forcing.precipitation" [[csv.column]] coordinate.x = 6.931 coordinate.y = 48.085 header = "ql" -parameter = "vertical.hydrometeo_forcing.q_land" +parameter = "vertical.hydrological_forcing.q_land" # [[csv.column]] # coordinate.x = 6.931 From ab48727e7b0d528a4629538f9e7b15b3f7db9a48 Mon Sep 17 00:00:00 2001 From: hboisgon Date: Wed, 4 Dec 2024 15:19:43 +0800 Subject: [PATCH 36/42] rename nc, inds to dataset, indices --- src/erosion.jl | 15 +-- src/sediment/erosion/overland_flow_erosion.jl | 20 +-- src/sediment/erosion/rainfall_erosion.jl | 44 +++---- src/sediment/erosion/river_erosion.jl | 12 +- src/sediment/erosion/soil_erosion.jl | 28 ++--- .../sediment_transport/land_to_river.jl | 8 +- .../overland_flow_transport.jl | 8 +- .../sediment_transport/river_transport.jl | 114 +++++++++--------- .../sediment_transport/transport_capacity.jl | 86 ++++++------- src/sediment_flux.jl | 52 ++++---- src/sediment_model.jl | 77 ++++++------ 11 files changed, 236 insertions(+), 228 deletions(-) diff --git a/src/erosion.jl b/src/erosion.jl index 6fb17ea42..58e432a21 100644 --- a/src/erosion.jl +++ b/src/erosion.jl @@ -8,19 +8,19 @@ soil_erosion::SE end -function SoilLoss(nc, config, inds) - n = length(inds) +function SoilLoss(dataset, config, indices) + n = length(indices) atmospheric_forcing = AtmosphericForcing(n) hydrological_forcing = HydrologicalForcing(n) - geometry = LandParameters(nc, config, inds) + geometry = LandParameters(dataset, config, indices) # Rainfall erosion rainfallerosionmodel = get(config.model, "rainfall_erosion", "answers")::String if rainfallerosionmodel == "answers" - rainfall_erosion_model = RainfallErosionAnswersModel(nc, config, inds) + rainfall_erosion_model = RainfallErosionAnswersModel(dataset, config, indices) elseif rainfallerosionmodel == "eurosem" - rainfall_erosion_model = RainfallErosionEurosemModel(nc, config, inds) + rainfall_erosion_model = RainfallErosionEurosemModel(dataset, config, indices) else error("Unknown rainfall erosion model: $rainfallerosionmodel") end @@ -28,14 +28,15 @@ function SoilLoss(nc, config, inds) # Overland flow erosion overlandflowerosionmodel = get(config.model, "overland_flow_erosion", "answers")::String if overlandflowerosionmodel == "answers" - overland_flow_erosion_model = OverlandFlowErosionAnswersModel(nc, config, inds) + overland_flow_erosion_model = + OverlandFlowErosionAnswersModel(dataset, config, indices) else error("Unknown overland flow erosion model: $overlandflowerosionmodel") # overland_flow_erosion_model = NoOverlandFlowErosionModel() end # Total soil erosion and particle differentiation - soil_erosion_model = SoilErosionModel(nc, config, inds) + soil_erosion_model = SoilErosionModel(dataset, config, indices) soil_loss = SoilLoss{ typeof(rainfall_erosion_model), diff --git a/src/sediment/erosion/overland_flow_erosion.jl b/src/sediment/erosion/overland_flow_erosion.jl index 35167f161..6883b6569 100644 --- a/src/sediment/erosion/overland_flow_erosion.jl +++ b/src/sediment/erosion/overland_flow_erosion.jl @@ -29,28 +29,28 @@ end answers_k::Vector{T} | "-" end -function OverlandFlowErosionAnswersParameters(nc, config, inds) +function OverlandFlowErosionAnswersParameters(dataset, config, indices) usle_k = ncread( - nc, + dataset, config, "vertical.overland_flow_erosion.parameters.usle_k"; - sel = inds, + sel = indices, defaults = 0.1, type = Float, ) usle_c = ncread( - nc, + dataset, config, "vertical.overland_flow_erosion.parameters.usle_c"; - sel = inds, + sel = indices, defaults = 0.01, type = Float, ) answers_k = ncread( - nc, + dataset, config, "vertical.overland_flow_erosion.parameters.answers_k"; - sel = inds, + sel = indices, defaults = 0.9, type = Float, ) @@ -68,10 +68,10 @@ end variables::OverlandFlowErosionVariables{T} end -function OverlandFlowErosionAnswersModel(nc, config, inds) - n = length(inds) +function OverlandFlowErosionAnswersModel(dataset, config, indices) + n = length(indices) vars = OverlandFlowErosionVariables(n) - params = OverlandFlowErosionAnswersParameters(nc, config, inds) + params = OverlandFlowErosionAnswersParameters(dataset, config, indices) bc = OverlandFlowErosionBC(n) model = OverlandFlowErosionAnswersModel(; boundary_conditions = bc, diff --git a/src/sediment/erosion/rainfall_erosion.jl b/src/sediment/erosion/rainfall_erosion.jl index b246b2b19..b87261792 100644 --- a/src/sediment/erosion/rainfall_erosion.jl +++ b/src/sediment/erosion/rainfall_erosion.jl @@ -52,44 +52,44 @@ end end "Initialize EUROSEM rainfall erosion model parameters" -function RainfallErosionEurosemParameters(nc, config, inds) +function RainfallErosionEurosemParameters(dataset, config, indices) soil_detachability = ncread( - nc, + dataset, config, "vertical.rainfall_erosion.parameters.soil_detachability"; - sel = inds, + sel = indices, defaults = 0.6, type = Float, ) eurosem_exponent = ncread( - nc, + dataset, config, "vertical.rainfall_erosion.parameters.eurosem_exponent"; - sel = inds, + sel = indices, defaults = 2.0, type = Float, ) canopyheight = ncread( - nc, + dataset, config, "vertical.rainfall_erosion.parameters.canopyheight"; - sel = inds, + sel = indices, defaults = 0.5, type = Float, ) canopygapfraction = ncread( - nc, + dataset, config, "vertical.rainfall_erosion.parameters.canopygapfraction"; - sel = inds, + sel = indices, defaults = 0.1, type = Float, ) soilcover_fraction = ncread( - nc, + dataset, config, "vertical.rainfall_erosion.parameters.pathfrac"; - sel = inds, + sel = indices, defaults = 0.01, type = Float, ) @@ -111,10 +111,10 @@ end end "Initialize EUROSEM rainfall erosion model" -function RainfallErosionEurosemModel(nc, config, inds) - n = length(inds) +function RainfallErosionEurosemModel(dataset, config, indices) + n = length(indices) vars = RainfallErosionModelVariables(n) - params = RainfallErosionEurosemParameters(nc, config, inds) + params = RainfallErosionEurosemParameters(dataset, config, indices) bc = RainfallErosionEurosemBC(n) model = RainfallErosionEurosemModel(; boundary_conditions = bc, @@ -185,20 +185,20 @@ end end "Initialize ANSWERS rainfall erosion model parameters" -function RainfallErosionAnswersParameters(nc, config, inds) +function RainfallErosionAnswersParameters(dataset, config, indices) usle_k = ncread( - nc, + dataset, config, "vertical.rainfall_erosion.parameters.usle_k"; - sel = inds, + sel = indices, defaults = 0.1, type = Float, ) usle_c = ncread( - nc, + dataset, config, "vertical.rainfall_erosion.parameters.usle_c"; - sel = inds, + sel = indices, defaults = 0.01, type = Float, ) @@ -215,11 +215,11 @@ end end "Initialize ANSWERS rainfall erosion model" -function RainfallErosionAnswersModel(nc, config, inds) - n = length(inds) +function RainfallErosionAnswersModel(dataset, config, indices) + n = length(indices) bc = RainfallErosionAnswersBC(n) vars = RainfallErosionModelVariables(n) - params = RainfallErosionAnswersParameters(nc, config, inds) + params = RainfallErosionAnswersParameters(dataset, config, indices) model = RainfallErosionAnswersModel(; boundary_conditions = bc, parameters = params, diff --git a/src/sediment/erosion/river_erosion.jl b/src/sediment/erosion/river_erosion.jl index 3031643f1..37d2c97ef 100644 --- a/src/sediment/erosion/river_erosion.jl +++ b/src/sediment/erosion/river_erosion.jl @@ -37,12 +37,12 @@ end variables::RiverErosionModelVariables{T} end -function RiverErosionParameters(nc, config, inds) +function RiverErosionParameters(dataset, config, indices) d50 = ncread( - nc, + dataset, config, "lateral.river.potential_erosion.parameters.d50"; - sel = inds, + sel = indices, defaults = 0.1, type = Float, ) @@ -51,10 +51,10 @@ function RiverErosionParameters(nc, config, inds) return river_parameters end -function RiverErosionJulianTorresModel(nc, config, inds) - n = length(inds) +function RiverErosionJulianTorresModel(dataset, config, indices) + n = length(indices) vars = RiverErosionModelVariables(n) - params = RiverErosionParameters(nc, config, inds) + params = RiverErosionParameters(dataset, config, indices) bc = RiverErosionBC(n) model = RiverErosionJulianTorresModel(; boundary_conditions = bc, diff --git a/src/sediment/erosion/soil_erosion.jl b/src/sediment/erosion/soil_erosion.jl index 5c0d8ad5d..4e9f7b575 100644 --- a/src/sediment/erosion/soil_erosion.jl +++ b/src/sediment/erosion/soil_erosion.jl @@ -67,44 +67,44 @@ end lagg_fraction::Vector{T} | "-" end -function SoilErosionParameters(nc, config, inds) +function SoilErosionParameters(dataset, config, indices) clay_fraction = ncread( - nc, + dataset, config, "vertical.soil_erosion.parameters.clay_fraction"; - sel = inds, + sel = indices, defaults = 0.4, type = Float, ) silt_fraction = ncread( - nc, + dataset, config, "vertical.soil_erosion.parameters.silt_fraction"; - sel = inds, + sel = indices, defaults = 0.3, type = Float, ) sand_fraction = ncread( - nc, + dataset, config, "vertical.soil_erosion.parameters.sand_fraction"; - sel = inds, + sel = indices, defaults = 0.3, type = Float, ) sagg_fraction = ncread( - nc, + dataset, config, "vertical.soil_erosion.parameters.sagg_fraction"; - sel = inds, + sel = indices, defaults = 0.0, type = Float, ) lagg_fraction = ncread( - nc, + dataset, config, "vertical.soil_erosion.parameters.lagg_fraction"; - sel = inds, + sel = indices, defaults = 0.0, type = Float, ) @@ -131,10 +131,10 @@ end variables::SoilErosionModelVariables{T} end -function SoilErosionModel(nc, config, inds) - n = length(inds) +function SoilErosionModel(dataset, config, indices) + n = length(indices) vars = SoilErosionModelVariables(n) - params = SoilErosionParameters(nc, config, inds) + params = SoilErosionParameters(dataset, config, indices) bc = SoilErosionBC(n) model = SoilErosionModel(; boundary_conditions = bc, parameters = params, variables = vars) diff --git a/src/sediment/sediment_transport/land_to_river.jl b/src/sediment/sediment_transport/land_to_river.jl index 8973a01b6..364569e3a 100644 --- a/src/sediment/sediment_transport/land_to_river.jl +++ b/src/sediment/sediment_transport/land_to_river.jl @@ -24,8 +24,8 @@ end variables::SedimentToRiverVariables{T} end -function SedimentToRiverModel(inds) - n = length(inds) +function SedimentToRiverModel(indices) + n = length(indices) vars = SedimentToRiverVariables(n) bc = SedimentToRiverBC(n) model = SedimentToRiverModel(; boundary_conditions = bc, variables = vars) @@ -118,8 +118,8 @@ end variables::SedimentToRiverDifferentiationVariables{T} end -function SedimentToRiverDifferentiationModel(inds) - n = length(inds) +function SedimentToRiverDifferentiationModel(indices) + n = length(indices) vars = SedimentToRiverDifferentiationVariables(n) bc = SedimentToRiverDifferentiationBC(n) model = diff --git a/src/sediment/sediment_transport/overland_flow_transport.jl b/src/sediment/sediment_transport/overland_flow_transport.jl index 162415236..b4d0fd44b 100644 --- a/src/sediment/sediment_transport/overland_flow_transport.jl +++ b/src/sediment/sediment_transport/overland_flow_transport.jl @@ -38,8 +38,8 @@ end variables::SedimentLandTransportVariables{T} end -function SedimentLandTransportModel(inds) - n = length(inds) +function SedimentLandTransportModel(indices) + n = length(indices) vars = SedimentLandTransportVariables(n) bc = SedimentLandTransportBC(n) model = SedimentLandTransportModel(; boundary_conditions = bc, variables = vars) @@ -182,8 +182,8 @@ end variables::SedimentLandTransportDifferentiationVariables{T} end -function SedimentLandTransportDifferentiationModel(inds) - n = length(inds) +function SedimentLandTransportDifferentiationModel(indices) + n = length(indices) vars = SedimentLandTransportDifferentiationVariables(n) bc = SedimentLandTransportDifferentiationBC(n) model = SedimentLandTransportDifferentiationModel(; diff --git a/src/sediment/sediment_transport/river_transport.jl b/src/sediment/sediment_transport/river_transport.jl index d71b54c3e..43ed2dd0f 100644 --- a/src/sediment/sediment_transport/river_transport.jl +++ b/src/sediment/sediment_transport/river_transport.jl @@ -154,37 +154,37 @@ end waterbodies_trapping_efficiency::Vector{T} | "-" end -function SedimentRiverTransportParameters(nc, config, inds) - n = length(inds) +function SedimentRiverTransportParameters(dataset, config, indices) + n = length(indices) clay_fraction = ncread( - nc, + dataset, config, "lateral.river.sediment_flux.parameters.clay_fraction"; - sel = inds, + sel = indices, defaults = 0.15, type = Float, ) silt_fraction = ncread( - nc, + dataset, config, "lateral.river.sediment_flux.parameters.silt_fraction"; - sel = inds, + sel = indices, defaults = 0.65, type = Float, ) sand_fraction = ncread( - nc, + dataset, config, "lateral.river.sediment_flux.parameters.sand_fraction"; - sel = inds, + sel = indices, defaults = 0.15, type = Float, ) gravel_fraction = ncread( - nc, + dataset, config, "lateral.river.sediment_flux.parameters.sagg_fraction"; - sel = inds, + sel = indices, defaults = 0.05, type = Float, ) @@ -194,50 +194,50 @@ function SedimentRiverTransportParameters(nc, config, inds) error("Particle fractions in the river bed must sum to 1") end dm_clay = ncread( - nc, + dataset, config, "lateral.river.sediment_flux.parameters.dm_clay"; - sel = inds, + sel = indices, defaults = 2.0, type = Float, ) dm_silt = ncread( - nc, + dataset, config, "lateral.river.sediment_flux.parameters.dm_silt"; - sel = inds, + sel = indices, defaults = 10.0, type = Float, ) dm_sand = ncread( - nc, + dataset, config, "lateral.river.sediment_flux.parameters.dm_sand"; - sel = inds, + sel = indices, defaults = 200.0, type = Float, ) dm_sagg = ncread( - nc, + dataset, config, "lateral.river.sediment_flux.parameters.dm_sagg"; - sel = inds, + sel = indices, defaults = 30.0, type = Float, ) dm_lagg = ncread( - nc, + dataset, config, "lateral.river.sediment_flux.parameters.dm_lagg"; - sel = inds, + sel = indices, defaults = 500.0, type = Float, ) dm_gravel = ncread( - nc, + dataset, config, "lateral.river.sediment_flux.parameters.dm_gravel"; - sel = inds, + sel = indices, defaults = 2000.0, type = Float, ) @@ -250,29 +250,29 @@ function SedimentRiverTransportParameters(nc, config, inds) if do_reservoirs reslocs = ncread( - nc, + dataset, config, "lateral.river.sediment_flux.parameters.reslocs"; optional = false, - sel = inds, + sel = indices, type = Float, fill = 0, ) resarea = ncread( - nc, + dataset, config, "lateral.river.sediment_flux.parameters.resarea"; optional = false, - sel = inds, + sel = indices, type = Float, fill = 0.0, ) restrapefficiency = ncread( - nc, + dataset, config, "lateral.river.sediment_flux.parameters.restrapeff"; optional = false, - sel = inds, + sel = indices, type = Float, defaults = 1.0, fill = 0.0, @@ -284,20 +284,20 @@ function SedimentRiverTransportParameters(nc, config, inds) if do_lakes lakelocs = ncread( - nc, + dataset, config, "lateral.river.sediment_flux.parameters.lakelocs"; optional = false, - sel = inds, + sel = indices, type = Float, fill = 0, ) lakearea = ncread( - nc, + dataset, config, "lateral.river.sediment_flux.parameters.lakearea"; optional = false, - sel = inds, + sel = indices, type = Float, fill = 0.0, ) @@ -330,10 +330,10 @@ end variables::SedimentRiverTransportVariables{T} end -function SedimentRiverTransportModel(nc, config, inds) - n = length(inds) +function SedimentRiverTransportModel(dataset, config, indices) + n = length(indices) vars = SedimentRiverTransportVariables(n) - params = SedimentRiverTransportParameters(nc, config, inds) + params = SedimentRiverTransportParameters(dataset, config, indices) bc = SedimentRiverTransportBC(n) model = SedimentRiverTransportModel(; boundary_conditions = bc, @@ -349,7 +349,7 @@ function update_boundary_conditions!( transport_capacity_model::AbstractTransportCapacityModel, to_river_model::SedimentToRiverDifferentiationModel, potential_erosion_model::AbstractRiverErosionModel, - inds_riv, + indices_riv, ) (; waterlevel, @@ -372,11 +372,11 @@ function update_boundary_conditions!( @. transport_capacity = transport_capacity_model.variables.amount # Input from soil erosion (; clay, silt, sand, sagg, lagg) = to_river_model.variables - @. erosion_land_clay = clay[inds_riv] - @. erosion_land_silt = silt[inds_riv] - @. erosion_land_sand = sand[inds_riv] - @. erosion_land_sagg = sagg[inds_riv] - @. erosion_land_lagg = lagg[inds_riv] + @. erosion_land_clay = clay[indices_riv] + @. erosion_land_silt = silt[indices_riv] + @. erosion_land_sand = sand[indices_riv] + @. erosion_land_sagg = sagg[indices_riv] + @. erosion_land_lagg = lagg[indices_riv] # Maximum direct river bed/bank erosion @. potential_erosion_river_bed = potential_erosion_model.variables.bed @. potential_erosion_river_bank = potential_erosion_model.variables.bank @@ -852,52 +852,52 @@ end dm_gravel::Vector{T} | "µm" end -function SedimentConcentrationsRiverParameters(nc, config, inds) +function SedimentConcentrationsRiverParameters(dataset, config, indices) dm_clay = ncread( - nc, + dataset, config, "lateral.river.concentrations.parameters.dm_clay"; - sel = inds, + sel = indices, defaults = 2.0, type = Float, ) dm_silt = ncread( - nc, + dataset, config, "lateral.river.concentrations.parameters.dm_silt"; - sel = inds, + sel = indices, defaults = 10.0, type = Float, ) dm_sand = ncread( - nc, + dataset, config, "lateral.river.concentrations.parameters.dm_sand"; - sel = inds, + sel = indices, defaults = 200.0, type = Float, ) dm_sagg = ncread( - nc, + dataset, config, "lateral.river.concentrations.parameters.dm_sagg"; - sel = inds, + sel = indices, defaults = 30.0, type = Float, ) dm_lagg = ncread( - nc, + dataset, config, "lateral.river.concentrations.parameters.dm_lagg"; - sel = inds, + sel = indices, defaults = 500.0, type = Float, ) dm_gravel = ncread( - nc, + dataset, config, "lateral.river.concentrations.parameters.dm_gravel"; - sel = inds, + sel = indices, defaults = 2000.0, type = Float, ) @@ -920,10 +920,10 @@ end variables::SedimentConcentrationsRiverVariables{T} end -function SedimentConcentrationsRiverModel(nc, config, inds) - n = length(inds) +function SedimentConcentrationsRiverModel(dataset, config, indices) + n = length(indices) vars = SedimentConcentrationsRiverVariables(n) - params = SedimentConcentrationsRiverParameters(nc, config, inds) + params = SedimentConcentrationsRiverParameters(dataset, config, indices) bc = SedimentConcentrationsRiverBC(n) model = SedimentConcentrationsRiverModel(; boundary_conditions = bc, diff --git a/src/sediment/sediment_transport/transport_capacity.jl b/src/sediment/sediment_transport/transport_capacity.jl index f8150772a..eeda04164 100644 --- a/src/sediment/sediment_transport/transport_capacity.jl +++ b/src/sediment/sediment_transport/transport_capacity.jl @@ -56,9 +56,9 @@ end n_govers::Vector{T} | "-" end -function TransportCapacityGoversParameters(nc, config, inds) +function TransportCapacityGoversParameters(dataset, config, inds) slope = ncread( - nc, + dataset, config, "lateral.land.transport_capacity.parameters.slope"; sel = inds, @@ -66,7 +66,7 @@ function TransportCapacityGoversParameters(nc, config, inds) type = Float, ) density = ncread( - nc, + dataset, config, "lateral.land.transport_capacity.parameters.density"; sel = inds, @@ -74,7 +74,7 @@ function TransportCapacityGoversParameters(nc, config, inds) type = Float, ) c_govers = ncread( - nc, + dataset, config, "lateral.land.transport_capacity.parameters.c_govers"; sel = inds, @@ -82,7 +82,7 @@ function TransportCapacityGoversParameters(nc, config, inds) type = Float, ) n_govers = ncread( - nc, + dataset, config, "lateral.land.transport_capacity.parameters.n_govers"; sel = inds, @@ -105,10 +105,10 @@ end variables::TransportCapacityModelVariables{T} end -function TransportCapacityGoversModel(nc, config, inds) +function TransportCapacityGoversModel(dataset, config, inds) n = length(inds) vars = TransportCapacityModelVariables(n) - params = TransportCapacityGoversParameters(nc, config, inds) + params = TransportCapacityGoversParameters(dataset, config, inds) bc = TransportCapacityBC(n) model = TransportCapacityGoversModel(; boundary_conditions = bc, @@ -150,9 +150,9 @@ end d50::Vector{T} | "mm" end -function TransportCapacityYalinParameters(nc, config, inds) +function TransportCapacityYalinParameters(dataset, config, inds) slope = ncread( - nc, + dataset, config, "lateral.land.transport_capacity.parameters.slope"; sel = inds, @@ -160,7 +160,7 @@ function TransportCapacityYalinParameters(nc, config, inds) type = Float, ) density = ncread( - nc, + dataset, config, "lateral.land.transport_capacity.parameters.density"; sel = inds, @@ -168,7 +168,7 @@ function TransportCapacityYalinParameters(nc, config, inds) type = Float, ) d50 = ncread( - nc, + dataset, config, "lateral.land.transport_capacity.parameters.d50"; sel = inds, @@ -187,10 +187,10 @@ end variables::TransportCapacityModelVariables{T} end -function TransportCapacityYalinModel(nc, config, inds) +function TransportCapacityYalinModel(dataset, config, inds) n = length(inds) vars = TransportCapacityModelVariables(n) - params = TransportCapacityYalinParameters(nc, config, inds) + params = TransportCapacityYalinParameters(dataset, config, inds) bc = TransportCapacityBC(n) model = TransportCapacityYalinModel(; boundary_conditions = bc, @@ -272,9 +272,9 @@ end dm_lagg::Vector{T} | "µm" end -function TransportCapacityYalinDifferentiationParameters(nc, config, inds) +function TransportCapacityYalinDifferentiationParameters(dataset, config, inds) density = ncread( - nc, + dataset, config, "lateral.land.transport_capacity.parameters.density"; sel = inds, @@ -282,7 +282,7 @@ function TransportCapacityYalinDifferentiationParameters(nc, config, inds) type = Float, ) dm_clay = ncread( - nc, + dataset, config, "lateral.land.transport_capacity.parameters.dm_clay"; sel = inds, @@ -290,7 +290,7 @@ function TransportCapacityYalinDifferentiationParameters(nc, config, inds) type = Float, ) dm_silt = ncread( - nc, + dataset, config, "lateral.land.transport_capacity.parameters.dm_silt"; sel = inds, @@ -298,7 +298,7 @@ function TransportCapacityYalinDifferentiationParameters(nc, config, inds) type = Float, ) dm_sand = ncread( - nc, + dataset, config, "lateral.land.transport_capacity.parameters.dm_sand"; sel = inds, @@ -306,7 +306,7 @@ function TransportCapacityYalinDifferentiationParameters(nc, config, inds) type = Float, ) dm_sagg = ncread( - nc, + dataset, config, "lateral.land.transport_capacity.parameters.dm_sagg"; sel = inds, @@ -314,7 +314,7 @@ function TransportCapacityYalinDifferentiationParameters(nc, config, inds) type = Float, ) dm_lagg = ncread( - nc, + dataset, config, "lateral.land.transport_capacity.parameters.dm_lagg"; sel = inds, @@ -340,10 +340,10 @@ end variables::TransportCapacityYalinDifferentiationModelVariables{T} end -function TransportCapacityYalinDifferentiationModel(nc, config, inds) +function TransportCapacityYalinDifferentiationModel(dataset, config, inds) n = length(inds) vars = TransportCapacityYalinDifferentiationModelVariables(n) - params = TransportCapacityYalinDifferentiationParameters(nc, config, inds) + params = TransportCapacityYalinDifferentiationParameters(dataset, config, inds) bc = TransportCapacityBC(n) model = TransportCapacityYalinDifferentiationModel(; boundary_conditions = bc, @@ -448,9 +448,9 @@ end d50::Vector{T} | "mm" end -function TransportCapacityRiverParameters(nc, config, inds) +function TransportCapacityRiverParameters(dataset, config, inds) density = ncread( - nc, + dataset, config, "lateral.river.transport_capacity.parameters.density"; sel = inds, @@ -458,7 +458,7 @@ function TransportCapacityRiverParameters(nc, config, inds) type = Float, ) d50 = ncread( - nc, + dataset, config, "lateral.river.transport_capacity.parameters.d50"; sel = inds, @@ -478,9 +478,9 @@ end e_bagnold::Vector{T} | "-" end -function TransportCapacityBagnoldParameters(nc, config, inds) +function TransportCapacityBagnoldParameters(dataset, config, inds) c_bagnold = ncread( - nc, + dataset, config, "lateral.river.transport_capacity.parameters.c_bagnold"; sel = inds, @@ -488,7 +488,7 @@ function TransportCapacityBagnoldParameters(nc, config, inds) type = Float, ) e_bagnold = ncread( - nc, + dataset, config, "lateral.river.transport_capacity.parameters.e_bagnold"; sel = inds, @@ -507,10 +507,10 @@ end variables::TransportCapacityModelVariables{T} end -function TransportCapacityBagnoldModel(nc, config, inds) +function TransportCapacityBagnoldModel(dataset, config, inds) n = length(inds) vars = TransportCapacityModelVariables(n) - params = TransportCapacityBagnoldParameters(nc, config, inds) + params = TransportCapacityBagnoldParameters(dataset, config, inds) bc = TransportCapacityBC(n) model = TransportCapacityBagnoldModel(; boundary_conditions = bc, @@ -548,10 +548,10 @@ end variables::TransportCapacityModelVariables{T} end -function TransportCapacityEngelundModel(nc, config, inds) +function TransportCapacityEngelundModel(dataset, config, inds) n = length(inds) vars = TransportCapacityModelVariables(n) - params = TransportCapacityRiverParameters(nc, config, inds) + params = TransportCapacityRiverParameters(dataset, config, inds) bc = TransportCapacityBC(n) model = TransportCapacityEngelundModel(; boundary_conditions = bc, @@ -593,9 +593,9 @@ end d_kodatie::Vector{T} | "-" end -function TransportCapacityKodatieParameters(nc, config, inds) +function TransportCapacityKodatieParameters(dataset, config, inds) a_kodatie = ncread( - nc, + dataset, config, "lateral.river.transport_capacity.parameters.a_kodatie"; sel = inds, @@ -603,7 +603,7 @@ function TransportCapacityKodatieParameters(nc, config, inds) type = Float, ) b_kodatie = ncread( - nc, + dataset, config, "lateral.river.transport_capacity.parameters.b_kodatie"; sel = inds, @@ -611,7 +611,7 @@ function TransportCapacityKodatieParameters(nc, config, inds) type = Float, ) c_kodatie = ncread( - nc, + dataset, config, "lateral.river.transport_capacity.parameters.c_kodatie"; sel = inds, @@ -619,7 +619,7 @@ function TransportCapacityKodatieParameters(nc, config, inds) type = Float, ) d_kodatie = ncread( - nc, + dataset, config, "lateral.river.transport_capacity.parameters.d_kodatie"; sel = inds, @@ -642,10 +642,10 @@ end variables::TransportCapacityModelVariables{T} end -function TransportCapacityKodatieModel(nc, config, inds) +function TransportCapacityKodatieModel(dataset, config, inds) n = length(inds) vars = TransportCapacityModelVariables(n) - params = TransportCapacityKodatieParameters(nc, config, inds) + params = TransportCapacityKodatieParameters(dataset, config, inds) bc = TransportCapacityBC(n) model = TransportCapacityKodatieModel(; boundary_conditions = bc, @@ -684,10 +684,10 @@ end variables::TransportCapacityModelVariables{T} end -function TransportCapacityYangModel(nc, config, inds) +function TransportCapacityYangModel(dataset, config, inds) n = length(inds) vars = TransportCapacityModelVariables(n) - params = TransportCapacityRiverParameters(nc, config, inds) + params = TransportCapacityRiverParameters(dataset, config, inds) bc = TransportCapacityBC(n) model = TransportCapacityYangModel(; boundary_conditions = bc, @@ -724,10 +724,10 @@ end variables::TransportCapacityModelVariables{T} end -function TransportCapacityMolinasModel(nc, config, inds) +function TransportCapacityMolinasModel(dataset, config, inds) n = length(inds) vars = TransportCapacityModelVariables(n) - params = TransportCapacityRiverParameters(nc, config, inds) + params = TransportCapacityRiverParameters(dataset, config, inds) bc = TransportCapacityBC(n) model = TransportCapacityMolinasModel(; boundary_conditions = bc, diff --git a/src/sediment_flux.jl b/src/sediment_flux.jl index 616953405..87aa5e874 100644 --- a/src/sediment_flux.jl +++ b/src/sediment_flux.jl @@ -1,5 +1,5 @@ ### Overland flow ### -@get_units @grid_loc @with_kw struct OverlandFlowSediment{TT, SF, TR, T} +@get_units @grid_loc @with_kw struct OverlandFlowSediment{TT, SF, TR} hydrological_forcing::HydrologicalForcing geometry::LandParameters transport_capacity::TT @@ -9,10 +9,10 @@ rivers::Vector{Bool} | "-" end -function OverlandFlowSediment(nc, config, inds, waterbodies, rivers) - n = length(inds) +function OverlandFlowSediment(dataset, config, indices, waterbodies, rivers) + n = length(indices) hydrological_forcing = HydrologicalForcing(n) - geometry = LandParameters(nc, config, inds) + geometry = LandParameters(dataset, config, indices) # Check what transport capacity equation will be used do_river = get(config.model, "runrivermodel", false)::Bool # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] @@ -20,28 +20,27 @@ function OverlandFlowSediment(nc, config, inds, waterbodies, rivers) if do_river || landtransportmethod == "yalinpart" transport_capacity_model = - TransportCapacityYalinDifferentiationModel(nc, config, inds) + TransportCapacityYalinDifferentiationModel(dataset, config, indices) elseif landtransportmethod == "govers" - transport_capacity_model = TransportCapacityGoversModel(nc, config, inds) + transport_capacity_model = TransportCapacityGoversModel(dataset, config, indices) elseif landtransportmethod == "yalin" - transport_capacity_model = TransportCapacityYalinModel(nc, config, inds) + transport_capacity_model = TransportCapacityYalinModel(dataset, config, indices) else error("Unknown land transport method: $landtransportmethod") end if do_river || landtransportmethod == "yalinpart" - sediment_flux_model = SedimentLandTransportDifferentiationModel(inds) - to_river_model = SedimentToRiverDifferentiationModel(inds) + sediment_flux_model = SedimentLandTransportDifferentiationModel(indices) + to_river_model = SedimentToRiverDifferentiationModel(indices) else - sediment_flux_model = SedimentLandTransportModel(inds) - to_river_model = SedimentToRiverModel(inds) + sediment_flux_model = SedimentLandTransportModel(indices) + to_river_model = SedimentToRiverModel(indices) end overland_flow_sediment = OverlandFlowSediment{ typeof(transport_capacity_model), typeof(sediment_flux_model), typeof(to_river_model), - Float, }(; hydrological_forcing = hydrological_forcing, geometry = geometry, @@ -78,7 +77,7 @@ function update!(model::OverlandFlowSediment, erosion_model::SoilErosionModel, n end ### River ### -@get_units @grid_loc @with_kw struct RiverSediment{TTR, ER, SFR, CR, T} +@get_units @grid_loc @with_kw struct RiverSediment{TTR, ER, SFR, CR} hydrological_forcing::HydrologicalForcing geometry::RiverParameters transport_capacity::TTR @@ -88,43 +87,42 @@ end waterbodies::Vector{Bool} | "-" end -function RiverSediment(nc, config, inds, waterbodies) - n = length(inds) +function RiverSediment(dataset, config, indices, waterbodies) + n = length(indices) hydrological_forcing = HydrologicalForcing(n) - geometry = RiverParameters(nc, config, inds) + geometry = RiverParameters(dataset, config, indices) # Check what transport capacity equation will be used # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] transport_method = get(config.model, "rivtransportmethod", "bagnold")::String if transport_method == "bagnold" - transport_capacity_model = TransportCapacityBagnoldModel(nc, config, inds) + transport_capacity_model = TransportCapacityBagnoldModel(dataset, config, indices) elseif transport_method == "engelund" - transport_capacity_model = TransportCapacityEngelundModel(nc, config, inds) + transport_capacity_model = TransportCapacityEngelundModel(dataset, config, indices) elseif transport_method == "yang" - transport_capacity_model = TransportCapacityYangModel(nc, config, inds) + transport_capacity_model = TransportCapacityYangModel(dataset, config, indices) elseif transport_method == "kodatie" - transport_capacity_model = TransportCapacityKodatieModel(nc, config, inds) + transport_capacity_model = TransportCapacityKodatieModel(dataset, config, indices) elseif transport_method == "molinas" - transport_capacity_model = TransportCapacityMolinasModel(nc, config, inds) + transport_capacity_model = TransportCapacityMolinasModel(dataset, config, indices) else error("Unknown river transport method: $transport_method") end # Potential river erosion - potential_erosion_model = RiverErosionJulianTorresModel(nc, config, inds) + potential_erosion_model = RiverErosionJulianTorresModel(dataset, config, indices) # Sediment flux in river / mass balance - sediment_flux_model = SedimentRiverTransportModel(nc, config, inds) + sediment_flux_model = SedimentRiverTransportModel(dataset, config, indices) # Concentrations - concentrations_model = SedimentConcentrationsRiverModel(nc, config, inds) + concentrations_model = SedimentConcentrationsRiverModel(dataset, config, indices) river_sediment = RiverSediment{ typeof(transport_capacity_model), typeof(potential_erosion_model), typeof(sediment_flux_model), typeof(concentrations_model), - Float, }(; hydrological_forcing = hydrological_forcing, geometry = geometry, @@ -141,7 +139,7 @@ function update!( model::RiverSediment, to_river_model::SedimentToRiverDifferentiationModel, network, - inds_riv, + indices_river, dt, ) # Convert dt to integer @@ -166,7 +164,7 @@ function update!( model.transport_capacity, to_river_model, model.potential_erosion, - inds_riv, + indices_river, ) update!(model.sediment_flux, network, model.geometry, model.waterbodies, ts) diff --git a/src/sediment_model.jl b/src/sediment_model.jl index 084846763..a12fbaab7 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -13,22 +13,29 @@ function initialize_sediment_model(config::Config) reader = prepare_reader(config) clock = Clock(config, reader) - nc = NCDataset(static_path) + dataset = NCDataset(static_path) - subcatch_2d = ncread(nc, config, "subcatchment"; optional = false, allow_missing = true) + subcatch_2d = + ncread(dataset, config, "subcatchment"; optional = false, allow_missing = true) # indices based on catchment - inds, rev_inds = active_indices(subcatch_2d, missing) - n = length(inds) + indices, rev_indices = active_indices(subcatch_2d, missing) + n = length(indices) - river_2d = - ncread(nc, config, "river_location"; optional = false, type = Bool, fill = false) - river = river_2d[inds] + river_2d = ncread( + dataset, + config, + "river_location"; + optional = false, + type = Bool, + fill = false, + ) + river = river_2d[indices] # Needed to update the forcing reservoir = () lake = () - soilloss = SoilLoss(nc, config, inds) + soilloss = SoilLoss(dataset, config, indices) # Get waterbodies mask do_reservoirs = get(config.model, "doreservoir", false)::Bool @@ -36,11 +43,11 @@ function initialize_sediment_model(config::Config) waterbodies = fill(0.0, n) if do_reservoirs reservoirs = ncread( - nc, + dataset, config, "reservoir_areas"; optional = false, - sel = inds, + sel = indices, type = Float, fill = 0, ) @@ -48,11 +55,11 @@ function initialize_sediment_model(config::Config) end if do_lakes lakes = ncread( - nc, + dataset, config, "lake_areas"; optional = false, - sel = inds, + sel = indices, type = Float, fill = 0, ) @@ -60,31 +67,32 @@ function initialize_sediment_model(config::Config) end waterbodies = waterbodies .> 0 - ldd_2d = ncread(nc, config, "ldd"; optional = false, allow_missing = true) - ldd = ldd_2d[inds] + ldd_2d = ncread(dataset, config, "ldd"; optional = false, allow_missing = true) + ldd = ldd_2d[indices] # # lateral part sediment in overland flow - overland_flow_sediment = OverlandFlowSediment(nc, config, inds, waterbodies, river) + overland_flow_sediment = + OverlandFlowSediment(dataset, config, indices, waterbodies, river) - graph = flowgraph(ldd, inds, pcr_dir) + graph = flowgraph(ldd, indices, pcr_dir) # River processes do_river = get(config.model, "runrivermodel", false)::Bool # TODO: see if we can skip init if the river model is not needed # or if we leave it when we restructure the Wflow Model struct - inds_riv, rev_inds_riv = active_indices(river_2d, 0) + indices_riv, rev_indices_riv = active_indices(river_2d, 0) - ldd_riv = ldd_2d[inds_riv] - graph_riv = flowgraph(ldd_riv, inds_riv, pcr_dir) + ldd_riv = ldd_2d[indices_riv] + graph_riv = flowgraph(ldd_riv, indices_riv, pcr_dir) # Needed for frac_to_river? landslope = ncread( - nc, + dataset, config, "vertical.land_parameter_set.slope"; optional = false, - sel = inds, + sel = indices, type = Float, ) clamp!(landslope, 0.00001, Inf) @@ -92,36 +100,37 @@ function initialize_sediment_model(config::Config) index_river = filter(i -> !isequal(river[i], 0), 1:n) frac_toriver = fraction_runoff_toriver(graph, ldd, index_river, landslope, n) - river_sediment = RiverSediment(nc, config, inds_riv, waterbodies) + river_sediment = RiverSediment(dataset, config, indices_riv, waterbodies) modelmap = ( vertical = soilloss, lateral = (land = overland_flow_sediment, river = river_sediment), ) indices_reverse = ( - land = rev_inds, - river = rev_inds_riv, + land = rev_indices, + river = rev_indices_riv, reservoir = isempty(reservoir) ? nothing : reservoir.reverse_indices, lake = isempty(lake) ? nothing : lake.reverse_indices, ) - y_nc = read_y_axis(nc) - x_nc = read_x_axis(nc) - writer = prepare_writer(config, modelmap, indices_reverse, x_nc, y_nc, nc) - close(nc) + y_dataset = read_y_axis(dataset) + x_dataset = read_x_axis(dataset) + writer = + prepare_writer(config, modelmap, indices_reverse, x_dataset, y_dataset, dataset) + close(dataset) # for each domain save the directed acyclic graph, the traversion order, # and the indices that map it back to the two dimensional grid land = ( graph = graph, order = topological_sort_by_dfs(graph), - indices = inds, - reverse_indices = rev_inds, + indices = indices, + reverse_indices = rev_indices, ) river = ( graph = graph_riv, order = topological_sort_by_dfs(graph_riv), - indices = inds_riv, - reverse_indices = rev_inds_riv, + indices = indices_riv, + reverse_indices = rev_indices_riv, ) model = Model( @@ -154,8 +163,8 @@ function update!(model::Model{N, L, V, R, W, T}) where {N, L, V, R, W, T <: Sedi # River sediment transport do_river = get(config.model, "runrivermodel", false)::Bool if do_river - inds_riv = network.index_river - update!(lateral.river, lateral.land.to_river, network.river, inds_riv, dt) + indices_riv = network.index_river + update!(lateral.river, lateral.land.to_river, network.river, indices_riv, dt) end return nothing From 3b99f4904df7353be2099176b1ead1a01b7a9a00 Mon Sep 17 00:00:00 2001 From: hboisgon Date: Wed, 4 Dec 2024 16:19:31 +0800 Subject: [PATCH 37/42] rename ts to dt --- src/erosion.jl | 7 +- src/sediment/erosion/erosion_process.jl | 36 ++--- src/sediment/erosion/overland_flow_erosion.jl | 4 +- src/sediment/erosion/rainfall_erosion.jl | 8 +- src/sediment/erosion/river_erosion.jl | 4 +- src/sediment/sediment_transport/deposition.jl | 2 +- .../sediment_transport/river_transport.jl | 8 +- .../sediment_transport/transport_capacity.jl | 142 +++++++++--------- .../transport_capacity_process.jl | 74 ++++----- src/sediment_flux.jl | 16 +- src/sediment_model.jl | 2 +- 11 files changed, 148 insertions(+), 155 deletions(-) diff --git a/src/erosion.jl b/src/erosion.jl index 58e432a21..d41a12920 100644 --- a/src/erosion.jl +++ b/src/erosion.jl @@ -63,17 +63,16 @@ function update!(model::SoilLoss, dt) overland_flow_erosion, soil_erosion, ) = model - # Convert dt to integer - ts = tosecond(dt) + #TODO add interception/canopygapfraction calculation here for eurosem #need SBM refactor # Rainfall erosion update_boundary_conditions!(rainfall_erosion, atmospheric_forcing, hydrological_forcing) - update!(rainfall_erosion, geometry, ts) + update!(rainfall_erosion, geometry, dt) # Overland flow erosion update_boundary_conditions!(overland_flow_erosion, hydrological_forcing) - update!(overland_flow_erosion, geometry, ts) + update!(overland_flow_erosion, geometry, dt) # Total soil erosion and particle differentiation update_boundary_conditions!(soil_erosion, rainfall_erosion, overland_flow_erosion) update!(soil_erosion) diff --git a/src/sediment/erosion/erosion_process.jl b/src/sediment/erosion/erosion_process.jl index eaf9d184c..b50f55b25 100644 --- a/src/sediment/erosion/erosion_process.jl +++ b/src/sediment/erosion/erosion_process.jl @@ -9,7 +9,7 @@ canopygapfraction, soilcover_fraction, area, - ts, + dt, ) Rainfall erosion model based on EUROSEM. @@ -24,7 +24,7 @@ Rainfall erosion model based on EUROSEM. - `canopygapfraction` (canopy gap fraction [-]) - `soilcover_fraction` (soil cover fraction [-]) - `area` (area [m2]) -- `ts` (timestep [seconds]) +- `dt` (timestep [seconds]) # Output - `rainfall_erosion` (soil loss [t Δt⁻¹]) @@ -39,10 +39,10 @@ function rainfall_erosion_eurosem( canopygapfraction, soilcover_fraction, area, - ts, + dt, ) # calculate rainfall intensity [mm/h] - rintnsty = precip / (ts / 3600) + rintnsty = precip / (dt / 3600) # Kinetic energy of direct throughfall [J/m2/mm] # kedir = max(11.87 + 8.73 * log10(max(0.0001, rintnsty)),0.0) #basis used in USLE kedir = max(8.95 + 8.44 * log10(max(0.0001, rintnsty)), 0.0) #variant used in most distributed mdoels @@ -72,7 +72,7 @@ end usle_k, usle_c, area, - ts, + dt, ) Rainfall erosion model based on ANSWERS. @@ -83,18 +83,18 @@ Rainfall erosion model based on ANSWERS. - `usle_c` (USLE cover and management factor [-]) - `soilcover_fraction` (soil cover fraction [-]) - `area` (area [m2]) -- `ts` (timestep [seconds]) +- `dt` (timestep [seconds]) # Output - `rainfall_erosion` (soil loss [t Δt⁻¹]) """ -function rainfall_erosion_answers(precip, usle_k, usle_c, area, ts) +function rainfall_erosion_answers(precip, usle_k, usle_c, area, dt) # calculate rainfall intensity [mm/min] - rintnsty = precip / (ts / 60) + rintnsty = precip / (dt / 60) # splash erosion [kg/min] rainfall_erosion = 0.108 * usle_c * usle_k * area * rintnsty^2 # [ton/timestep] - rainfall_erosion = rainfall_erosion * (ts / 60) * 1e-3 + rainfall_erosion = rainfall_erosion * (dt / 60) * 1e-3 return rainfall_erosion end @@ -108,7 +108,7 @@ end slope, soilcover_fraction, area, - ts, + dt, ) Overland flow erosion model based on ANSWERS. @@ -121,7 +121,7 @@ Overland flow erosion model based on ANSWERS. - `answers_k` (ANSWERS overland flow factor [-]) - `slope` (slope [-]) - `area` (area [m2]) -- `ts` (timestep [seconds]) +- `dt` (timestep [seconds]) # Output - `overland_flow_erosion` (soil loss [t Δt⁻¹]) @@ -133,7 +133,7 @@ function overland_flow_erosion_answers( answers_k, slope, area, - ts, + dt, ) # Overland flow rate [m2/min] qr_land = overland_flow * 60 / (area .^ 0.5) @@ -144,7 +144,7 @@ function overland_flow_erosion_answers( # For a wide range of slope, it is better to use the sine of slope rather than tangeant erosion = answers_k * usle_c * usle_k * area * sinslope * qr_land # [ton/timestep] - erosion = erosion * (ts / 60) * 1e-3 + erosion = erosion * (dt / 60) * 1e-3 return erosion end @@ -210,7 +210,7 @@ end width, length, slope, - ts, + dt, ) River erosion model based on Julian Torres. @@ -222,13 +222,13 @@ Repartition of the effective shear stress between the bank and the bed from Knig - `width` (width [m]) - `length` (length [m]) - `slope` (slope [-]) -- `ts` (timestep [seconds]) +- `dt` (timestep [seconds]) # Output - `bed` (potential river erosion [t Δt⁻¹]) - `bank` (potential bank erosion [t Δt⁻¹]) """ -function river_erosion_julian_torres(waterlevel, d50, width, length, slope, ts) +function river_erosion_julian_torres(waterlevel, d50, width, length, slope, dt) if waterlevel > 0.0 # Bed and Bank from Shields diagram, Da Silva & Yalin (2017) E_ = (2.65 - 1) * 9.81 @@ -257,9 +257,9 @@ function river_erosion_julian_torres(waterlevel, d50, width, length, slope, ts) #(assuming only one bank is eroding) Tex = max(TEffbank - TCrbank, 0.0) # 1.4 is bank default bulk density - ERbank = kdbank * Tex * length * waterlevel * 1.4 * ts + ERbank = kdbank * Tex * length * waterlevel * 1.4 * dt # 1.5 is bed default bulk density - ERbed = kdbed * (TEffbed - TCrbed) * length * width * 1.5 * ts + ERbed = kdbed * (TEffbed - TCrbed) * length * width * 1.5 * dt # Potential maximum bed/bank erosion bed = max(ERbed, 0.0) diff --git a/src/sediment/erosion/overland_flow_erosion.jl b/src/sediment/erosion/overland_flow_erosion.jl index 6883b6569..7fcfd22cc 100644 --- a/src/sediment/erosion/overland_flow_erosion.jl +++ b/src/sediment/erosion/overland_flow_erosion.jl @@ -90,7 +90,7 @@ function update_boundary_conditions!( @. q = q_land end -function update!(model::OverlandFlowErosionAnswersModel, geometry::LandParameters, ts) +function update!(model::OverlandFlowErosionAnswersModel, geometry::LandParameters, dt) (; q) = model.boundary_conditions (; usle_k, usle_c, answers_k) = model.parameters (; amount) = model.variables @@ -104,7 +104,7 @@ function update!(model::OverlandFlowErosionAnswersModel, geometry::LandParameter answers_k[i], geometry.slope[i], geometry.area[i], - ts, + dt, ) end end \ No newline at end of file diff --git a/src/sediment/erosion/rainfall_erosion.jl b/src/sediment/erosion/rainfall_erosion.jl index b87261792..3257d4e65 100644 --- a/src/sediment/erosion/rainfall_erosion.jl +++ b/src/sediment/erosion/rainfall_erosion.jl @@ -136,7 +136,7 @@ function update_boundary_conditions!( end "Update EUROSEM rainfall erosion model for a single timestep" -function update!(model::RainfallErosionEurosemModel, geometry::LandParameters, ts) +function update!(model::RainfallErosionEurosemModel, geometry::LandParameters, dt) (; precipitation, interception, waterlevel) = model.boundary_conditions (; soil_detachability, @@ -159,7 +159,7 @@ function update!(model::RainfallErosionEurosemModel, geometry::LandParameters, t canopygapfraction[i], soilcover_fraction[i], geometry.area[i], - ts, + dt, ) end end @@ -239,7 +239,7 @@ function update_boundary_conditions!( end "Update ANSWERS rainfall erosion model for a single timestep" -function update!(model::RainfallErosionAnswersModel, geometry::LandParameters, ts) +function update!(model::RainfallErosionAnswersModel, geometry::LandParameters, dt) (; precipitation) = model.boundary_conditions (; usle_k, usle_c) = model.parameters (; amount) = model.variables @@ -251,7 +251,7 @@ function update!(model::RainfallErosionAnswersModel, geometry::LandParameters, t usle_k[i], usle_c[i], geometry.area[i], - ts, + dt, ) end end \ No newline at end of file diff --git a/src/sediment/erosion/river_erosion.jl b/src/sediment/erosion/river_erosion.jl index 37d2c97ef..56c102378 100644 --- a/src/sediment/erosion/river_erosion.jl +++ b/src/sediment/erosion/river_erosion.jl @@ -73,7 +73,7 @@ function update_boundary_conditions!( @. waterlevel = waterlevel_river end -function update!(model::RiverErosionJulianTorresModel, geometry::RiverParameters, ts) +function update!(model::RiverErosionJulianTorresModel, geometry::RiverParameters, dt) (; waterlevel) = model.boundary_conditions (; d50) = model.parameters (; bed, bank) = model.variables @@ -86,7 +86,7 @@ function update!(model::RiverErosionJulianTorresModel, geometry::RiverParameters geometry.width[i], geometry.length[i], geometry.slope[i], - ts, + dt, ) end end \ No newline at end of file diff --git a/src/sediment/sediment_transport/deposition.jl b/src/sediment/sediment_transport/deposition.jl index 720c6685f..a533fd357 100644 --- a/src/sediment/sediment_transport/deposition.jl +++ b/src/sediment/sediment_transport/deposition.jl @@ -38,7 +38,7 @@ function reservoir_deposition_camp( # Natural deposition deposition = input * min(1.0, (DCres * (dm / 1000)^2)) - # Check if the particles was travelling in suspension or bed load using Rouse number + # Check if particles are travelling in suspension or bed load using Rouse number dsuspf = 1e3 * (1.2 * 3600 * 0.41 / 411 * (9.81 * waterlevel * slope)^0.5)^0.5 # If bed load, we have extra deposition depending on the reservoir type if dm > dsuspf diff --git a/src/sediment/sediment_transport/river_transport.jl b/src/sediment/sediment_transport/river_transport.jl index 43ed2dd0f..9c87e9ee3 100644 --- a/src/sediment/sediment_transport/river_transport.jl +++ b/src/sediment/sediment_transport/river_transport.jl @@ -387,7 +387,7 @@ function update!( network, geometry::RiverParameters, waterbodies, - ts, + dt, ) (; waterlevel, @@ -744,7 +744,7 @@ function update!( # Reduce the fraction so that there is still some sediment staying in the river cell if waterlevel[v] > 0.0 fwaterout = min( - q[v] * ts / (waterlevel[v] * geometry.width[v] * geometry.length[v]), + q[v] * dt / (waterlevel[v] * geometry.width[v] * geometry.length[v]), 1.0, ) else @@ -952,14 +952,14 @@ function update_boundary_conditions!( @. gravel = sediment_flux_model.variables.gravel end -function update!(model::SedimentConcentrationsRiverModel, geometry::RiverParameters, ts) +function update!(model::SedimentConcentrationsRiverModel, geometry::RiverParameters, dt) (; q, waterlevel, clay, silt, sand, sagg, lagg, gravel) = model.boundary_conditions (; dm_clay, dm_silt, dm_sand, dm_sagg, dm_lagg, dm_gravel) = model.parameters (; total, suspended, bed) = model.variables zeros = fill(0.0, length(q)) # Conversion from load [ton] to concentration for rivers [mg/L] - toconc = ifelse.(q .> 0.0, 1e6 ./ (q .* ts), zeros) + toconc = ifelse.(q .> 0.0, 1e6 ./ (q .* dt), zeros) # Differentiation of bed and suspended load using Rouse number for suspension # threshold diameter between bed load and mixed load using Rouse number diff --git a/src/sediment/sediment_transport/transport_capacity.jl b/src/sediment/sediment_transport/transport_capacity.jl index eeda04164..dbad7cf91 100644 --- a/src/sediment/sediment_transport/transport_capacity.jl +++ b/src/sediment/sediment_transport/transport_capacity.jl @@ -56,12 +56,12 @@ end n_govers::Vector{T} | "-" end -function TransportCapacityGoversParameters(dataset, config, inds) +function TransportCapacityGoversParameters(dataset, config, indices) slope = ncread( dataset, config, "lateral.land.transport_capacity.parameters.slope"; - sel = inds, + sel = indices, defaults = 0.01, type = Float, ) @@ -69,7 +69,7 @@ function TransportCapacityGoversParameters(dataset, config, inds) dataset, config, "lateral.land.transport_capacity.parameters.density"; - sel = inds, + sel = indices, defaults = 2650.0, type = Float, ) @@ -77,7 +77,7 @@ function TransportCapacityGoversParameters(dataset, config, inds) dataset, config, "lateral.land.transport_capacity.parameters.c_govers"; - sel = inds, + sel = indices, defaults = 0.000505, type = Float, ) @@ -85,7 +85,7 @@ function TransportCapacityGoversParameters(dataset, config, inds) dataset, config, "lateral.land.transport_capacity.parameters.n_govers"; - sel = inds, + sel = indices, defaults = 4.27, type = Float, ) @@ -105,10 +105,10 @@ end variables::TransportCapacityModelVariables{T} end -function TransportCapacityGoversModel(dataset, config, inds) - n = length(inds) +function TransportCapacityGoversModel(dataset, config, indices) + n = length(indices) vars = TransportCapacityModelVariables(n) - params = TransportCapacityGoversParameters(dataset, config, inds) + params = TransportCapacityGoversParameters(dataset, config, indices) bc = TransportCapacityBC(n) model = TransportCapacityGoversModel(; boundary_conditions = bc, @@ -118,7 +118,7 @@ function TransportCapacityGoversModel(dataset, config, inds) return model end -function update!(model::TransportCapacityGoversModel, width, waterbodies, rivers, ts) +function update!(model::TransportCapacityGoversModel, width, waterbodies, rivers, dt) (; q, waterlevel) = model.boundary_conditions (; slope, density, c_govers, n_govers) = model.parameters (; amount) = model.variables @@ -135,7 +135,7 @@ function update!(model::TransportCapacityGoversModel, width, waterbodies, rivers width[i], waterbodies[i], rivers[i], - ts, + dt, ) end end @@ -150,12 +150,12 @@ end d50::Vector{T} | "mm" end -function TransportCapacityYalinParameters(dataset, config, inds) +function TransportCapacityYalinParameters(dataset, config, indices) slope = ncread( dataset, config, "lateral.land.transport_capacity.parameters.slope"; - sel = inds, + sel = indices, defaults = 0.01, type = Float, ) @@ -163,7 +163,7 @@ function TransportCapacityYalinParameters(dataset, config, inds) dataset, config, "lateral.land.transport_capacity.parameters.density"; - sel = inds, + sel = indices, defaults = 2650.0, type = Float, ) @@ -171,7 +171,7 @@ function TransportCapacityYalinParameters(dataset, config, inds) dataset, config, "lateral.land.transport_capacity.parameters.d50"; - sel = inds, + sel = indices, defaults = 0.1, type = Float, ) @@ -187,10 +187,10 @@ end variables::TransportCapacityModelVariables{T} end -function TransportCapacityYalinModel(dataset, config, inds) - n = length(inds) +function TransportCapacityYalinModel(dataset, config, indices) + n = length(indices) vars = TransportCapacityModelVariables(n) - params = TransportCapacityYalinParameters(dataset, config, inds) + params = TransportCapacityYalinParameters(dataset, config, indices) bc = TransportCapacityBC(n) model = TransportCapacityYalinModel(; boundary_conditions = bc, @@ -200,7 +200,7 @@ function TransportCapacityYalinModel(dataset, config, inds) return model end -function update!(model::TransportCapacityYalinModel, width, waterbodies, rivers, ts) +function update!(model::TransportCapacityYalinModel, width, waterbodies, rivers, dt) (; q, waterlevel) = model.boundary_conditions (; slope, density, d50) = model.parameters (; amount) = model.variables @@ -216,7 +216,7 @@ function update!(model::TransportCapacityYalinModel, width, waterbodies, rivers, width[i], waterbodies[i], rivers[i], - ts, + dt, ) end end @@ -272,12 +272,12 @@ end dm_lagg::Vector{T} | "µm" end -function TransportCapacityYalinDifferentiationParameters(dataset, config, inds) +function TransportCapacityYalinDifferentiationParameters(dataset, config, indices) density = ncread( dataset, config, "lateral.land.transport_capacity.parameters.density"; - sel = inds, + sel = indices, defaults = 2650.0, type = Float, ) @@ -285,7 +285,7 @@ function TransportCapacityYalinDifferentiationParameters(dataset, config, inds) dataset, config, "lateral.land.transport_capacity.parameters.dm_clay"; - sel = inds, + sel = indices, defaults = 2.0, type = Float, ) @@ -293,7 +293,7 @@ function TransportCapacityYalinDifferentiationParameters(dataset, config, inds) dataset, config, "lateral.land.transport_capacity.parameters.dm_silt"; - sel = inds, + sel = indices, defaults = 10.0, type = Float, ) @@ -301,7 +301,7 @@ function TransportCapacityYalinDifferentiationParameters(dataset, config, inds) dataset, config, "lateral.land.transport_capacity.parameters.dm_sand"; - sel = inds, + sel = indices, defaults = 200.0, type = Float, ) @@ -309,7 +309,7 @@ function TransportCapacityYalinDifferentiationParameters(dataset, config, inds) dataset, config, "lateral.land.transport_capacity.parameters.dm_sagg"; - sel = inds, + sel = indices, defaults = 30.0, type = Float, ) @@ -317,7 +317,7 @@ function TransportCapacityYalinDifferentiationParameters(dataset, config, inds) dataset, config, "lateral.land.transport_capacity.parameters.dm_lagg"; - sel = inds, + sel = indices, defaults = 500.0, type = Float, ) @@ -340,10 +340,10 @@ end variables::TransportCapacityYalinDifferentiationModelVariables{T} end -function TransportCapacityYalinDifferentiationModel(dataset, config, inds) - n = length(inds) +function TransportCapacityYalinDifferentiationModel(dataset, config, indices) + n = length(indices) vars = TransportCapacityYalinDifferentiationModelVariables(n) - params = TransportCapacityYalinDifferentiationParameters(dataset, config, inds) + params = TransportCapacityYalinDifferentiationParameters(dataset, config, indices) bc = TransportCapacityBC(n) model = TransportCapacityYalinDifferentiationModel(; boundary_conditions = bc, @@ -358,7 +358,7 @@ function update!( geometry::LandParameters, waterbodies, rivers, - ts, + dt, ) (; q, waterlevel) = model.boundary_conditions (; density, dm_clay, dm_silt, dm_sand, dm_sagg, dm_lagg) = model.parameters @@ -386,7 +386,7 @@ function update!( waterbodies[i], rivers[i], dtot, - ts, + dt, ) silt[i] = transport_capacity_yalin_differentiation( q[i], @@ -398,7 +398,7 @@ function update!( waterbodies[i], rivers[i], dtot, - ts, + dt, ) sand[i] = transport_capacity_yalin_differentiation( q[i], @@ -410,7 +410,7 @@ function update!( waterbodies[i], rivers[i], dtot, - ts, + dt, ) sagg[i] = transport_capacity_yalin_differentiation( q[i], @@ -422,7 +422,7 @@ function update!( waterbodies[i], rivers[i], dtot, - ts, + dt, ) lagg[i] = transport_capacity_yalin_differentiation( q[i], @@ -434,7 +434,7 @@ function update!( waterbodies[i], rivers[i], dtot, - ts, + dt, ) amount[i] = clay[i] + silt[i] + sand[i] + sagg[i] + lagg[i] end @@ -448,12 +448,12 @@ end d50::Vector{T} | "mm" end -function TransportCapacityRiverParameters(dataset, config, inds) +function TransportCapacityRiverParameters(dataset, config, indices) density = ncread( dataset, config, "lateral.river.transport_capacity.parameters.density"; - sel = inds, + sel = indices, defaults = 2650.0, type = Float, ) @@ -461,7 +461,7 @@ function TransportCapacityRiverParameters(dataset, config, inds) dataset, config, "lateral.river.transport_capacity.parameters.d50"; - sel = inds, + sel = indices, defaults = 0.1, type = Float, ) @@ -478,12 +478,12 @@ end e_bagnold::Vector{T} | "-" end -function TransportCapacityBagnoldParameters(dataset, config, inds) +function TransportCapacityBagnoldParameters(dataset, config, indices) c_bagnold = ncread( dataset, config, "lateral.river.transport_capacity.parameters.c_bagnold"; - sel = inds, + sel = indices, optional = false, type = Float, ) @@ -491,7 +491,7 @@ function TransportCapacityBagnoldParameters(dataset, config, inds) dataset, config, "lateral.river.transport_capacity.parameters.e_bagnold"; - sel = inds, + sel = indices, optional = false, type = Float, ) @@ -507,10 +507,10 @@ end variables::TransportCapacityModelVariables{T} end -function TransportCapacityBagnoldModel(dataset, config, inds) - n = length(inds) +function TransportCapacityBagnoldModel(dataset, config, indices) + n = length(indices) vars = TransportCapacityModelVariables(n) - params = TransportCapacityBagnoldParameters(dataset, config, inds) + params = TransportCapacityBagnoldParameters(dataset, config, indices) bc = TransportCapacityBC(n) model = TransportCapacityBagnoldModel(; boundary_conditions = bc, @@ -520,7 +520,7 @@ function TransportCapacityBagnoldModel(dataset, config, inds) return model end -function update!(model::TransportCapacityBagnoldModel, geometry::RiverParameters, ts) +function update!(model::TransportCapacityBagnoldModel, geometry::RiverParameters, dt) (; q, waterlevel) = model.boundary_conditions (; c_bagnold, e_bagnold) = model.parameters (; amount) = model.variables @@ -536,7 +536,7 @@ function update!(model::TransportCapacityBagnoldModel, geometry::RiverParameters e_bagnold[i], geometry.width[i], geometry.length[i], - ts, + dt, ) end end @@ -548,10 +548,10 @@ end variables::TransportCapacityModelVariables{T} end -function TransportCapacityEngelundModel(dataset, config, inds) - n = length(inds) +function TransportCapacityEngelundModel(dataset, config, indices) + n = length(indices) vars = TransportCapacityModelVariables(n) - params = TransportCapacityRiverParameters(dataset, config, inds) + params = TransportCapacityRiverParameters(dataset, config, indices) bc = TransportCapacityBC(n) model = TransportCapacityEngelundModel(; boundary_conditions = bc, @@ -561,7 +561,7 @@ function TransportCapacityEngelundModel(dataset, config, inds) return model end -function update!(model::TransportCapacityEngelundModel, geometry::RiverParameters, ts) +function update!(model::TransportCapacityEngelundModel, geometry::RiverParameters, dt) (; q, waterlevel) = model.boundary_conditions (; density, d50) = model.parameters (; amount) = model.variables @@ -576,7 +576,7 @@ function update!(model::TransportCapacityEngelundModel, geometry::RiverParameter geometry.width[i], geometry.length[i], geometry.slope[i], - ts, + dt, ) end end @@ -593,12 +593,12 @@ end d_kodatie::Vector{T} | "-" end -function TransportCapacityKodatieParameters(dataset, config, inds) +function TransportCapacityKodatieParameters(dataset, config, indices) a_kodatie = ncread( dataset, config, "lateral.river.transport_capacity.parameters.a_kodatie"; - sel = inds, + sel = indices, optional = false, type = Float, ) @@ -606,7 +606,7 @@ function TransportCapacityKodatieParameters(dataset, config, inds) dataset, config, "lateral.river.transport_capacity.parameters.b_kodatie"; - sel = inds, + sel = indices, optional = false, type = Float, ) @@ -614,7 +614,7 @@ function TransportCapacityKodatieParameters(dataset, config, inds) dataset, config, "lateral.river.transport_capacity.parameters.c_kodatie"; - sel = inds, + sel = indices, optional = false, type = Float, ) @@ -622,7 +622,7 @@ function TransportCapacityKodatieParameters(dataset, config, inds) dataset, config, "lateral.river.transport_capacity.parameters.d_kodatie"; - sel = inds, + sel = indices, optional = false, type = Float, ) @@ -642,10 +642,10 @@ end variables::TransportCapacityModelVariables{T} end -function TransportCapacityKodatieModel(dataset, config, inds) - n = length(inds) +function TransportCapacityKodatieModel(dataset, config, indices) + n = length(indices) vars = TransportCapacityModelVariables(n) - params = TransportCapacityKodatieParameters(dataset, config, inds) + params = TransportCapacityKodatieParameters(dataset, config, indices) bc = TransportCapacityBC(n) model = TransportCapacityKodatieModel(; boundary_conditions = bc, @@ -655,7 +655,7 @@ function TransportCapacityKodatieModel(dataset, config, inds) return model end -function update!(model::TransportCapacityKodatieModel, geometry::RiverParameters, ts) +function update!(model::TransportCapacityKodatieModel, geometry::RiverParameters, dt) (; q, waterlevel) = model.boundary_conditions (; a_kodatie, b_kodatie, c_kodatie, d_kodatie) = model.parameters (; amount) = model.variables @@ -672,7 +672,7 @@ function update!(model::TransportCapacityKodatieModel, geometry::RiverParameters geometry.width[i], geometry.length[i], geometry.slope[i], - ts, + dt, ) end end @@ -684,10 +684,10 @@ end variables::TransportCapacityModelVariables{T} end -function TransportCapacityYangModel(dataset, config, inds) - n = length(inds) +function TransportCapacityYangModel(dataset, config, indices) + n = length(indices) vars = TransportCapacityModelVariables(n) - params = TransportCapacityRiverParameters(dataset, config, inds) + params = TransportCapacityRiverParameters(dataset, config, indices) bc = TransportCapacityBC(n) model = TransportCapacityYangModel(; boundary_conditions = bc, @@ -697,7 +697,7 @@ function TransportCapacityYangModel(dataset, config, inds) return model end -function update!(model::TransportCapacityYangModel, geometry::RiverParameters, ts) +function update!(model::TransportCapacityYangModel, geometry::RiverParameters, dt) (; q, waterlevel) = model.boundary_conditions (; density, d50) = model.parameters (; amount) = model.variables @@ -712,7 +712,7 @@ function update!(model::TransportCapacityYangModel, geometry::RiverParameters, t geometry.width[i], geometry.length[i], geometry.slope[i], - ts, + dt, ) end end @@ -724,10 +724,10 @@ end variables::TransportCapacityModelVariables{T} end -function TransportCapacityMolinasModel(dataset, config, inds) - n = length(inds) +function TransportCapacityMolinasModel(dataset, config, indices) + n = length(indices) vars = TransportCapacityModelVariables(n) - params = TransportCapacityRiverParameters(dataset, config, inds) + params = TransportCapacityRiverParameters(dataset, config, indices) bc = TransportCapacityBC(n) model = TransportCapacityMolinasModel(; boundary_conditions = bc, @@ -737,7 +737,7 @@ function TransportCapacityMolinasModel(dataset, config, inds) return model end -function update!(model::TransportCapacityMolinasModel, geometry::RiverParameters, ts) +function update!(model::TransportCapacityMolinasModel, geometry::RiverParameters, dt) (; q, waterlevel) = model.boundary_conditions (; density, d50) = model.parameters (; amount) = model.variables @@ -752,7 +752,7 @@ function update!(model::TransportCapacityMolinasModel, geometry::RiverParameters geometry.width[i], geometry.length[i], geometry.slope[i], - ts, + dt, ) end end \ No newline at end of file diff --git a/src/sediment/sediment_transport/transport_capacity_process.jl b/src/sediment/sediment_transport/transport_capacity_process.jl index f4b85e4df..2e8e2338e 100644 --- a/src/sediment/sediment_transport/transport_capacity_process.jl +++ b/src/sediment/sediment_transport/transport_capacity_process.jl @@ -36,7 +36,7 @@ end waterlevel, width, length, - ts, + dt, ) Limit to stremaflow and not debris flow and convert transport in ton/m3 to ton. @@ -47,7 +47,7 @@ Limit to stremaflow and not debris flow and convert transport in ton/m3 to ton. - `waterlevel` (water level [m]) - `width` (drain width [m]) - `length` (drain length [m]) -- `ts` (time step [s]) +- `dt` (time step [s]) # Output - `transport_capacity` (total sediment transport capacity [t dt-1]) @@ -58,12 +58,12 @@ function limit_and_convert_transport_capacity( waterlevel, width, length, - ts, + dt, ) # 1285 g/L: boundary between streamflow and debris flow (Costa, 1988) transport_capacity = min(transport_capacity, 1.285) # Transport capacity [ton] - transport_capacity = transport_capacity * (waterlevel * width * length + q * ts) + transport_capacity = transport_capacity * (waterlevel * width * length + q * dt) return transport_capacity end @@ -79,7 +79,7 @@ end width, waterbodies, rivers, - ts, + dt, ) Total sediment transport capacity based on Govers. @@ -94,7 +94,7 @@ Total sediment transport capacity based on Govers. - `width` (drain width [m]) - `waterbodies` (waterbodies mask [-]) - `rivers` (rivers mask [-]) -- `ts` (time step [s]) +- `dt` (time step [s]) # Output - `transport_capacity` (total sediment transport capacity [t dt-1]) @@ -109,7 +109,7 @@ function transport_capacity_govers( width, waterbodies, rivers, - ts, + dt, ) # Transport capacity from govers 1990 sinslope = sin(atan(slope)) #slope in radians @@ -125,7 +125,7 @@ function transport_capacity_govers( else TCf = 0.0 end - transport_capacity = TCf * q * ts * 1e-3 #[ton/cell] + transport_capacity = TCf * q * dt * 1e-3 #[ton/cell] # Mask transport capacity values for waterbodies and rivers transport_capacity = mask_transport_capacity(transport_capacity, waterbodies, rivers) @@ -143,7 +143,7 @@ end width, waterbodies, rivers, - ts, + dt, ) Total sediment transport capacity based on Yalin. @@ -157,7 +157,7 @@ Total sediment transport capacity based on Yalin. - `width` (drain width [m]) - `waterbodies` (waterbodies mask [-]) - `rivers` (rivers mask [-]) -- `ts` (time step [s]) +- `dt` (time step [s]) # Output - `transport_capacity` (total sediment transport capacity [t dt-1]) @@ -171,7 +171,7 @@ function transport_capacity_yalin( width, waterbodies, rivers, - ts, + dt, ) sinslope = sin(atan(slope)) #slope in radians # Transport capacity from Yalin without particle differentiation @@ -189,7 +189,7 @@ function transport_capacity_yalin( delta * (1 - log(1 + alphay) / (alphay)) ) # [kg/m3] - transport_capacity = TC * q * ts * 1e-3 #[ton] + transport_capacity = TC * q * dt * 1e-3 #[ton] else transport_capacity = 0.0 end @@ -262,7 +262,7 @@ end waterbodies, rivers, dtot, - ts, + dt, ) Transport capacity for a specific grain size based on Yalin with particle differentiation. @@ -277,7 +277,7 @@ Transport capacity for a specific grain size based on Yalin with particle differ - `waterbodies` (waterbodies mask [-]) - `rivers` (rivers mask [-]) - `dtot` (total flow transportability [t dt-1]) -- `ts` (time step [s]) +- `dt` (time step [s]) # Output - `transport_capacity` (total sediment transport capacity [t dt-1]) @@ -292,7 +292,7 @@ function transport_capacity_yalin_differentiation( waterbodies, rivers, dtot, - ts, + dt, ) sinslope = sin(atan(slope)) #slope in radians # Transport capacity from Yalin with particle differentiation @@ -314,7 +314,7 @@ function transport_capacity_yalin_differentiation( 0.635 * d_part * (1 - log(1 + d_part * TCb) / d_part * TCb) # [kg/m3] - transport_capacity = TC * q * ts * 1e-3 #[ton] + transport_capacity = TC * q * dt * 1e-3 #[ton] else transport_capacity = 0.0 end @@ -333,7 +333,7 @@ end e_bagnold, width, length, - ts, + dt, ) Total sediment transport capacity based on Bagnold. @@ -345,12 +345,12 @@ Total sediment transport capacity based on Bagnold. - `e_bagnold` (Bagnold transport capacity exponent [-]) - `width` (drain width [m]) - `length` (drain length [m]) -- `ts` (time step [s]) +- `dt` (time step [s]) # Output - `transport_capacity` (total sediment transport capacity [t dt-1]) """ -function transport_capacity_bagnold(q, waterlevel, c_bagnold, e_bagnold, width, length, ts) +function transport_capacity_bagnold(q, waterlevel, c_bagnold, e_bagnold, width, length, dt) # Transport capacity from Bagnold if waterlevel > 0.0 # Transport capacity [tons/m3] @@ -361,7 +361,7 @@ function transport_capacity_bagnold(q, waterlevel, c_bagnold, e_bagnold, width, waterlevel, width, length, - ts, + dt, ) else transport_capacity = 0.0 @@ -379,7 +379,7 @@ end width, length, slope, - ts, + dt, ) Total sediment transport capacity based on Engelund and Hansen. @@ -392,12 +392,12 @@ Total sediment transport capacity based on Engelund and Hansen. - `width` (drain width [m]) - `length` (drain length [m]) - `slope` (slope [-]) -- `ts` (time step [s]) +- `dt` (time step [s]) # Output - `transport_capacity` (total sediment transport capacity [t dt-1]) """ -function transport_capacity_engelund(q, waterlevel, density, d50, width, length, slope, ts) +function transport_capacity_engelund(q, waterlevel, density, d50, width, length, slope, dt) # Transport capacity from Engelund and Hansen if waterlevel > 0.0 # Hydraulic radius of the river [m] (rectangular channel) @@ -423,7 +423,7 @@ function transport_capacity_engelund(q, waterlevel, density, d50, width, length, waterlevel, width, length, - ts, + dt, ) else @@ -444,7 +444,7 @@ end width, length, slope, - ts, + dt, ) Total sediment transport capacity based on Kodatie. @@ -458,7 +458,7 @@ Total sediment transport capacity based on Kodatie. - `d_kodatie` (Kodatie transport capacity coefficient [-]) - `width` (drain width [m]) - `slope` (slope [-]) -- `ts` (time step [s]) +- `dt` (time step [s]) # Output - `transport_capacity` (total sediment transport capacity [t dt-1]) @@ -473,7 +473,7 @@ function transport_capacity_kodatie( width, length, slope, - ts, + dt, ) # Transport capacity from Kodatie if waterlevel > 0.0 @@ -485,14 +485,14 @@ function transport_capacity_kodatie( a_kodatie * velocity^b_kodatie * waterlevel^c_kodatie * slope^d_kodatie # Transport capacity [tons/m3] - transport_capacity = transport_capacity * width / (q * ts) + transport_capacity = transport_capacity * width / (q * dt) transport_capacity = limit_and_convert_transport_capacity( transport_capacity, q, waterlevel, width, length, - ts, + dt, ) else @@ -511,7 +511,7 @@ end width, length, slope, - ts, + dt, ) Total sediment transport capacity based on Yang sand and gravel equations. @@ -524,12 +524,12 @@ Total sediment transport capacity based on Yang sand and gravel equations. - `width` (drain width [m]) - `length` (drain length [m]) - `slope` (slope [-]) -- `ts` (time step [s]) +- `dt` (time step [s]) # Output - `transport_capacity` (total sediment transport capacity [t dt-1]) """ -function transport_capacity_yang(q, waterlevel, density, d50, width, length, slope, ts) +function transport_capacity_yang(q, waterlevel, density, d50, width, length, slope, dt) # Transport capacity from Yang omegas = 411 * d50^2 / 3600 # Hydraulic radius of the river [m] (rectangular channel) @@ -575,7 +575,7 @@ function transport_capacity_yang(q, waterlevel, density, d50, width, length, slo waterlevel, width, length, - ts, + dt, ) return transport_capacity @@ -590,7 +590,7 @@ end width, length, slope, - ts, + dt, ) Total sediment transport capacity based on Molinas and Wu. @@ -603,12 +603,12 @@ Total sediment transport capacity based on Molinas and Wu. - `width` (drain width [m]) - `length` (drain length [m]) - `slope` (slope [-]) -- `ts` (time step [s]) +- `dt` (time step [s]) # Output - `transport_capacity` (total sediment transport capacity [t dt-1]) """ -function transport_capacity_molinas(q, waterlevel, density, d50, width, length, slope, ts) +function transport_capacity_molinas(q, waterlevel, density, d50, width, length, slope, dt) # Transport capacity from Molinas and Wu if waterlevel > 0.0 # Flow velocity [m/s] @@ -637,7 +637,7 @@ function transport_capacity_molinas(q, waterlevel, density, d50, width, length, waterlevel, width, length, - ts, + dt, ) else diff --git a/src/sediment_flux.jl b/src/sediment_flux.jl index 87aa5e874..753299303 100644 --- a/src/sediment_flux.jl +++ b/src/sediment_flux.jl @@ -54,12 +54,9 @@ function OverlandFlowSediment(dataset, config, indices, waterbodies, rivers) end function update!(model::OverlandFlowSediment, erosion_model::SoilErosionModel, network, dt) - # Convert dt to integer - ts = tosecond(dt) - # Transport capacity update_boundary_conditions!(model.transport_capacity, model.hydrological_forcing, :land) - update!(model.transport_capacity, model.geometry, model.waterbodies, model.rivers, ts) + update!(model.transport_capacity, model.geometry, model.waterbodies, model.rivers, dt) # Update boundary conditions before transport update_boundary_conditions!( @@ -142,20 +139,17 @@ function update!( indices_river, dt, ) - # Convert dt to integer - ts = tosecond(dt) - # Transport capacity update_boundary_conditions!( model.transport_capacity, model.hydrological_forcing, :river, ) - update!(model.transport_capacity, model.geometry, ts) + update!(model.transport_capacity, model.geometry, dt) # Potential maximum river erosion update_boundary_conditions!(model.potential_erosion, model.hydrological_forcing) - update!(model.potential_erosion, model.geometry, ts) + update!(model.potential_erosion, model.geometry, dt) # River transport update_boundary_conditions!( @@ -166,7 +160,7 @@ function update!( model.potential_erosion, indices_river, ) - update!(model.sediment_flux, network, model.geometry, model.waterbodies, ts) + update!(model.sediment_flux, network, model.geometry, model.waterbodies, dt) # Concentrations update_boundary_conditions!( @@ -174,5 +168,5 @@ function update!( model.hydrological_forcing, model.sediment_flux, ) - update!(model.concentrations, model.geometry, ts) + update!(model.concentrations, model.geometry, dt) end \ No newline at end of file diff --git a/src/sediment_model.jl b/src/sediment_model.jl index a12fbaab7..1cceab4d7 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -152,7 +152,7 @@ end function update!(model::Model{N, L, V, R, W, T}) where {N, L, V, R, W, T <: SedimentModel} (; lateral, vertical, network, config, clock) = model - dt = clock.dt + dt = tosecond(clock.dt) # Soil erosion update!(vertical, dt) From ff673c34382bdbeb73a327c8f1eb22edef63454b Mon Sep 17 00:00:00 2001 From: hboisgon Date: Thu, 5 Dec 2024 10:58:32 +0800 Subject: [PATCH 38/42] add docstrings --- src/erosion.jl | 4 +- src/sediment/erosion/overland_flow_erosion.jl | 12 ++++- src/sediment/erosion/rainfall_erosion.jl | 3 -- src/sediment/erosion/river_erosion.jl | 12 ++++- src/sediment/erosion/soil_erosion.jl | 12 ++++- .../sediment_transport/land_to_river.jl | 18 ++++++- .../overland_flow_transport.jl | 18 ++++++- .../sediment_transport/river_transport.jl | 24 +++++++-- .../sediment_transport/transport_capacity.jl | 54 +++++++++++++++---- src/sediment_flux.jl | 7 ++- src/sediment_model.jl | 2 + 11 files changed, 136 insertions(+), 30 deletions(-) diff --git a/src/erosion.jl b/src/erosion.jl index d41a12920..d3dcbc3c6 100644 --- a/src/erosion.jl +++ b/src/erosion.jl @@ -1,4 +1,4 @@ -"Total soil erosion model" +"Soil loss model" @with_kw struct SoilLoss{RE, OFE, SE, T} atmospheric_forcing::AtmosphericForcing{T} hydrological_forcing::HydrologicalForcing{T} @@ -8,6 +8,7 @@ soil_erosion::SE end +"Initialize soil loss model" function SoilLoss(dataset, config, indices) n = length(indices) @@ -54,6 +55,7 @@ function SoilLoss(dataset, config, indices) return soil_loss end +"Update soil loss model for a single timestep" function update!(model::SoilLoss, dt) (; atmospheric_forcing, diff --git a/src/sediment/erosion/overland_flow_erosion.jl b/src/sediment/erosion/overland_flow_erosion.jl index 7fcfd22cc..1f8987850 100644 --- a/src/sediment/erosion/overland_flow_erosion.jl +++ b/src/sediment/erosion/overland_flow_erosion.jl @@ -1,25 +1,28 @@ abstract type AbstractOverlandFlowErosionModel{T} end -## Overland flow structs and functions +"Struct for storing overland flow erosion model variables" @get_units @grid_loc @with_kw struct OverlandFlowErosionVariables{T} # Total soil erosion from overland flow amount::Vector{T} | "t dt-1" end +"Initialize overland flow erosion model variables" function OverlandFlowErosionVariables(n; amount::Vector{T} = fill(mv, n)) where {T} return OverlandFlowErosionVariables{T}(; amount = amount) end +"Struct for storing overland flow erosion model boundary conditions" @get_units @grid_loc @with_kw struct OverlandFlowErosionBC{T} # Overland flow [m3 s-1] q::Vector{T} end +"Initialize overland flow erosion model boundary conditions" function OverlandFlowErosionBC(n; q::Vector{T} = fill(mv, n)) where {T} return OverlandFlowErosionBC{T}(; q = q) end -# ANSWERS specific structs and functions for rainfall erosion +"Struct for storing ANSWERS overland flow erosion model parameters" @get_units @grid_loc @with_kw struct OverlandFlowErosionAnswersParameters{T} # Soil erodibility factor usle_k::Vector{T} | "-" @@ -29,6 +32,7 @@ end answers_k::Vector{T} | "-" end +"Initialize ANSWERS overland flow erosion model parameters" function OverlandFlowErosionAnswersParameters(dataset, config, indices) usle_k = ncread( dataset, @@ -62,12 +66,14 @@ function OverlandFlowErosionAnswersParameters(dataset, config, indices) return answers_parameters end +"ANSWERS overland flow erosion model" @with_kw struct OverlandFlowErosionAnswersModel{T} <: AbstractOverlandFlowErosionModel{T} boundary_conditions::OverlandFlowErosionBC{T} parameters::OverlandFlowErosionAnswersParameters{T} variables::OverlandFlowErosionVariables{T} end +"Initialize ANSWERS overland flow erosion model" function OverlandFlowErosionAnswersModel(dataset, config, indices) n = length(indices) vars = OverlandFlowErosionVariables(n) @@ -81,6 +87,7 @@ function OverlandFlowErosionAnswersModel(dataset, config, indices) return model end +"Update boundary conditions for ANSWERS overland flow erosion model" function update_boundary_conditions!( model::OverlandFlowErosionAnswersModel, hydrological_forcing::HydrologicalForcing, @@ -90,6 +97,7 @@ function update_boundary_conditions!( @. q = q_land end +"Update ANSWERS overland flow erosion model for a single timestep" function update!(model::OverlandFlowErosionAnswersModel, geometry::LandParameters, dt) (; q) = model.boundary_conditions (; usle_k, usle_c, answers_k) = model.parameters diff --git a/src/sediment/erosion/rainfall_erosion.jl b/src/sediment/erosion/rainfall_erosion.jl index 3257d4e65..a94b7ee2f 100644 --- a/src/sediment/erosion/rainfall_erosion.jl +++ b/src/sediment/erosion/rainfall_erosion.jl @@ -1,6 +1,5 @@ abstract type AbstractRainfallErosionModel{T} end -## General rainfall erosion functions and structs "Struct for storing rainfall erosion model variables" @get_units @grid_loc @with_kw struct RainfallErosionModelVariables{T} # Total soil erosion from rainfall (splash) @@ -12,7 +11,6 @@ function RainfallErosionModelVariables(n; amount::Vector{T} = fill(mv, n)) where return RainfallErosionModelVariables{T}(; amount = amount) end -## EUROSEM specific structs and functions for rainfall erosion "Struct for storing EUROSEM rainfall erosion model boundary conditions" @get_units @grid_loc @with_kw struct RainfallErosionEurosemBC{T} # precipitation @@ -164,7 +162,6 @@ function update!(model::RainfallErosionEurosemModel, geometry::LandParameters, d end end -### ANSWERS specific structs and functions for rainfall erosion "Struct for storing ANSWERS rainfall erosion model boundary conditions" @get_units @grid_loc @with_kw struct RainfallErosionAnswersBC{T} # precipitation diff --git a/src/sediment/erosion/river_erosion.jl b/src/sediment/erosion/river_erosion.jl index 56c102378..d7b2c9322 100644 --- a/src/sediment/erosion/river_erosion.jl +++ b/src/sediment/erosion/river_erosion.jl @@ -1,6 +1,6 @@ abstract type AbstractRiverErosionModel{T} end -## Potential direct river erosion structs and functions +"Struct for storing river bed and bank erosion model variables" @get_units @grid_loc @with_kw struct RiverErosionModelVariables{T} # Potential river bed erosion bed::Vector{T} | "t dt-1" @@ -8,6 +8,7 @@ abstract type AbstractRiverErosionModel{T} end bank::Vector{T} | "t dt-1" end +"Initialize river bed and bank erosion model variables" function RiverErosionModelVariables( n; bed::Vector{T} = fill(mv, n), @@ -16,27 +17,31 @@ function RiverErosionModelVariables( return RiverErosionModelVariables{T}(; bed = bed, bank = bank) end +"Struct for storing river erosion model boundary conditions" @get_units @grid_loc @with_kw struct RiverErosionBC{T} # Waterlevel waterlevel::Vector{T} | "t dt-1" end +"Initialize river erosion model boundary conditions" function RiverErosionBC(n; waterlevel::Vector{T} = fill(mv, n)) where {T} return RiverErosionBC{T}(; waterlevel = waterlevel) end -# Parameters for the Julian Torres river erosion model +"Struct for storing river erosion model parameters" @get_units @grid_loc @with_kw struct RiverErosionParameters{T} # Mean diameter in the river bed/bank d50::Vector{T} | "mm" end +"Julian and Torres river erosion model" @with_kw struct RiverErosionJulianTorresModel{T} <: AbstractRiverErosionModel{T} boundary_conditions::RiverErosionBC{T} parameters::RiverErosionParameters{T} variables::RiverErosionModelVariables{T} end +"Initialize Julian and Torres river erosion parameters" function RiverErosionParameters(dataset, config, indices) d50 = ncread( dataset, @@ -51,6 +56,7 @@ function RiverErosionParameters(dataset, config, indices) return river_parameters end +"Initialize Julian and Torres river erosion model" function RiverErosionJulianTorresModel(dataset, config, indices) n = length(indices) vars = RiverErosionModelVariables(n) @@ -64,6 +70,7 @@ function RiverErosionJulianTorresModel(dataset, config, indices) return model end +"Update river erosion model boundary conditions" function update_boundary_conditions!( model::RiverErosionJulianTorresModel, hydrological_forcing::HydrologicalForcing, @@ -73,6 +80,7 @@ function update_boundary_conditions!( @. waterlevel = waterlevel_river end +"Update Julian and Torres river erosion model for a single timestep" function update!(model::RiverErosionJulianTorresModel, geometry::RiverParameters, dt) (; waterlevel) = model.boundary_conditions (; d50) = model.parameters diff --git a/src/sediment/erosion/soil_erosion.jl b/src/sediment/erosion/soil_erosion.jl index 4e9f7b575..6578a0dbe 100644 --- a/src/sediment/erosion/soil_erosion.jl +++ b/src/sediment/erosion/soil_erosion.jl @@ -1,6 +1,6 @@ abstract type AbstractSoilErosionModel{T} end -## Total soil erosion and differentiation structs and functions +"Struct for storing total soil erosion with differentiation model variables" @get_units @grid_loc @with_kw struct SoilErosionModelVariables{T} # Total soil erosion amount::Vector{T} | "t dt-1" @@ -16,6 +16,7 @@ abstract type AbstractSoilErosionModel{T} end lagg::Vector{T} | "t dt-1" end +"Initialize soil erosion model variables" function SoilErosionModelVariables( n; amount::Vector{T} = fill(mv, n), @@ -35,6 +36,7 @@ function SoilErosionModelVariables( ) end +"Struct for storing soil erosion model boundary conditions" @get_units @grid_loc @with_kw struct SoilErosionBC{T} # Rainfall erosion rainfall_erosion::Vector{T} | "t dt-1" @@ -42,6 +44,7 @@ end overland_flow_erosion::Vector{T} | "m dt-1" end +"Initialize soil erosion model boundary conditions" function SoilErosionBC( n; rainfall_erosion::Vector{T} = fill(mv, n), @@ -53,7 +56,7 @@ function SoilErosionBC( ) end -# Parameters for particle differentiation +"Struct for storing soil erosion model parameters" @get_units @grid_loc @with_kw struct SoilErosionParameters{T} # Soil content clay clay_fraction::Vector{T} | "-" @@ -67,6 +70,7 @@ end lagg_fraction::Vector{T} | "-" end +"Initialize soil erosion model parameters" function SoilErosionParameters(dataset, config, indices) clay_fraction = ncread( dataset, @@ -125,12 +129,14 @@ function SoilErosionParameters(dataset, config, indices) return soil_parameters end +"Total soil erosion with differentiation model" @with_kw struct SoilErosionModel{T} <: AbstractSoilErosionModel{T} boundary_conditions::SoilErosionBC{T} parameters::SoilErosionParameters{T} variables::SoilErosionModelVariables{T} end +"Initialize soil erosion model" function SoilErosionModel(dataset, config, indices) n = length(indices) vars = SoilErosionModelVariables(n) @@ -141,6 +147,7 @@ function SoilErosionModel(dataset, config, indices) return model end +"Update boundary conditions for soil erosion model" function update_boundary_conditions!( model::SoilErosionModel, rainfall_erosion::AbstractRainfallErosionModel, @@ -153,6 +160,7 @@ function update_boundary_conditions!( @. overland_flow_erosion = ole end +"Update soil erosion model for a single timestep" function update!(model::SoilErosionModel) (; rainfall_erosion, overland_flow_erosion) = model.boundary_conditions (; clay_fraction, silt_fraction, sand_fraction, sagg_fraction, lagg_fraction) = diff --git a/src/sediment/sediment_transport/land_to_river.jl b/src/sediment/sediment_transport/land_to_river.jl index 364569e3a..c4c34971b 100644 --- a/src/sediment/sediment_transport/land_to_river.jl +++ b/src/sediment/sediment_transport/land_to_river.jl @@ -1,29 +1,34 @@ abstract type AbstractSedimentToRiverModel{T} end -## Total sediment transport in overland flow structs and functions +"Struct to store total sediment reaching the river model variables" @get_units @grid_loc @with_kw struct SedimentToRiverVariables{T} # Total sediment reaching the river amount::Vector{T} | "t dt-1" end +"Initialize total sediment reaching the river model variables" function SedimentToRiverVariables(n; amount::Vector{T} = fill(mv, n)) where {T} return SedimentToRiverVariables{T}(; amount = amount) end +"Struct to store total sediment reaching the river model boundary conditions" @get_units @grid_loc @with_kw struct SedimentToRiverBC{T} # Deposited material deposition::Vector{T} | "t dt-1" end +"Initialize total sediment reaching the river model boundary conditions" function SedimentToRiverBC(n; deposition::Vector{T} = fill(mv, n)) where {T} return SedimentToRiverBC{T}(; deposition = deposition) end +"Struct to store total sediment reaching the river model" @with_kw struct SedimentToRiverModel{T} <: AbstractSedimentToRiverModel{T} boundary_conditions::SedimentToRiverBC{T} variables::SedimentToRiverVariables{T} end +"Initialize total sediment reaching the river model" function SedimentToRiverModel(indices) n = length(indices) vars = SedimentToRiverVariables(n) @@ -32,6 +37,7 @@ function SedimentToRiverModel(indices) return model end +"Update total sediment reaching the river model boundary conditions" function update_boundary_conditions!( model::SedimentToRiverModel, transport_model::SedimentLandTransportModel, @@ -40,6 +46,7 @@ function update_boundary_conditions!( @. deposition = transport_model.variables.deposition end +"Update total sediment reaching the river model for a single timestep" function update!(model::SedimentToRiverModel, rivers) (; deposition) = model.boundary_conditions (; amount) = model.variables @@ -48,7 +55,7 @@ function update!(model::SedimentToRiverModel, rivers) amount .= ifelse.(rivers, deposition, zeros) end -## Different particles reaching the river structs and functions +"Struct to store differentiated sediment reaching the river model variables" @get_units @grid_loc @with_kw struct SedimentToRiverDifferentiationVariables{T} # Total sediment flux amount::Vector{T} | "t dt-1" @@ -64,6 +71,7 @@ end lagg::Vector{T} | "t dt-1" end +"Initialize differentiated sediment reaching the river model variables" function SedimentToRiverDifferentiationVariables( n; amount::Vector{T} = fill(mv, n), @@ -83,6 +91,7 @@ function SedimentToRiverDifferentiationVariables( ) end +"Struct to store differentiated sediment reaching the river model boundary conditions" @get_units @grid_loc @with_kw struct SedimentToRiverDifferentiationBC{T} # Deposited clay deposition_clay::Vector{T} | "t dt-1" @@ -96,6 +105,7 @@ end deposition_lagg::Vector{T} | "t dt-1" end +"Initialize differentiated sediment reaching the river model boundary conditions" function SedimentToRiverDifferentiationBC( n; deposition_clay::Vector{T} = fill(mv, n), @@ -113,11 +123,13 @@ function SedimentToRiverDifferentiationBC( ) end +"Struct to store differentiated sediment reaching the river model" @with_kw struct SedimentToRiverDifferentiationModel{T} <: AbstractSedimentToRiverModel{T} boundary_conditions::SedimentToRiverDifferentiationBC{T} variables::SedimentToRiverDifferentiationVariables{T} end +"Initialize differentiated sediment reaching the river model" function SedimentToRiverDifferentiationModel(indices) n = length(indices) vars = SedimentToRiverDifferentiationVariables(n) @@ -127,6 +139,7 @@ function SedimentToRiverDifferentiationModel(indices) return model end +"Update differentiated sediment reaching the river model boundary conditions" function update_boundary_conditions!( model::SedimentToRiverDifferentiationModel, transport_model::SedimentLandTransportDifferentiationModel, @@ -145,6 +158,7 @@ function update_boundary_conditions!( @. deposition_lagg = transport_model.variables.deposition_lagg end +"Update differentiated sediment reaching the river model for a single timestep" function update!(model::SedimentToRiverDifferentiationModel, rivers) (; deposition_clay, diff --git a/src/sediment/sediment_transport/overland_flow_transport.jl b/src/sediment/sediment_transport/overland_flow_transport.jl index b4d0fd44b..b6ef17ff2 100644 --- a/src/sediment/sediment_transport/overland_flow_transport.jl +++ b/src/sediment/sediment_transport/overland_flow_transport.jl @@ -1,12 +1,13 @@ abstract type AbstractSedimentLandTransportModel{T} end -## Total sediment transport in overland flow structs and functions +"Struct to store total sediment flux in overland flow model variables" @get_units @grid_loc @with_kw struct SedimentLandTransportVariables{T} # Total sediment flux amount::Vector{T} | "t dt-1" deposition::Vector{T} | "t dt-1" end +"Initialize total sediment flux in overland flow model variables" function SedimentLandTransportVariables( n; amount::Vector{T} = fill(mv, n), @@ -15,6 +16,7 @@ function SedimentLandTransportVariables( return SedimentLandTransportVariables{T}(; amount = amount, deposition = deposition) end +"Struct to store total sediment flux in overland flow model boundary conditions" @get_units @grid_loc @with_kw struct SedimentLandTransportBC{T} # Eroded material erosion::Vector{T} | "t dt-1" @@ -22,6 +24,7 @@ end transport_capacity::Vector{T} | "t dt-1" end +"Initialize total sediment flux in overland flow model boundary conditions" function SedimentLandTransportBC( n; erosion::Vector{T} = fill(mv, n), @@ -33,11 +36,13 @@ function SedimentLandTransportBC( ) end +"Struct to store total sediment flux in overland flow model" @with_kw struct SedimentLandTransportModel{T} <: AbstractSedimentLandTransportModel{T} boundary_conditions::SedimentLandTransportBC{T} variables::SedimentLandTransportVariables{T} end +"Initialize total sediment flux in overland flow model" function SedimentLandTransportModel(indices) n = length(indices) vars = SedimentLandTransportVariables(n) @@ -46,6 +51,7 @@ function SedimentLandTransportModel(indices) return model end +"Update total sediment flux in overland flow model boundary conditions" function update_boundary_conditions!( model::SedimentLandTransportModel, erosion_model::SoilErosionModel, @@ -59,6 +65,7 @@ function update_boundary_conditions!( @. transport_capacity = amount end +"Update total sediment flux in overland flow model for a single timestep" function update!(model::SedimentLandTransportModel, network) (; erosion, transport_capacity) = model.boundary_conditions (; amount, deposition) = model.variables @@ -67,7 +74,7 @@ function update!(model::SedimentLandTransportModel, network) deposition .= erosion end -## Total transport capacity with particle differentiation structs and functions +"Struct to store differentiated sediment flux in overland flow model variables" @get_units @grid_loc @with_kw struct SedimentLandTransportDifferentiationVariables{T} # Total sediment flux amount::Vector{T} | "t dt-1" @@ -95,6 +102,7 @@ end deposition_lagg::Vector{T} | "t dt-1" end +"Initialize differentiated sediment flux in overland flow model variables" function SedimentLandTransportDifferentiationVariables( n; amount::Vector{T} = fill(mv, n), @@ -126,6 +134,7 @@ function SedimentLandTransportDifferentiationVariables( ) end +"Struct to store differentiated sediment flux in overland flow model boundary conditions" @get_units @grid_loc @with_kw struct SedimentLandTransportDifferentiationBC{T} # Eroded clay erosion_clay::Vector{T} | "t dt-1" @@ -149,6 +158,7 @@ end transport_capacity_lagg::Vector{T} | "t dt-1" end +"Initialize differentiated sediment flux in overland flow model boundary conditions" function SedimentLandTransportDifferentiationBC( n; erosion_clay::Vector{T} = fill(mv, n), @@ -176,12 +186,14 @@ function SedimentLandTransportDifferentiationBC( ) end +"Struct to store differentiated sediment flux in overland flow model" @with_kw struct SedimentLandTransportDifferentiationModel{T} <: AbstractSedimentLandTransportModel{T} boundary_conditions::SedimentLandTransportDifferentiationBC{T} variables::SedimentLandTransportDifferentiationVariables{T} end +"Initialize differentiated sediment flux in overland flow model" function SedimentLandTransportDifferentiationModel(indices) n = length(indices) vars = SedimentLandTransportDifferentiationVariables(n) @@ -193,6 +205,7 @@ function SedimentLandTransportDifferentiationModel(indices) return model end +"Update differentiated sediment flux in overland flow model boundary conditions" function update_boundary_conditions!( model::SedimentLandTransportDifferentiationModel, erosion_model::SoilErosionModel, @@ -225,6 +238,7 @@ function update_boundary_conditions!( @. transport_capacity_lagg = lagg end +"Update differentiated sediment flux in overland flow model for a single timestep" function update!(model::SedimentLandTransportDifferentiationModel, network) (; erosion_clay, diff --git a/src/sediment/sediment_transport/river_transport.jl b/src/sediment/sediment_transport/river_transport.jl index 9c87e9ee3..1e9d711a3 100644 --- a/src/sediment/sediment_transport/river_transport.jl +++ b/src/sediment/sediment_transport/river_transport.jl @@ -1,6 +1,6 @@ abstract type AbstractSedimentRiverTransportModel{T} end -## Total sediment transport in overland flow structs and functions +"Struct to store river sediment transport model variables" @get_units @grid_loc @with_kw struct SedimentRiverTransportVariables{T} # Sediment flux [ton] amount::Vector{T} | "t dt-1" @@ -30,6 +30,7 @@ abstract type AbstractSedimentRiverTransportModel{T} end store_gravel::Vector{T} | "t dt-1" end +"Initialize river sediment transport model variables" function SedimentRiverTransportVariables( n; amount::Vector{T} = fill(mv, n), @@ -79,6 +80,7 @@ function SedimentRiverTransportVariables( ) end +"Struct to store river sediment transport model boundary conditions" @get_units @grid_loc @with_kw struct SedimentRiverTransportBC{T} # Waterlevel waterlevel::Vector{T} | "t dt-1" @@ -97,6 +99,7 @@ end potential_erosion_river_bank::Vector{T} | "t dt-1" end +"Initialize river sediment transport model boundary conditions" function SedimentRiverTransportBC( n; waterlevel::Vector{T} = fill(mv, n), @@ -124,7 +127,7 @@ function SedimentRiverTransportBC( ) end -# Parameters for river transport +"Struct to store river sediment transport model parameters" @get_units @grid_loc @with_kw struct SedimentRiverTransportParameters{T} # River bed/bank content clay clay_fraction::Vector{T} | "-" @@ -154,6 +157,7 @@ end waterbodies_trapping_efficiency::Vector{T} | "-" end +"Initialize river sediment transport model parameters" function SedimentRiverTransportParameters(dataset, config, indices) n = length(indices) clay_fraction = ncread( @@ -324,12 +328,14 @@ function SedimentRiverTransportParameters(dataset, config, indices) return river_parameters end +"Struct to store river sediment transport model" @with_kw struct SedimentRiverTransportModel{T} <: AbstractSedimentRiverTransportModel{T} boundary_conditions::SedimentRiverTransportBC{T} parameters::SedimentRiverTransportParameters{T} variables::SedimentRiverTransportVariables{T} end +"Initialize river sediment transport model" function SedimentRiverTransportModel(dataset, config, indices) n = length(indices) vars = SedimentRiverTransportVariables(n) @@ -343,6 +349,7 @@ function SedimentRiverTransportModel(dataset, config, indices) return model end +"Update boundary conditions for river sediment transport model" function update_boundary_conditions!( model::SedimentRiverTransportModel, hydrological_forcing::HydrologicalForcing, @@ -382,6 +389,7 @@ function update_boundary_conditions!( @. potential_erosion_river_bank = potential_erosion_model.variables.bank end +"Update river sediment transport model for a single timestep" function update!( model::SedimentRiverTransportModel, network, @@ -772,7 +780,7 @@ end abstract type AbstractSedimentConcentrationsRiverModel{T} end -## Total sediment transport in overland flow structs and functions +"Struct to store river sediment concentrations model variables" @get_units @grid_loc @with_kw struct SedimentConcentrationsRiverVariables{T} # Total sediment concentration in the river total::Vector{T} | "g m-3" @@ -782,6 +790,7 @@ abstract type AbstractSedimentConcentrationsRiverModel{T} end bed::Vector{T} | "g m-3" end +"Initialize river sediment concentrations model variables" function SedimentConcentrationsRiverVariables( n; total::Vector{T} = fill(mv, n), @@ -795,6 +804,7 @@ function SedimentConcentrationsRiverVariables( ) end +"Struct to store river sediment concentrations model boundary conditions" @get_units @grid_loc @with_kw struct SedimentConcentrationsRiverBC{T} # Discharge q::Vector{T} | "m3 s-1" @@ -813,6 +823,7 @@ end gravel::Vector{T} | "g m-3" end +"Initialize river sediment concentrations model boundary conditions" function SedimentConcentrationsRiverBC( n; q::Vector{T} = fill(mv, n), @@ -836,7 +847,7 @@ function SedimentConcentrationsRiverBC( ) end -# Common parameters for transport capacity models +"Struct to store river sediment concentrations model parameters" @get_units @grid_loc @with_kw struct SedimentConcentrationsRiverParameters{T} # Clay mean diameter dm_clay::Vector{T} | "µm" @@ -852,6 +863,7 @@ end dm_gravel::Vector{T} | "µm" end +"Initialize river sediment concentrations model parameters" function SedimentConcentrationsRiverParameters(dataset, config, indices) dm_clay = ncread( dataset, @@ -913,6 +925,7 @@ function SedimentConcentrationsRiverParameters(dataset, config, indices) return conc_parameters end +"Struct to store river sediment concentrations model" @with_kw struct SedimentConcentrationsRiverModel{T} <: AbstractSedimentConcentrationsRiverModel{T} boundary_conditions::SedimentConcentrationsRiverBC{T} @@ -920,6 +933,7 @@ end variables::SedimentConcentrationsRiverVariables{T} end +"Initialize river sediment concentrations model" function SedimentConcentrationsRiverModel(dataset, config, indices) n = length(indices) vars = SedimentConcentrationsRiverVariables(n) @@ -933,6 +947,7 @@ function SedimentConcentrationsRiverModel(dataset, config, indices) return model end +"Update boundary conditions for river sediment concentrations model" function update_boundary_conditions!( model::SedimentConcentrationsRiverModel, hydrological_forcing::HydrologicalForcing, @@ -952,6 +967,7 @@ function update_boundary_conditions!( @. gravel = sediment_flux_model.variables.gravel end +"Update river sediment concentrations model for a single timestep" function update!(model::SedimentConcentrationsRiverModel, geometry::RiverParameters, dt) (; q, waterlevel, clay, silt, sand, sagg, lagg, gravel) = model.boundary_conditions (; dm_clay, dm_silt, dm_sand, dm_sagg, dm_lagg, dm_gravel) = model.parameters diff --git a/src/sediment/sediment_transport/transport_capacity.jl b/src/sediment/sediment_transport/transport_capacity.jl index dbad7cf91..82a52eb42 100644 --- a/src/sediment/sediment_transport/transport_capacity.jl +++ b/src/sediment/sediment_transport/transport_capacity.jl @@ -1,15 +1,17 @@ abstract type AbstractTransportCapacityModel{T} end -## Total sediment transport capacity structs and functions +"Struct to store total transport capacity model variables" @get_units @grid_loc @with_kw struct TransportCapacityModelVariables{T} # Total sediment transport capacity amount::Vector{T} | "t dt-1" end +"Initialize total transport capacity model variables" function TransportCapacityModelVariables(n; amount::Vector{T} = fill(mv, n)) where {T} return TransportCapacityModelVariables{T}(; amount = amount) end +"Struct to store total transport capacity model boundary conditions" @get_units @grid_loc @with_kw struct TransportCapacityBC{T} # Discharge q::Vector{T} | "m3 s-1" @@ -17,6 +19,7 @@ end waterlevel::Vector{T} | "m" end +"Initialize total transport capacity model boundary conditions" function TransportCapacityBC( n; q::Vector{T} = fill(mv, n), @@ -25,6 +28,7 @@ function TransportCapacityBC( return TransportCapacityBC{T}(; q = q, waterlevel = waterlevel) end +"Update total transport capacity model boundary conditions" function update_boundary_conditions!( model::AbstractTransportCapacityModel, hydrological_forcing::HydrologicalForcing, @@ -44,7 +48,7 @@ end ##################### Overland Flow ##################### -# Govers parameters for transport capacity models +"Struct to store Govers overland flow transport capacity model parameters" @get_units @grid_loc @with_kw struct TransportCapacityGoversParameters{T} # Drain slope slope::Vector{T} | "m m-1" @@ -56,6 +60,7 @@ end n_govers::Vector{T} | "-" end +"Initialize Govers overland flow transport capacity model parameters" function TransportCapacityGoversParameters(dataset, config, indices) slope = ncread( dataset, @@ -99,12 +104,14 @@ function TransportCapacityGoversParameters(dataset, config, indices) return tc_parameters end +"Govers overland flow transport capacity model" @with_kw struct TransportCapacityGoversModel{T} <: AbstractTransportCapacityModel{T} boundary_conditions::TransportCapacityBC{T} parameters::TransportCapacityGoversParameters{T} variables::TransportCapacityModelVariables{T} end +"Initialize Govers overland flow transport capacity model" function TransportCapacityGoversModel(dataset, config, indices) n = length(indices) vars = TransportCapacityModelVariables(n) @@ -118,6 +125,7 @@ function TransportCapacityGoversModel(dataset, config, indices) return model end +"Update Govers overland flow transport capacity model for a single timestep" function update!(model::TransportCapacityGoversModel, width, waterbodies, rivers, dt) (; q, waterlevel) = model.boundary_conditions (; slope, density, c_govers, n_govers) = model.parameters @@ -140,7 +148,7 @@ function update!(model::TransportCapacityGoversModel, width, waterbodies, rivers end end -# Common parameters for transport capacity models +"Struct to store Yalin overland flow transport capacity model parameters" @get_units @grid_loc @with_kw struct TransportCapacityYalinParameters{T} # Drain slope slope::Vector{T} | "m m-1" @@ -150,6 +158,7 @@ end d50::Vector{T} | "mm" end +"Initialize Yalin overland flow transport capacity model parameters" function TransportCapacityYalinParameters(dataset, config, indices) slope = ncread( dataset, @@ -181,12 +190,14 @@ function TransportCapacityYalinParameters(dataset, config, indices) return tc_parameters end +"Yalin overland flow transport capacity model" @with_kw struct TransportCapacityYalinModel{T} <: AbstractTransportCapacityModel{T} boundary_conditions::TransportCapacityBC{T} parameters::TransportCapacityYalinParameters{T} variables::TransportCapacityModelVariables{T} end +"Initialize Yalin overland flow transport capacity model" function TransportCapacityYalinModel(dataset, config, indices) n = length(indices) vars = TransportCapacityModelVariables(n) @@ -200,6 +211,7 @@ function TransportCapacityYalinModel(dataset, config, indices) return model end +"Update Yalin overland flow transport capacity model for a single timestep" function update!(model::TransportCapacityYalinModel, width, waterbodies, rivers, dt) (; q, waterlevel) = model.boundary_conditions (; slope, density, d50) = model.parameters @@ -221,7 +233,7 @@ function update!(model::TransportCapacityYalinModel, width, waterbodies, rivers, end end -## Total transport capacity with particle differentiation structs and functions +"Struct to store Yalin differentiated overland flow transport capacity model variables" @get_units @grid_loc @with_kw struct TransportCapacityYalinDifferentiationModelVariables{T} # Total sediment transport capacity amount::Vector{T} | "t dt-1" @@ -237,6 +249,7 @@ end lagg::Vector{T} | "t dt-1" end +"Initialize Yalin differentiated overland flow transport capacity model variables" function TransportCapacityYalinDifferentiationModelVariables( n; amount::Vector{T} = fill(mv, n), @@ -256,7 +269,7 @@ function TransportCapacityYalinDifferentiationModelVariables( ) end -# Common parameters for transport capacity models +"Struct to store Yalin differentiated overland flow transport capacity model parameters" @get_units @grid_loc @with_kw struct TransportCapacityYalinDifferentiationParameters{T} # Particle density density::Vector{T} | "kg m-3" @@ -272,6 +285,7 @@ end dm_lagg::Vector{T} | "µm" end +"Initialize Yalin differentiated overland flow transport capacity model parameters" function TransportCapacityYalinDifferentiationParameters(dataset, config, indices) density = ncread( dataset, @@ -333,6 +347,7 @@ function TransportCapacityYalinDifferentiationParameters(dataset, config, indice return tc_parameters end +"Yalin differentiated overland flow transport capacity model" @with_kw struct TransportCapacityYalinDifferentiationModel{T} <: AbstractTransportCapacityModel{T} boundary_conditions::TransportCapacityBC{T} @@ -340,6 +355,7 @@ end variables::TransportCapacityYalinDifferentiationModelVariables{T} end +"Initialize Yalin differentiated overland flow transport capacity model" function TransportCapacityYalinDifferentiationModel(dataset, config, indices) n = length(indices) vars = TransportCapacityYalinDifferentiationModelVariables(n) @@ -353,6 +369,7 @@ function TransportCapacityYalinDifferentiationModel(dataset, config, indices) return model end +"Update Yalin differentiated overland flow transport capacity model for a single timestep" function update!( model::TransportCapacityYalinDifferentiationModel, geometry::LandParameters, @@ -440,7 +457,7 @@ function update!( end end -##################### River Flow ##################### +"Struct to store common river transport capacity model parameters" @get_units @grid_loc @with_kw struct TransportCapacityRiverParameters{T} # Particle density density::Vector{T} | "kg m-3" @@ -448,6 +465,7 @@ end d50::Vector{T} | "mm" end +"Initialize common river transport capacity model parameters" function TransportCapacityRiverParameters(dataset, config, indices) density = ncread( dataset, @@ -470,7 +488,7 @@ function TransportCapacityRiverParameters(dataset, config, indices) return tc_parameters end -# Bagnold parameters for transport capacity models +"Struct to store Bagnold transport capacity model parameters" @get_units @grid_loc @with_kw struct TransportCapacityBagnoldParameters{T} # Bagnold transport capacity coefficient c_bagnold::Vector{T} | "-" @@ -478,6 +496,7 @@ end e_bagnold::Vector{T} | "-" end +"Initialize Bagnold transport capacity model parameters" function TransportCapacityBagnoldParameters(dataset, config, indices) c_bagnold = ncread( dataset, @@ -501,12 +520,14 @@ function TransportCapacityBagnoldParameters(dataset, config, indices) return tc_parameters end +"Bagnold river transport capacity model" @with_kw struct TransportCapacityBagnoldModel{T} <: AbstractTransportCapacityModel{T} boundary_conditions::TransportCapacityBC{T} parameters::TransportCapacityBagnoldParameters{T} variables::TransportCapacityModelVariables{T} end +"Initialize Bagnold river transport capacity model" function TransportCapacityBagnoldModel(dataset, config, indices) n = length(indices) vars = TransportCapacityModelVariables(n) @@ -520,6 +541,7 @@ function TransportCapacityBagnoldModel(dataset, config, indices) return model end +"Update Bagnold river transport capacity model for a single timestep" function update!(model::TransportCapacityBagnoldModel, geometry::RiverParameters, dt) (; q, waterlevel) = model.boundary_conditions (; c_bagnold, e_bagnold) = model.parameters @@ -541,13 +563,14 @@ function update!(model::TransportCapacityBagnoldModel, geometry::RiverParameters end end -# Engelund and Hansen parameters for transport capacity models +"Engelund and Hansen river transport capacity model parameters" @with_kw struct TransportCapacityEngelundModel{T} <: AbstractTransportCapacityModel{T} boundary_conditions::TransportCapacityBC{T} parameters::TransportCapacityRiverParameters{T} variables::TransportCapacityModelVariables{T} end +"Initialize Engelund and Hansen river transport capacity model" function TransportCapacityEngelundModel(dataset, config, indices) n = length(indices) vars = TransportCapacityModelVariables(n) @@ -561,6 +584,7 @@ function TransportCapacityEngelundModel(dataset, config, indices) return model end +"Update Engelund and Hansen river transport capacity model for a single timestep" function update!(model::TransportCapacityEngelundModel, geometry::RiverParameters, dt) (; q, waterlevel) = model.boundary_conditions (; density, d50) = model.parameters @@ -581,7 +605,7 @@ function update!(model::TransportCapacityEngelundModel, geometry::RiverParameter end end -# Kodatie parameters for transport capacity models +"Struct to store Kodatie river transport capacity model parameters" @get_units @grid_loc @with_kw struct TransportCapacityKodatieParameters{T} # Kodatie transport capacity coefficient a a_kodatie::Vector{T} | "-" @@ -593,6 +617,7 @@ end d_kodatie::Vector{T} | "-" end +"Initialize Kodatie river transport capacity model parameters" function TransportCapacityKodatieParameters(dataset, config, indices) a_kodatie = ncread( dataset, @@ -636,12 +661,14 @@ function TransportCapacityKodatieParameters(dataset, config, indices) return tc_parameters end +"Kodatie river transport capacity model" @with_kw struct TransportCapacityKodatieModel{T} <: AbstractTransportCapacityModel{T} boundary_conditions::TransportCapacityBC{T} parameters::TransportCapacityKodatieParameters{T} variables::TransportCapacityModelVariables{T} end +"Initialize Kodatie river transport capacity model" function TransportCapacityKodatieModel(dataset, config, indices) n = length(indices) vars = TransportCapacityModelVariables(n) @@ -655,6 +682,7 @@ function TransportCapacityKodatieModel(dataset, config, indices) return model end +"Update Kodatie river transport capacity model for a single timestep" function update!(model::TransportCapacityKodatieModel, geometry::RiverParameters, dt) (; q, waterlevel) = model.boundary_conditions (; a_kodatie, b_kodatie, c_kodatie, d_kodatie) = model.parameters @@ -677,13 +705,14 @@ function update!(model::TransportCapacityKodatieModel, geometry::RiverParameters end end -# Yang parameters for transport capacity models +"Yang river transport capacity model" @with_kw struct TransportCapacityYangModel{T} <: AbstractTransportCapacityModel{T} boundary_conditions::TransportCapacityBC{T} parameters::TransportCapacityRiverParameters{T} variables::TransportCapacityModelVariables{T} end +"Initialize Yang river transport capacity model" function TransportCapacityYangModel(dataset, config, indices) n = length(indices) vars = TransportCapacityModelVariables(n) @@ -697,6 +726,7 @@ function TransportCapacityYangModel(dataset, config, indices) return model end +"Update Yang river transport capacity model for a single timestep" function update!(model::TransportCapacityYangModel, geometry::RiverParameters, dt) (; q, waterlevel) = model.boundary_conditions (; density, d50) = model.parameters @@ -717,13 +747,14 @@ function update!(model::TransportCapacityYangModel, geometry::RiverParameters, d end end -# Molinas and Wu parameters for transport capacity models +"Molinas and Wu river transport capacity model" @with_kw struct TransportCapacityMolinasModel{T} <: AbstractTransportCapacityModel{T} boundary_conditions::TransportCapacityBC{T} parameters::TransportCapacityRiverParameters{T} variables::TransportCapacityModelVariables{T} end +"Initialize Molinas and Wu river transport capacity model" function TransportCapacityMolinasModel(dataset, config, indices) n = length(indices) vars = TransportCapacityModelVariables(n) @@ -737,6 +768,7 @@ function TransportCapacityMolinasModel(dataset, config, indices) return model end +"Update Molinas and Wu river transport capacity model for a single timestep" function update!(model::TransportCapacityMolinasModel, geometry::RiverParameters, dt) (; q, waterlevel) = model.boundary_conditions (; density, d50) = model.parameters diff --git a/src/sediment_flux.jl b/src/sediment_flux.jl index 753299303..24117e44b 100644 --- a/src/sediment_flux.jl +++ b/src/sediment_flux.jl @@ -1,4 +1,4 @@ -### Overland flow ### +"Sediment transport in overland flow model" @get_units @grid_loc @with_kw struct OverlandFlowSediment{TT, SF, TR} hydrological_forcing::HydrologicalForcing geometry::LandParameters @@ -9,6 +9,7 @@ rivers::Vector{Bool} | "-" end +"Initialize the overland flow sediment transport model" function OverlandFlowSediment(dataset, config, indices, waterbodies, rivers) n = length(indices) hydrological_forcing = HydrologicalForcing(n) @@ -53,6 +54,7 @@ function OverlandFlowSediment(dataset, config, indices, waterbodies, rivers) return overland_flow_sediment end +"Update the overland flow sediment transport model for a single timestep" function update!(model::OverlandFlowSediment, erosion_model::SoilErosionModel, network, dt) # Transport capacity update_boundary_conditions!(model.transport_capacity, model.hydrological_forcing, :land) @@ -74,6 +76,7 @@ function update!(model::OverlandFlowSediment, erosion_model::SoilErosionModel, n end ### River ### +"Sediment transport in river model" @get_units @grid_loc @with_kw struct RiverSediment{TTR, ER, SFR, CR} hydrological_forcing::HydrologicalForcing geometry::RiverParameters @@ -84,6 +87,7 @@ end waterbodies::Vector{Bool} | "-" end +"Initialize the river sediment transport model" function RiverSediment(dataset, config, indices, waterbodies) n = length(indices) hydrological_forcing = HydrologicalForcing(n) @@ -132,6 +136,7 @@ function RiverSediment(dataset, config, indices, waterbodies) return river_sediment end +"Update the river sediment transport model for a single timestep" function update!( model::RiverSediment, to_river_model::SedimentToRiverDifferentiationModel, diff --git a/src/sediment_model.jl b/src/sediment_model.jl index 1cceab4d7..81d722cd2 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -150,6 +150,7 @@ function initialize_sediment_model(config::Config) return model end +"update sediment model for a single timestep" function update!(model::Model{N, L, V, R, W, T}) where {N, L, V, R, W, T <: SedimentModel} (; lateral, vertical, network, config, clock) = model dt = tosecond(clock.dt) @@ -170,6 +171,7 @@ function update!(model::Model{N, L, V, R, W, T}) where {N, L, V, R, W, T <: Sedi return nothing end +"set the initial states of the sediment model" function set_states!( model::Model{N, L, V, R, W, T}, ) where {N, L, V, R, W, T <: SedimentModel} From 36c155e5b99d0e8d4c56b61158d0bcc57a5e4c00 Mon Sep 17 00:00:00 2001 From: hboisgon Date: Thu, 5 Dec 2024 13:28:46 +0800 Subject: [PATCH 39/42] improve test coverage --- src/forcing.jl | 11 +- src/sediment/erosion/rainfall_erosion.jl | 5 +- .../sediment_transport/transport_capacity.jl | 52 +++--- src/sediment_flux.jl | 6 +- src/sediment_model.jl | 4 +- test/run_sediment.jl | 172 +++++++++++++++++- test/sediment_config.toml | 24 +-- test/sediment_eurosem_engelund_config.toml | 170 +++++++++++++++++ 8 files changed, 385 insertions(+), 59 deletions(-) create mode 100644 test/sediment_eurosem_engelund_config.toml diff --git a/src/forcing.jl b/src/forcing.jl index eb7241df6..68f94bb20 100644 --- a/src/forcing.jl +++ b/src/forcing.jl @@ -20,6 +20,8 @@ end "Struct to store hydrological forcing variables" @get_units @grid_loc @with_kw struct HydrologicalForcing{T} + # Rainfall interception by the vegetation [mm] + interception::Vector{T} | "mm" # Overland flow depth [m] waterlevel_land::Vector{T} | "m" # Overland flow discharge [m3 s-1] @@ -33,10 +35,17 @@ end "Initialize hydrological forcing" function HydrologicalForcing( n; + interception::Vector{T} = fill(mv, n), waterlevel_land::Vector{T} = fill(mv, n), q_land::Vector{T} = fill(mv, n), waterlevel_river::Vector{T} = fill(mv, n), q_river::Vector{T} = fill(mv, n), ) where {T} - return HydrologicalForcing{T}(; waterlevel_land, q_land, waterlevel_river, q_river) + return HydrologicalForcing{T}(; + interception, + waterlevel_land, + q_land, + waterlevel_river, + q_river, + ) end \ No newline at end of file diff --git a/src/sediment/erosion/rainfall_erosion.jl b/src/sediment/erosion/rainfall_erosion.jl index a94b7ee2f..8e5c5b150 100644 --- a/src/sediment/erosion/rainfall_erosion.jl +++ b/src/sediment/erosion/rainfall_erosion.jl @@ -25,7 +25,7 @@ end function RainfallErosionEurosemBC( n; precipitation::Vector{T} = fill(mv, n), - interception::Vector{T} = fill(0.0, n), + interception::Vector{T} = fill(mv, n), waterlevel::Vector{T} = fill(mv, n), ) where {T} return RainfallErosionEurosemBC{T}(; @@ -128,9 +128,10 @@ function update_boundary_conditions!( atmospheric_forcing::AtmosphericForcing, hydrological_forcing::HydrologicalForcing, ) - (; precipitation, waterlevel) = model.boundary_conditions + (; precipitation, interception, waterlevel) = model.boundary_conditions @. precipitation = atmospheric_forcing.precipitation @. waterlevel = hydrological_forcing.waterlevel_land + @. interception = hydrological_forcing.interception end "Update EUROSEM rainfall erosion model for a single timestep" diff --git a/src/sediment/sediment_transport/transport_capacity.jl b/src/sediment/sediment_transport/transport_capacity.jl index 82a52eb42..02772dafb 100644 --- a/src/sediment/sediment_transport/transport_capacity.jl +++ b/src/sediment/sediment_transport/transport_capacity.jl @@ -50,8 +50,6 @@ end "Struct to store Govers overland flow transport capacity model parameters" @get_units @grid_loc @with_kw struct TransportCapacityGoversParameters{T} - # Drain slope - slope::Vector{T} | "m m-1" # Particle density density::Vector{T} | "kg m-3" # Govers transport capacity coefficient @@ -62,14 +60,6 @@ end "Initialize Govers overland flow transport capacity model parameters" function TransportCapacityGoversParameters(dataset, config, indices) - slope = ncread( - dataset, - config, - "lateral.land.transport_capacity.parameters.slope"; - sel = indices, - defaults = 0.01, - type = Float, - ) density = ncread( dataset, config, @@ -95,7 +85,6 @@ function TransportCapacityGoversParameters(dataset, config, indices) type = Float, ) tc_parameters = TransportCapacityGoversParameters(; - slope = slope, density = density, c_govers = c_govers, n_govers = n_govers, @@ -126,9 +115,15 @@ function TransportCapacityGoversModel(dataset, config, indices) end "Update Govers overland flow transport capacity model for a single timestep" -function update!(model::TransportCapacityGoversModel, width, waterbodies, rivers, dt) +function update!( + model::TransportCapacityGoversModel, + geometry::LandParameters, + waterbodies, + rivers, + dt, +) (; q, waterlevel) = model.boundary_conditions - (; slope, density, c_govers, n_govers) = model.parameters + (; density, c_govers, n_govers) = model.parameters (; amount) = model.variables n = length(q) @@ -139,8 +134,8 @@ function update!(model::TransportCapacityGoversModel, width, waterbodies, rivers c_govers[i], n_govers[i], density[i], - slope[i], - width[i], + geometry.slope[i], + geometry.width[i], waterbodies[i], rivers[i], dt, @@ -150,8 +145,6 @@ end "Struct to store Yalin overland flow transport capacity model parameters" @get_units @grid_loc @with_kw struct TransportCapacityYalinParameters{T} - # Drain slope - slope::Vector{T} | "m m-1" # Particle density density::Vector{T} | "kg m-3" # Particle mean diameter @@ -160,14 +153,6 @@ end "Initialize Yalin overland flow transport capacity model parameters" function TransportCapacityYalinParameters(dataset, config, indices) - slope = ncread( - dataset, - config, - "lateral.land.transport_capacity.parameters.slope"; - sel = indices, - defaults = 0.01, - type = Float, - ) density = ncread( dataset, config, @@ -184,8 +169,7 @@ function TransportCapacityYalinParameters(dataset, config, indices) defaults = 0.1, type = Float, ) - tc_parameters = - TransportCapacityYalinParameters(; slope = slope, density = density, d50 = d50) + tc_parameters = TransportCapacityYalinParameters(; density = density, d50 = d50) return tc_parameters end @@ -212,9 +196,15 @@ function TransportCapacityYalinModel(dataset, config, indices) end "Update Yalin overland flow transport capacity model for a single timestep" -function update!(model::TransportCapacityYalinModel, width, waterbodies, rivers, dt) +function update!( + model::TransportCapacityYalinModel, + geometry::LandParameters, + waterbodies, + rivers, + dt, +) (; q, waterlevel) = model.boundary_conditions - (; slope, density, d50) = model.parameters + (; density, d50) = model.parameters (; amount) = model.variables n = length(q) @@ -224,8 +214,8 @@ function update!(model::TransportCapacityYalinModel, width, waterbodies, rivers, waterlevel[i], density[i], d50[i], - slope[i], - width[i], + geometry.slope[i], + geometry.width[i], waterbodies[i], rivers[i], dt, diff --git a/src/sediment_flux.jl b/src/sediment_flux.jl index 24117e44b..e789ebae7 100644 --- a/src/sediment_flux.jl +++ b/src/sediment_flux.jl @@ -15,9 +15,9 @@ function OverlandFlowSediment(dataset, config, indices, waterbodies, rivers) hydrological_forcing = HydrologicalForcing(n) geometry = LandParameters(dataset, config, indices) # Check what transport capacity equation will be used - do_river = get(config.model, "runrivermodel", false)::Bool + do_river = get(config.model, "run_river_model", false)::Bool # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] - landtransportmethod = get(config.model, "landtransportmethod", "yalinpart")::String + landtransportmethod = get(config.model, "land_transport", "yalinpart")::String if do_river || landtransportmethod == "yalinpart" transport_capacity_model = @@ -95,7 +95,7 @@ function RiverSediment(dataset, config, indices, waterbodies) # Check what transport capacity equation will be used # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] - transport_method = get(config.model, "rivtransportmethod", "bagnold")::String + transport_method = get(config.model, "river_transport", "bagnold")::String if transport_method == "bagnold" transport_capacity_model = TransportCapacityBagnoldModel(dataset, config, indices) elseif transport_method == "engelund" diff --git a/src/sediment_model.jl b/src/sediment_model.jl index 81d722cd2..d18c14fc6 100644 --- a/src/sediment_model.jl +++ b/src/sediment_model.jl @@ -77,7 +77,7 @@ function initialize_sediment_model(config::Config) graph = flowgraph(ldd, indices, pcr_dir) # River processes - do_river = get(config.model, "runrivermodel", false)::Bool + do_river = get(config.model, "run_river_model", false)::Bool # TODO: see if we can skip init if the river model is not needed # or if we leave it when we restructure the Wflow Model struct @@ -162,7 +162,7 @@ function update!(model::Model{N, L, V, R, W, T}) where {N, L, V, R, W, T <: Sedi update!(lateral.land, vertical.soil_erosion, network.land, dt) # River sediment transport - do_river = get(config.model, "runrivermodel", false)::Bool + do_river = get(config.model, "run_river_model", false)::Bool if do_river indices_riv = network.index_river update!(lateral.river, lateral.land.to_river, network.river, indices_riv, dt) diff --git a/test/run_sediment.jl b/test/run_sediment.jl index a2c9f5805..603642e23 100644 --- a/test/run_sediment.jl +++ b/test/run_sediment.jl @@ -19,8 +19,6 @@ Wflow.run_timestep!(model) @test eros.overland_flow_erosion.variables.amount[1] ≈ 0.0f0 @test eros.rainfall_erosion.variables.amount[1] ≈ 0.00027245577922893746f0 @test model.clock.iteration == 1 - #@test mean(eros.leaf_area_index) ≈ 1.7120018886212223f0 - #@test mean(eros.interception) ≈ 0.4767846753916875f0 @test mean(eros.overland_flow_erosion.variables.amount) ≈ 0.00861079076689589f0 @test mean(eros.rainfall_erosion.variables.amount) ≈ 0.00016326203201620437f0 @test mean(eros.soil_erosion.variables.amount) ≈ 0.008774052798912092f0 @@ -81,3 +79,173 @@ end end Wflow.close_files(model) + +### Test the sediment model with a different configuration file ### +tomlpath = joinpath(@__DIR__, "sediment_eurosem_engelund_config.toml") +config = Wflow.Config(tomlpath) + +model = Wflow.initialize_sediment_model(config) +(; network) = model + +Wflow.run_timestep!(model) + +@testset "first timestep sediment model eurosem (vertical)" begin + eros = model.vertical + + @test eros.atmospheric_forcing.precipitation[1] ≈ 4.086122035980225f0 + @test eros.hydrological_forcing.interception[1] ≈ 0.6329902410507202f0 + @test eros.hydrological_forcing.q_land[1] ≈ 0.0f0 + @test eros.rainfall_erosion.parameters.soil_detachability[1] ≈ 2.0f0 + @test eros.rainfall_erosion.parameters.eurosem_exponent[1] ≈ 2.0f0 + @test eros.overland_flow_erosion.parameters.usle_c[1] ≈ 0.014194443821907043f0 + @test eros.overland_flow_erosion.variables.amount[1] ≈ 0.0f0 + @test eros.rainfall_erosion.variables.amount[1] ≈ 0.01232301374083337f0 + @test mean(eros.overland_flow_erosion.variables.amount) ≈ 0.00861079076689589f0 + @test mean(eros.rainfall_erosion.variables.amount) ≈ 0.0014726364432116048f0 + @test mean(eros.soil_erosion.variables.amount) ≈ 0.010083427210107495f0 +end + +# run the second timestep +Wflow.run_timestep!(model) + +@testset "second timestep sediment model engelund (lateral)" begin + land = model.lateral.land + river = model.lateral.river + + @test river.transport_capacity.parameters.d50[1] == 0.05000000074505806f0 + @test mean(river.transport_capacity.boundary_conditions.q) ≈ 0.6975180562953642f0 + @test mean(river.transport_capacity.variables.amount) ≈ 0.14184859055736687f0 + + @test mean(river.concentrations.variables.suspended) ≈ 0.24788001458305775f0 +end + +Wflow.close_files(model) + +### Test land only model configuration and transport capacity ### + +tomlpath = joinpath(@__DIR__, "sediment_eurosem_engelund_config.toml") +config = Wflow.Config(tomlpath) +# Update config to run only the land model +config.model.run_river_model = false +# Use govers equation for land transport capacity +config.model.land_transport = "govers" + +model = Wflow.initialize_sediment_model(config) +(; network) = model + +# run the first and second timestep +Wflow.run_timestep!(model) +Wflow.run_timestep!(model) + +@testset "second timestep sediment model govers" begin + eros = model.vertical + land = model.lateral.land + + @test mean(eros.soil_erosion.variables.amount) ≈ 0.0776983847440198f0 + @test mean(land.transport_capacity.parameters.c_govers) ≈ 0.16393911236592437f0 + @test mean(land.transport_capacity.variables.amount) ≈ 1.0988158364353527f6 + @test mean(land.to_river.variables.amount) ≈ 0.07708434959918917f0 +end + +Wflow.close_files(model) + +tomlpath = joinpath(@__DIR__, "sediment_eurosem_engelund_config.toml") +config = Wflow.Config(tomlpath) +# Update config to run only the land model +config.model.run_river_model = false +# Use yalin equation for land transport capacity +config.model.land_transport = "yalin" + +model = Wflow.initialize_sediment_model(config) +(; network) = model + +# run the first and second timestep +Wflow.run_timestep!(model) +Wflow.run_timestep!(model) + +@testset "second timestep sediment model yalin" begin + eros = model.vertical + land = model.lateral.land + + @test mean(eros.soil_erosion.variables.amount) ≈ 0.0776983847440198f0 + @test mean(land.transport_capacity.parameters.d50) ≈ 0.001534350291334408f0 + @test mean(land.transport_capacity.variables.amount) ≈ 1.0988158364353527f6 + @test mean(land.to_river.variables.amount) ≈ 0.07759391658531742f0 +end + +Wflow.close_files(model) + +### Test all river transport capacity ### + +tomlpath = joinpath(@__DIR__, "sediment_eurosem_engelund_config.toml") +config = Wflow.Config(tomlpath) +# Use yang equation for river transport capacity +config.model.river_transport = "yang" + +model = Wflow.initialize_sediment_model(config) +(; network) = model + +# run the first and second timestep +Wflow.run_timestep!(model) +Wflow.run_timestep!(model) + +@testset "second timestep sediment model yang (lateral)" begin + river = model.lateral.river + + @test river.transport_capacity.parameters.d50[1] == 0.05000000074505806f0 + @test mean(river.transport_capacity.boundary_conditions.q) ≈ 0.6975180562953642f0 + @test mean(river.transport_capacity.variables.amount) ≈ 39.959093179632234f0 + + @test mean(river.concentrations.variables.suspended) ≈ 0.004036947448899768f0 +end + +Wflow.close_files(model) + +tomlpath = joinpath(@__DIR__, "sediment_eurosem_engelund_config.toml") +config = Wflow.Config(tomlpath) +# Use kodatie equation for river transport capacity +config.model.river_transport = "kodatie" + +model = Wflow.initialize_sediment_model(config) +(; network) = model + +# run the first and second timestep +Wflow.run_timestep!(model) +Wflow.run_timestep!(model) + +@testset "second timestep sediment model kodatie (lateral)" begin + river = model.lateral.river + + @test river.transport_capacity.parameters.a_kodatie[1] == 2829.6 + @test river.transport_capacity.parameters.b_kodatie[1] == 3.646 + @test mean(river.transport_capacity.boundary_conditions.q) ≈ 0.6975180562953642f0 + @test mean(river.transport_capacity.variables.amount) ≈ 30.332588671299625f0 + + @test mean(river.concentrations.variables.suspended) ≈ 54.75483621753514f0 +end + +Wflow.close_files(model) + +tomlpath = joinpath(@__DIR__, "sediment_eurosem_engelund_config.toml") +config = Wflow.Config(tomlpath) +# Use molinas equation for river transport capacity +config.model.river_transport = "molinas" + +model = Wflow.initialize_sediment_model(config) +(; network) = model + +# run the first and second timestep +Wflow.run_timestep!(model) +Wflow.run_timestep!(model) + +@testset "second timestep sediment model molinas (lateral)" begin + river = model.lateral.river + + @test river.transport_capacity.parameters.d50[1] == 0.05000000074505806f0 + @test mean(river.transport_capacity.boundary_conditions.q) ≈ 0.6975180562953642f0 + @test mean(river.transport_capacity.variables.amount) ≈ 350.6483600591209f0 + + @test mean(river.concentrations.variables.suspended) ≈ 884.4249630198293f0 +end + +Wflow.close_files(model) \ No newline at end of file diff --git a/test/sediment_config.toml b/test/sediment_config.toml index 095752069..6839ee4af 100644 --- a/test/sediment_config.toml +++ b/test/sediment_config.toml @@ -53,7 +53,6 @@ lake_areas = "wflow_lakeareas" # 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.interception", "vertical.atmospheric_forcing.precipitation", "vertical.hydrological_forcing.waterlevel_land", "lateral.land.hydrological_forcing.waterlevel_land", @@ -63,22 +62,11 @@ forcing = [ "lateral.river.hydrological_forcing.q_river", ] -#cyclic = ["vertical.leaf_area_index"] - -#[input.vertical] -## interception parameters to be moved -#kext = "Kext" -#leaf_area_index = "LAI" # cyclic -#specific_leaf = "Sl" -#storage_wood = "Swood" -## other parameters - [input.vertical.atmospheric_forcing] precipitation = "P" [input.vertical.hydrological_forcing] waterlevel_land = "levKinL" -#interception = "int" q_land = "runL" [input.vertical.land_parameter_set] @@ -87,7 +75,6 @@ slope = "Slope" [input.vertical.rainfall_erosion.parameters] soil_detachability = "soil_detachability" eurosem_exponent = "eros_spl_EUROSEM" -canopyheight = "CanopyHeight" canopygapfraction = "CanopyGapFraction" usle_k = "usle_k" usle_c = "USLE_C" @@ -170,13 +157,14 @@ dm_lagg = "dm_lagg" dm_gravel = "dm_gravel" [model] +reinit = true +run_river_model = true dolake = false doreservoir = true # cannot use reservoirs as in sbm because then states/volume need to be added -landtransportmethod = "yalinpart" # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] -rainerosmethod = "answers" # Rainfall erosion equation: ["answers", "eurosem"] -reinit = true -rivtransportmethod = "bagnold" # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] -runrivermodel = true +rainfall_erosion = "answers" # Rainfall erosion equation: ["answers", "eurosem"] +overland_flow_erosion = "answers" # Overland flow erosion equation: ["answers"] +land_transport = "yalinpart" # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] +river_transport = "bagnold" # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] type = "sediment" [output] diff --git a/test/sediment_eurosem_engelund_config.toml b/test/sediment_eurosem_engelund_config.toml new file mode 100644 index 000000000..6c9fb3be8 --- /dev/null +++ b/test/sediment_eurosem_engelund_config.toml @@ -0,0 +1,170 @@ +# 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-01-03T00:00:00 +starttime = 1999-12-31T00:00:00 +time_units = "days since 1900-01-01 00:00:00" +timestepsecs = 86400 +dir_input = "data/input" +dir_output = "data/output" + +[state] +path_input = "instates-moselle-sed.nc" +path_output = "outstates-moselle-sed.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.lateral.river.sediment_flux.variables] +leftover_clay = "clayload" +store_clay = "claystore" +leftover_gravel = "gravload" +store_gravel = "gravstore" +leftover_lagg = "laggload" +store_lagg = "laggstore" +clay = "outclay" +gravel = "outgrav" +lagg= "outlagg" +sagg = "outsagg" +sand = "outsand" +silt = "outsilt" +leftover_sagg= "saggload" +store_sagg = "saggstore" +leftover_sand = "sandload" +store_sand = "sandstore" +leftover_silt = "siltload" +store_silt = "siltstore" + +[input] +path_forcing = "forcing-moselle-sed.nc" +path_static = "staticmaps-moselle-sed.nc" + +# these are not directly part of the model +gauges = "wflow_gauges" +ldd = "wflow_ldd" +river_location = "wflow_river" +subcatchment = "wflow_subcatch" +reservoir_areas = "wflow_reservoirareas" +lake_areas = "wflow_lakeareas" + +# 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.atmospheric_forcing.precipitation", + "vertical.hydrological_forcing.interception", + "vertical.hydrological_forcing.waterlevel_land", + "lateral.land.hydrological_forcing.waterlevel_land", + "vertical.hydrological_forcing.q_land", + "lateral.land.hydrological_forcing.q_land", + "lateral.river.hydrological_forcing.waterlevel_river", + "lateral.river.hydrological_forcing.q_river", +] + +[input.vertical.atmospheric_forcing] +precipitation = "P" + +[input.vertical.hydrological_forcing] +waterlevel_land = "levKinL" +q_land = "runL" +interception = "int" + +[input.vertical.land_parameter_set] +slope = "Slope" + +[input.vertical.rainfall_erosion.parameters] +soil_detachability = "soil_detachability" +eurosem_exponent = "eros_spl_EUROSEM" +canopyheight = "CanopyHeight" +usle_k = "usle_k" +usle_c = "USLE_C" +pathfrac = "PathFrac" + +[input.vertical.overland_flow_erosion.parameters] +usle_k = "usle_k" +usle_c = "USLE_C" +answers_k = "eros_ov" + +[input.vertical.soil_erosion.parameters] +clay_fraction = "fclay_soil" +silt_fraction = "fsilt_soil" +sand_fraction = "fsand_soil" +sagg_fraction = "fsagg_soil" +lagg_fraction = "flagg_soil" + +[input.lateral.land.hydrological_forcing] +waterlevel_land = "levKinL" +q_land = "runL" + +[input.lateral.land.transport_capacity.parameters] +density = "sediment_density" +d50 = "d50_soil" +c_govers = "c_govers" +n_govers = "n_govers" +dm_clay = "dm_clay" +dm_silt = "dm_silt" +dm_sand = "dm_sand" +dm_sagg = "dm_sagg" +dm_lagg = "dm_lagg" + +[input.lateral.river.hydrological_forcing] +waterlevel_river = "h" +q_river = "q" + +[input.lateral.river_parameter_set] +length = "wflow_riverlength" +slope = "RiverSlope" +width = "wflow_riverwidth" + +[input.lateral.river.transport_capacity.parameters] +density = "sediment_density" +d50 = "D50_River" +c_bagnold = "c_Bagnold" +e_bagnold = "exp_Bagnold" +a_kodatie.value = 2829.6 +b_kodatie.value = 3.646 +c_kodatie.value = 0.406 +d_kodatie.value = 0.412 + +[input.lateral.river.potential_erosion.parameters] +d50 = "D50_River" + +[input.lateral.river.sediment_flux.parameters] +clay_fraction = "ClayF_River" +silt_fraction = "SiltF_River" +sand_fraction = "SandF_River" +gravel_fraction = "GravelF_River" +dm_clay = "dm_clay" +dm_silt = "dm_silt" +dm_sand = "dm_sand" +dm_sagg = "dm_sagg" +dm_lagg = "dm_lagg" +dm_gravel = "dm_gravel" +# Reservoir +resarea = "ResSimpleArea" +restrapeff = "ResTrapEff" +reslocs = "wflow_reservoirlocs" +# Lake +lakearea = "LakeArea" +lakelocs = "wflow_lakelocs" + +[input.lateral.river.concentrations.parameters] +dm_clay = "dm_clay" +dm_silt = "dm_silt" +dm_sand = "dm_sand" +dm_sagg = "dm_sagg" +dm_lagg = "dm_lagg" +dm_gravel = "dm_gravel" + +[model] +reinit = true +run_river_model = true +dolake = false +doreservoir = true # cannot use reservoirs as in sbm because then states/volume need to be added +rainfall_erosion = "eurosem" # Rainfall erosion equation: ["answers", "eurosem"] +overland_flow_erosion = "answers" # Overland flow erosion equation: ["answers"] +land_transport = "yalinpart" # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] +river_transport = "engelund" # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] +type = "sediment" From 22c2f454932112b7277c39677ff7a62f266c5dfe Mon Sep 17 00:00:00 2001 From: hboisgon Date: Thu, 5 Dec 2024 13:30:19 +0800 Subject: [PATCH 40/42] undo skipping tests --- test/runtests.jl | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index fa8511d6e..05518f8b2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -78,20 +78,20 @@ include("testing_utils.jl") with_logger(NullLogger()) do ## run all tests @testset "Wflow.jl" begin - #include("horizontal_process.jl") - #include("io.jl") - #include("vertical_process.jl") - #include("reservoir_lake.jl") - #include("run_sbm.jl") - #include("run_sbm_piave.jl") - #include("run_sbm_gwf_piave.jl") - #include("run_sbm_gwf.jl") - #include("run.jl") - #include("groundwater.jl") - #include("utils.jl") - #include("bmi.jl") + include("horizontal_process.jl") + include("io.jl") + include("vertical_process.jl") + include("reservoir_lake.jl") + include("run_sbm.jl") + include("run_sbm_piave.jl") + include("run_sbm_gwf_piave.jl") + include("run_sbm_gwf.jl") + include("run.jl") + include("groundwater.jl") + include("utils.jl") + include("bmi.jl") include("run_sediment.jl") - #include("subdomains.jl") + include("subdomains.jl") Aqua.test_all(Wflow; ambiguities = false, persistent_tasks = false) end From 2246a3d0937cc989aa3b0ce807f4307f0e3b9163 Mon Sep 17 00:00:00 2001 From: hboisgon Date: Thu, 5 Dec 2024 14:31:18 +0800 Subject: [PATCH 41/42] bugfixes after merge + changelog --- docs/changelog.qmd | 5 +++++ src/groundwater/boundary_conditions.jl | 29 ++++++++++++++------------ src/parameters.jl | 2 +- src/sbm_gwf_model.jl | 2 +- src/states.jl | 7 +++++-- test/groundwater.jl | 6 +++--- 6 files changed, 31 insertions(+), 20 deletions(-) diff --git a/docs/changelog.qmd b/docs/changelog.qmd index 44d4798f7..6a138394e 100644 --- a/docs/changelog.qmd +++ b/docs/changelog.qmd @@ -38,6 +38,11 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). long update function of the `SBM` soil part has been split into separate functions. - Refactor the lateral (routing) components: as for the vertical `SBM` concept split the structs into `variables`, `parameters` and `boundary_conditions` (if applicable). +- Refactor the `sediment` concept: similar to the refactor of `SBM` concept, the sediment + model has been split into three main structures. `SoilLoss` was split into rainfall_erosion, + overland_flow_erosion and (total) soil_erosion concepts. `OverlandFlowSediment` has been + split into transport_capacity, sediment_flux and to_river concepts. `RiverSediment` has been + split into transport_capacity, potential_erosion, sediment_flux and concentrations concepts. - Timestepping method parameters for solving the kinematic wave and local inertial approaches for river and overland flow are moved to a `TimeStepping` struct. The timestepping implementation for the kinematic wave is now similar to the local inertial diff --git a/src/groundwater/boundary_conditions.jl b/src/groundwater/boundary_conditions.jl index 52927733c..fbbb04416 100644 --- a/src/groundwater/boundary_conditions.jl +++ b/src/groundwater/boundary_conditions.jl @@ -11,29 +11,29 @@ end # Do nothing for a confined aquifer: aquifer can always provide flux check_flux(flux, aquifer::ConfinedAquifer, index::Int) = flux -@get_units @grid_loc @with_kw struct RiverParameters{T} +@get_units @grid_loc @with_kw struct HyporheicParameters{T} infiltration_conductance::Vector{T} | "m2 d-1" exfiltration_conductance::Vector{T} | "m2 d-1" bottom::Vector{T} | "m" end -@get_units @grid_loc @with_kw struct RiverVariables{T} +@get_units @grid_loc @with_kw struct HyporheicVariables{T} stage::Vector{T} | "m" flux::Vector{T} | "m3 d-1" end -function RiverVariables(n) - variables = RiverVariables{Float}(; stage = fill(mv, n), flux = fill(mv, n)) +function HyporheicVariables(n) + variables = HyporheicVariables{Float}(; stage = fill(mv, n), flux = fill(mv, n)) return variables end -@get_units @grid_loc @with_kw struct River{T} <: AquiferBoundaryCondition - parameters::RiverParameters{T} - variables::RiverVariables{T} +@get_units @grid_loc @with_kw struct Hyporheic{T} <: AquiferBoundaryCondition + parameters::HyporheicParameters{T} + variables::HyporheicVariables{T} index::Vector{Int} | "-" end -function River(dataset, config, indices, index) +function Hyporheic(dataset, config, indices, index) infiltration_conductance = ncread( dataset, config, @@ -56,15 +56,18 @@ function River(dataset, config, indices, index) type = Float, ) - parameters = - RiverParameters{Float}(infiltration_conductance, exfiltration_conductance, bottom) + parameters = HyporheicParameters{Float}( + infiltration_conductance, + exfiltration_conductance, + bottom, + ) n = length(indices) - variables = RiverVariables(n) - river = River(parameters, variables, index) + variables = HyporheicVariables(n) + river = Hyporheic(parameters, variables, index) return river end -function flux!(Q, river::River, aquifer) +function flux!(Q, river::Hyporheic, aquifer) for (i, index) in enumerate(river.index) head = aquifer.variables.head[index] stage = river.variables.stage[i] diff --git a/src/parameters.jl b/src/parameters.jl index c75e568c9..4c723a9d7 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -123,7 +123,7 @@ function LandParameters(nc, config, inds) xl, yl = cell_lengths(y, cellength, sizeinmetres) area = xl .* yl ldd = ncread(nc, config, "ldd"; optional = false, sel = inds, allow_missing = true) - drain_width = map(detdrainwidth, ldd, xl, yl) + drain_width = map(get_flow_width, ldd, xl, yl) landslope = ncread( nc, config, diff --git a/src/sbm_gwf_model.jl b/src/sbm_gwf_model.jl index 31d037e5f..82a64adbc 100644 --- a/src/sbm_gwf_model.jl +++ b/src/sbm_gwf_model.jl @@ -254,7 +254,7 @@ function initialize_sbm_gwf_model(config::Config) ) # river boundary of unconfined aquifer - river = River(dataset, config, inds_river, inds_land_map2river) + river = Hyporheic(dataset, config, inds_river, inds_land_map2river) # recharge boundary of unconfined aquifer recharge = Recharge( diff --git a/src/states.jl b/src/states.jl index d0d824cbc..cc25adf5a 100644 --- a/src/states.jl +++ b/src/states.jl @@ -238,8 +238,11 @@ function extract_required_states(config::Config) river_states, ) else - required_states = - add_to_required_states(required_states, (:lateral, :river), river_states) + required_states = add_to_required_states( + required_states, + (:lateral, :river, :variables), + river_states, + ) end # Add floodplain states to dict required_states = add_to_required_states( diff --git a/test/groundwater.jl b/test/groundwater.jl index 427c4c96b..e7610b5d1 100644 --- a/test/groundwater.jl +++ b/test/groundwater.jl @@ -295,13 +295,13 @@ end end @testset "river" begin - parameters = Wflow.RiverParameters(; + parameters = Wflow.HyporheicParameters(; infiltration_conductance = [100.0, 100.0], exfiltration_conductance = [200.0, 200.0], bottom = [1.0, 1.0], ) - variables = Wflow.RiverVariables(; stage = [2.0, 2.0], flux = [0.0, 0.0]) - river = Wflow.River(; parameters, variables, index = [1, 3]) + variables = Wflow.HyporheicVariables(; stage = [2.0, 2.0], flux = [0.0, 0.0]) + river = Wflow.Hyporheic(; parameters, variables, index = [1, 3]) Q = zeros(3) Wflow.flux!(Q, river, conf_aqf) # infiltration, below bottom, flux is (stage - bottom) * inf_cond From af9dd7a626f7227f88c56d47a14c9301fc61c148 Mon Sep 17 00:00:00 2001 From: hboisgon Date: Fri, 6 Dec 2024 08:57:39 +0800 Subject: [PATCH 42/42] use geometry again --- src/erosion.jl | 4 +-- src/groundwater/boundary_conditions.jl | 29 +++++++++---------- src/parameters.jl | 21 +++++++------- src/sbm_gwf_model.jl | 2 +- src/sediment/erosion/overland_flow_erosion.jl | 2 +- src/sediment/erosion/rainfall_erosion.jl | 4 +-- src/sediment/erosion/river_erosion.jl | 2 +- .../sediment_transport/river_transport.jl | 4 +-- .../sediment_transport/transport_capacity.jl | 16 +++++----- src/sediment_flux.jl | 8 ++--- test/groundwater.jl | 6 ++-- 11 files changed, 48 insertions(+), 50 deletions(-) diff --git a/src/erosion.jl b/src/erosion.jl index d3dcbc3c6..187e91ac4 100644 --- a/src/erosion.jl +++ b/src/erosion.jl @@ -2,7 +2,7 @@ @with_kw struct SoilLoss{RE, OFE, SE, T} atmospheric_forcing::AtmosphericForcing{T} hydrological_forcing::HydrologicalForcing{T} - geometry::LandParameters{T} + geometry::LandGeometry{T} rainfall_erosion::RE overland_flow_erosion::OFE soil_erosion::SE @@ -14,7 +14,7 @@ function SoilLoss(dataset, config, indices) atmospheric_forcing = AtmosphericForcing(n) hydrological_forcing = HydrologicalForcing(n) - geometry = LandParameters(dataset, config, indices) + geometry = LandGeometry(dataset, config, indices) # Rainfall erosion rainfallerosionmodel = get(config.model, "rainfall_erosion", "answers")::String diff --git a/src/groundwater/boundary_conditions.jl b/src/groundwater/boundary_conditions.jl index fbbb04416..52927733c 100644 --- a/src/groundwater/boundary_conditions.jl +++ b/src/groundwater/boundary_conditions.jl @@ -11,29 +11,29 @@ end # Do nothing for a confined aquifer: aquifer can always provide flux check_flux(flux, aquifer::ConfinedAquifer, index::Int) = flux -@get_units @grid_loc @with_kw struct HyporheicParameters{T} +@get_units @grid_loc @with_kw struct RiverParameters{T} infiltration_conductance::Vector{T} | "m2 d-1" exfiltration_conductance::Vector{T} | "m2 d-1" bottom::Vector{T} | "m" end -@get_units @grid_loc @with_kw struct HyporheicVariables{T} +@get_units @grid_loc @with_kw struct RiverVariables{T} stage::Vector{T} | "m" flux::Vector{T} | "m3 d-1" end -function HyporheicVariables(n) - variables = HyporheicVariables{Float}(; stage = fill(mv, n), flux = fill(mv, n)) +function RiverVariables(n) + variables = RiverVariables{Float}(; stage = fill(mv, n), flux = fill(mv, n)) return variables end -@get_units @grid_loc @with_kw struct Hyporheic{T} <: AquiferBoundaryCondition - parameters::HyporheicParameters{T} - variables::HyporheicVariables{T} +@get_units @grid_loc @with_kw struct River{T} <: AquiferBoundaryCondition + parameters::RiverParameters{T} + variables::RiverVariables{T} index::Vector{Int} | "-" end -function Hyporheic(dataset, config, indices, index) +function River(dataset, config, indices, index) infiltration_conductance = ncread( dataset, config, @@ -56,18 +56,15 @@ function Hyporheic(dataset, config, indices, index) type = Float, ) - parameters = HyporheicParameters{Float}( - infiltration_conductance, - exfiltration_conductance, - bottom, - ) + parameters = + RiverParameters{Float}(infiltration_conductance, exfiltration_conductance, bottom) n = length(indices) - variables = HyporheicVariables(n) - river = Hyporheic(parameters, variables, index) + variables = RiverVariables(n) + river = River(parameters, variables, index) return river end -function flux!(Q, river::Hyporheic, aquifer) +function flux!(Q, river::River, aquifer) for (i, index) in enumerate(river.index) head = aquifer.variables.head[index] stage = river.variables.stage[i] diff --git a/src/parameters.jl b/src/parameters.jl index 4c723a9d7..0200feb2c 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -103,7 +103,8 @@ function VegetationParameters(dataset, config, indices) return vegetation_parameter_set end -@get_units @grid_loc @with_kw struct LandParameters{T} +"Struct to store land geometry parameters" +@get_units @grid_loc @with_kw struct LandGeometry{T} # cell area [m^2] area::Vector{T} | "m^2" # drain width [m] @@ -112,7 +113,8 @@ end slope::Vector{T} | "-" end -function LandParameters(nc, config, inds) +"Initialize land geometry parameters" +function LandGeometry(nc, config, inds) # read x, y coordinates and calculate cell length [m] y_nc = read_y_axis(nc) x_nc = read_x_axis(nc) @@ -135,11 +137,12 @@ function LandParameters(nc, config, inds) clamp!(landslope, 0.00001, Inf) land_parameter_set = - LandParameters{Float}(; area = area, width = drain_width, slope = landslope) + LandGeometry{Float}(; area = area, width = drain_width, slope = landslope) return land_parameter_set end -@get_units @grid_loc @with_kw struct RiverParameters{T} +"Struct to store river geometry parameters" +@get_units @grid_loc @with_kw struct RiverGeometry{T} # river width [m] width::Vector{T} | "m" # river length @@ -148,7 +151,8 @@ end slope::Vector{T} | "-" end -function RiverParameters(nc, config, inds) +"Initialize river geometry parameters" +function RiverGeometry(nc, config, inds) riverwidth = ncread( nc, config, @@ -177,10 +181,7 @@ function RiverParameters(nc, config, inds) minimum(riverwidth) > 0 || error("river width must be positive on river cells") clamp!(riverslope, 0.00001, Inf) - river_parameter_set = RiverParameters{Float}(; - width = riverwidth, - length = riverlength, - slope = riverslope, - ) + river_parameter_set = + RiverGeometry{Float}(; width = riverwidth, length = riverlength, slope = riverslope) return river_parameter_set end \ No newline at end of file diff --git a/src/sbm_gwf_model.jl b/src/sbm_gwf_model.jl index 82a64adbc..31d037e5f 100644 --- a/src/sbm_gwf_model.jl +++ b/src/sbm_gwf_model.jl @@ -254,7 +254,7 @@ function initialize_sbm_gwf_model(config::Config) ) # river boundary of unconfined aquifer - river = Hyporheic(dataset, config, inds_river, inds_land_map2river) + river = River(dataset, config, inds_river, inds_land_map2river) # recharge boundary of unconfined aquifer recharge = Recharge( diff --git a/src/sediment/erosion/overland_flow_erosion.jl b/src/sediment/erosion/overland_flow_erosion.jl index 1f8987850..879af50fd 100644 --- a/src/sediment/erosion/overland_flow_erosion.jl +++ b/src/sediment/erosion/overland_flow_erosion.jl @@ -98,7 +98,7 @@ function update_boundary_conditions!( end "Update ANSWERS overland flow erosion model for a single timestep" -function update!(model::OverlandFlowErosionAnswersModel, geometry::LandParameters, dt) +function update!(model::OverlandFlowErosionAnswersModel, geometry::LandGeometry, dt) (; q) = model.boundary_conditions (; usle_k, usle_c, answers_k) = model.parameters (; amount) = model.variables diff --git a/src/sediment/erosion/rainfall_erosion.jl b/src/sediment/erosion/rainfall_erosion.jl index 8e5c5b150..73016f874 100644 --- a/src/sediment/erosion/rainfall_erosion.jl +++ b/src/sediment/erosion/rainfall_erosion.jl @@ -135,7 +135,7 @@ function update_boundary_conditions!( end "Update EUROSEM rainfall erosion model for a single timestep" -function update!(model::RainfallErosionEurosemModel, geometry::LandParameters, dt) +function update!(model::RainfallErosionEurosemModel, geometry::LandGeometry, dt) (; precipitation, interception, waterlevel) = model.boundary_conditions (; soil_detachability, @@ -237,7 +237,7 @@ function update_boundary_conditions!( end "Update ANSWERS rainfall erosion model for a single timestep" -function update!(model::RainfallErosionAnswersModel, geometry::LandParameters, dt) +function update!(model::RainfallErosionAnswersModel, geometry::LandGeometry, dt) (; precipitation) = model.boundary_conditions (; usle_k, usle_c) = model.parameters (; amount) = model.variables diff --git a/src/sediment/erosion/river_erosion.jl b/src/sediment/erosion/river_erosion.jl index d7b2c9322..9e6e470e5 100644 --- a/src/sediment/erosion/river_erosion.jl +++ b/src/sediment/erosion/river_erosion.jl @@ -81,7 +81,7 @@ function update_boundary_conditions!( end "Update Julian and Torres river erosion model for a single timestep" -function update!(model::RiverErosionJulianTorresModel, geometry::RiverParameters, dt) +function update!(model::RiverErosionJulianTorresModel, geometry::RiverGeometry, dt) (; waterlevel) = model.boundary_conditions (; d50) = model.parameters (; bed, bank) = model.variables diff --git a/src/sediment/sediment_transport/river_transport.jl b/src/sediment/sediment_transport/river_transport.jl index 1e9d711a3..9d5addde5 100644 --- a/src/sediment/sediment_transport/river_transport.jl +++ b/src/sediment/sediment_transport/river_transport.jl @@ -393,7 +393,7 @@ end function update!( model::SedimentRiverTransportModel, network, - geometry::RiverParameters, + geometry::RiverGeometry, waterbodies, dt, ) @@ -968,7 +968,7 @@ function update_boundary_conditions!( end "Update river sediment concentrations model for a single timestep" -function update!(model::SedimentConcentrationsRiverModel, geometry::RiverParameters, dt) +function update!(model::SedimentConcentrationsRiverModel, geometry::RiverGeometry, dt) (; q, waterlevel, clay, silt, sand, sagg, lagg, gravel) = model.boundary_conditions (; dm_clay, dm_silt, dm_sand, dm_sagg, dm_lagg, dm_gravel) = model.parameters (; total, suspended, bed) = model.variables diff --git a/src/sediment/sediment_transport/transport_capacity.jl b/src/sediment/sediment_transport/transport_capacity.jl index 02772dafb..9dd1e4b4b 100644 --- a/src/sediment/sediment_transport/transport_capacity.jl +++ b/src/sediment/sediment_transport/transport_capacity.jl @@ -117,7 +117,7 @@ end "Update Govers overland flow transport capacity model for a single timestep" function update!( model::TransportCapacityGoversModel, - geometry::LandParameters, + geometry::LandGeometry, waterbodies, rivers, dt, @@ -198,7 +198,7 @@ end "Update Yalin overland flow transport capacity model for a single timestep" function update!( model::TransportCapacityYalinModel, - geometry::LandParameters, + geometry::LandGeometry, waterbodies, rivers, dt, @@ -362,7 +362,7 @@ end "Update Yalin differentiated overland flow transport capacity model for a single timestep" function update!( model::TransportCapacityYalinDifferentiationModel, - geometry::LandParameters, + geometry::LandGeometry, waterbodies, rivers, dt, @@ -532,7 +532,7 @@ function TransportCapacityBagnoldModel(dataset, config, indices) end "Update Bagnold river transport capacity model for a single timestep" -function update!(model::TransportCapacityBagnoldModel, geometry::RiverParameters, dt) +function update!(model::TransportCapacityBagnoldModel, geometry::RiverGeometry, dt) (; q, waterlevel) = model.boundary_conditions (; c_bagnold, e_bagnold) = model.parameters (; amount) = model.variables @@ -575,7 +575,7 @@ function TransportCapacityEngelundModel(dataset, config, indices) end "Update Engelund and Hansen river transport capacity model for a single timestep" -function update!(model::TransportCapacityEngelundModel, geometry::RiverParameters, dt) +function update!(model::TransportCapacityEngelundModel, geometry::RiverGeometry, dt) (; q, waterlevel) = model.boundary_conditions (; density, d50) = model.parameters (; amount) = model.variables @@ -673,7 +673,7 @@ function TransportCapacityKodatieModel(dataset, config, indices) end "Update Kodatie river transport capacity model for a single timestep" -function update!(model::TransportCapacityKodatieModel, geometry::RiverParameters, dt) +function update!(model::TransportCapacityKodatieModel, geometry::RiverGeometry, dt) (; q, waterlevel) = model.boundary_conditions (; a_kodatie, b_kodatie, c_kodatie, d_kodatie) = model.parameters (; amount) = model.variables @@ -717,7 +717,7 @@ function TransportCapacityYangModel(dataset, config, indices) end "Update Yang river transport capacity model for a single timestep" -function update!(model::TransportCapacityYangModel, geometry::RiverParameters, dt) +function update!(model::TransportCapacityYangModel, geometry::RiverGeometry, dt) (; q, waterlevel) = model.boundary_conditions (; density, d50) = model.parameters (; amount) = model.variables @@ -759,7 +759,7 @@ function TransportCapacityMolinasModel(dataset, config, indices) end "Update Molinas and Wu river transport capacity model for a single timestep" -function update!(model::TransportCapacityMolinasModel, geometry::RiverParameters, dt) +function update!(model::TransportCapacityMolinasModel, geometry::RiverGeometry, dt) (; q, waterlevel) = model.boundary_conditions (; density, d50) = model.parameters (; amount) = model.variables diff --git a/src/sediment_flux.jl b/src/sediment_flux.jl index e789ebae7..4ba49e98d 100644 --- a/src/sediment_flux.jl +++ b/src/sediment_flux.jl @@ -1,7 +1,7 @@ "Sediment transport in overland flow model" @get_units @grid_loc @with_kw struct OverlandFlowSediment{TT, SF, TR} hydrological_forcing::HydrologicalForcing - geometry::LandParameters + geometry::LandGeometry transport_capacity::TT sediment_flux::SF to_river::TR @@ -13,7 +13,7 @@ end function OverlandFlowSediment(dataset, config, indices, waterbodies, rivers) n = length(indices) hydrological_forcing = HydrologicalForcing(n) - geometry = LandParameters(dataset, config, indices) + geometry = LandGeometry(dataset, config, indices) # Check what transport capacity equation will be used do_river = get(config.model, "run_river_model", false)::Bool # Overland flow transport capacity method: ["yalinpart", "govers", "yalin"] @@ -79,7 +79,7 @@ end "Sediment transport in river model" @get_units @grid_loc @with_kw struct RiverSediment{TTR, ER, SFR, CR} hydrological_forcing::HydrologicalForcing - geometry::RiverParameters + geometry::RiverGeometry transport_capacity::TTR potential_erosion::ER sediment_flux::SFR @@ -91,7 +91,7 @@ end function RiverSediment(dataset, config, indices, waterbodies) n = length(indices) hydrological_forcing = HydrologicalForcing(n) - geometry = RiverParameters(dataset, config, indices) + geometry = RiverGeometry(dataset, config, indices) # Check what transport capacity equation will be used # River flow transport capacity method: ["bagnold", "engelund", "yang", "kodatie", "molinas"] diff --git a/test/groundwater.jl b/test/groundwater.jl index e7610b5d1..427c4c96b 100644 --- a/test/groundwater.jl +++ b/test/groundwater.jl @@ -295,13 +295,13 @@ end end @testset "river" begin - parameters = Wflow.HyporheicParameters(; + parameters = Wflow.RiverParameters(; infiltration_conductance = [100.0, 100.0], exfiltration_conductance = [200.0, 200.0], bottom = [1.0, 1.0], ) - variables = Wflow.HyporheicVariables(; stage = [2.0, 2.0], flux = [0.0, 0.0]) - river = Wflow.Hyporheic(; parameters, variables, index = [1, 3]) + variables = Wflow.RiverVariables(; stage = [2.0, 2.0], flux = [0.0, 0.0]) + river = Wflow.River(; parameters, variables, index = [1, 3]) Q = zeros(3) Wflow.flux!(Q, river, conf_aqf) # infiltration, below bottom, flux is (stage - bottom) * inf_cond