Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor SBM concept #487

Merged
merged 73 commits into from
Nov 4, 2024
Merged

Refactor SBM concept #487

merged 73 commits into from
Nov 4, 2024

Conversation

verseve
Copy link
Contributor

@verseve verseve commented Oct 17, 2024

Issue addressed

Fixes #486

Explanation

See issue #486. Additionally, the vertical concepts HBV and FLEXTopo have been removed from the code, see also Discussion #342.

Checklist

  • Updated tests or added new tests
  • Branch is up to date with master
  • Tests & pre-commit hooks pass
  • Updated documentation if needed
  • Updated changelog.md if needed

vers-w and others added 30 commits July 1, 2024 10:29
* 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 <[email protected]>

---------

Co-authored-by: JoostBuitink <[email protected]>
* 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
* 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
* Stop using local JULIAUP_DEPOT_PATH

* Avoid error when override is already set

Co-authored-by: Martijn Visser <[email protected]>
In `update` functions use `model` for interception, snow and glacier models. Add wrapper methods for snow runoff and glacier melt.
This is with kinematic wave routing. Renamed the model for `SBM` from `soil` to `bucket` as it is more related to this type of model (Simple Bucket Model) than a soil model that for example solves the Richards equation for the unsaturated zone and upper part of the saturated zone.
Also removed the vertical_process.jl file.
Mostly water demand and allocation functionality. All tests pass.
Add water demand and allocation, and snow transport computations.
Add separate functions to update the boundary conditions of the snow and `SBM` models.
In addition:
- add a parameter set for LandParameters (to share accross model components).
- make use of type NamedTuple (for external models) in the update of models and boundary conditions, these external models provide input to the models and boundary conditions.
- move shared parameters (vegetation and land) to file Parameters.jl.
In update of models and boundaries of `LandHydrologySBM`
It only contains two parameters that are not shared that much across different models of `LandHydrologySBM`.
Use the struct name for outer constructor functions.
@verseve verseve requested a review from JoostBuitink October 21, 2024 08:04
@verseve verseve marked this pull request as ready for review October 21, 2024 08:04
@verseve verseve self-assigned this Oct 21, 2024
@verseve verseve added the v1.0 Activities related to v1.0 label Oct 21, 2024
Copy link
Contributor

@SouthEndMusic SouthEndMusic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot comment on functionality yet, but I made some code quality suggestions

src/utils.jl Outdated
Comment on lines 835 to 841
for i in eachindex(ssf)
ssfmax[i] = ((kh_0[i] * slope[i]) / f[i]) * (1.0 - exp(-f[i] * soilthickness[i]))
ssf[i] =
((kh_0[i] * slope[i]) / f[i]) *
(exp(-f[i] * zi[i]) - exp(-f[i] * soilthickness[i])) *
dw[i]
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this can be written without an explicit loop

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good catch!

Comment on lines 17 to 33
"Initialize interception model variables"
function InterceptionVariables(
n;
canopy_potevap::Vector{T} = fill(mv, n),
interception_rate::Vector{T} = fill(mv, n),
canopy_storage::Vector{T} = fill(0.0, n),
stemflow::Vector{T} = fill(mv, n),
throughfall::Vector{T} = fill(mv, n),
) where {T}
return InterceptionVariables(;
canopy_potevap = canopy_potevap,
interception_rate = interception_rate,
canopy_storage = canopy_storage,
stemflow = stemflow,
throughfall = throughfall,
)
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be done with default values as mentioned before

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make now use of a simple constructor as in water_demand,jl.

Copy link
Contributor

@JoostBuitink JoostBuitink left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice work, makes the code a lot more futureproof! I have a couple of improvements/questions, see the comments for those!

Comment on lines 41 to 56
sl = ncread(
nc,
config,
"vertical.vegetation_parameter_set.specific_leaf";
optional = false,
sel = inds,
type = Float,
)
swood = ncread(
nc,
config,
"vertical.vegetation_parameter_set.storage_wood";
optional = false,
sel = inds,
type = Float,
)
Copy link
Contributor

@JoostBuitink JoostBuitink Oct 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be an idea to actually match the names of these parameter to what we set in the toml now as well? So sl would become specific_leaf (or maybe even storage_specific_leaf to be in line with storage_wood?), and swood would become storage_wood

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, was planning to actually look into this for all variables in Wflow as part of another PR. Anyway, good to already change these parameter names!

src/sbm.jl Outdated
Comment on lines 36 to 39
if modelsnow && modelglacier
glacier_bc = SnowStateBC{Float}(; snow_storage = snow_model.variables.snow_storage)
glacier_model = GlacierHbvModel(nc, config, inds, dt, glacier_bc)
else
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe good to throw a warning when modelglacier=true && modelsnow=false. It does not make sense to use this combination, but at least there will be some feedback to the user that glaciers will not be accounted for.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree and added a warning.

src/sbm_model.jl Outdated
cellength = abs(mean(diff(x_nc)))

sizeinmetres = get(config.model, "sizeinmetres", false)::Bool
xl, yl = cell_lengths(y, cellength, sizeinmetres)
riverfrac = river_fraction(river, riverlength, riverwidth, xl, yl)

lsm = LandHydrologySBM(nc, config, riverfrac, inds)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in sbm.jl this is called land_hydrology_model, so to be consistent, maybe change this to lhm?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point, changed to lhm.

src/soil/soil.jl Outdated
Comment on lines 552 to 557
if haskey(config.input.vertical.soil.parameters, "et_reftopot")
@warn string(
"The `et_reftopot` key in `[input.vertical.soil.parameters]` is now called ",
"`kc`. Please update your TOML file.",
)
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For v1.0, I think we can get rid of this warning, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, removed the warning.

Comment on lines 43 to 50
e_r = ncread(
nc,
config,
"vertical.interception.parameters.eoverr";
sel = inds,
defaults = 0.1,
type = Float,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Best to match the names of the parameters here (either e_r or eoverr)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, changed it to e_r.

vers-w and others added 6 commits October 29, 2024 09:36
-Use consistently `!` for functions that are mutating, and return always  `nothing` for these functions.
- Fix missing `canonicalize` from `Dates`
Co-authored-by: JoostBuitink <[email protected]>
As of Julia 1.7 there is no difference and `isnothing` is also used in other code sections.
Copy link
Contributor

@SouthEndMusic SouthEndMusic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell these changes look good. In Ribasim we don't have the mandatory return at the end of each function, but that is a matter of taste.

As briefly disscussed with @verseve, I am in favor of splitting the parameters and the states (i.e. the unknowns you are solving for) in your data structure, but let's leave that as a follow up. The main reason for splitting those is that it is more in alignment with the SciML standard.

Comment on lines +400 to +418
"Struct to store water demand model variables"
@get_units @grid_loc @with_kw struct DemandVariables{T}
irri_demand_gross::Vector{T} # irrigation gross demand [mm Δt⁻¹]
nonirri_demand_gross::Vector{T} # non-irrigation gross demand [mm Δt⁻¹]
total_gross_demand::Vector{T} # total gross demand [mm Δt⁻¹]
surfacewater_demand::Vector{T} # demand from surface water [mm Δt⁻¹]
groundwater_demand::Vector{T} # demand from groundwater [mm Δt⁻¹]
end

"Initialize water demand variables"
function DemandVariables(T::Type{<:AbstractFloat}, n::Int)
return DemandVariables{T}(;
irri_demand_gross = zeros(T, n),
nonirri_demand_gross = zeros(T, n),
total_gross_demand = zeros(T, n),
surfacewater_demand = zeros(T, n),
groundwater_demand = zeros(T, n),
)
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think things like this can be squashed into one by using default field values allowed by @with_kw, or is there a specific reason you're not doing that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, not a specific reason, I would say it is a matter of taste.

Comment on lines +629 to +632
inds_river = network.river.indices_allocation_areas
inds_land = network.land.indices_allocation_areas
res_index = network.river.reservoir_index
lake_index = network.river.lake_index
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
inds_river = network.river.indices_allocation_areas
inds_land = network.land.indices_allocation_areas
res_index = network.river.reservoir_index
lake_index = network.river.lake_index
(; indices_allocation_area, resevoir_index, lake_index) = network.river
inds_land = network.land.indices_allocation_areas

The only disadvantage of this is that you have to use the exact name of the field

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and although a small disadvantage: the naming is quite different now between inds_land and indices_allocation_area. Suggest to keep the original naming.

Copy link
Contributor

@JoostBuitink JoostBuitink left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@verseve
Copy link
Contributor Author

verseve commented Nov 4, 2024

As briefly disscussed with @verseve, I am in favor of splitting the parameters and the states (i.e. the unknowns you are solving for) in your data structure, but let's leave that as a follow up. The main reason for splitting those is that it is more in alignment with the SciML standard.

Yes, note that this PR made a start with this by using the fields parameters and variables for the different sub-models of LandHydrologySBM (refactoring of vertical SBM concept).

@verseve verseve merged commit c9e5b87 into master Nov 4, 2024
10 checks passed
@verseve verseve deleted the refactor/sbm_concept branch November 4, 2024 12:17
@vers-w vers-w mentioned this pull request Feb 27, 2025
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
v1.0 Activities related to v1.0
Projects
None yet
5 participants