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

Water balance computation #525

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Wflow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ struct Model{R <: Routing, L <: AbstractLandModel, T <: AbstractModelType} <:
network::Network # connectivity information, directed graph
routing::R # routing model (horizontal fluxes), moves along network
land::L # land model simulating vertical fluxes, independent of each other
# water_balance::WaterBalance{Float64}
clock::Clock # to keep track of simulation time
reader::NCReader # provides the model with dynamic input
writer::Writer # writes model output
Expand All @@ -148,6 +149,7 @@ end
# prevent a large printout of model components and arrays
Base.show(io::IO, ::AbstractModel{T}) where {T} = print(io, "model of type ", T)

include("water_balance.jl")
include("forcing.jl")
include("parameters.jl")
include("groundwater/connectivity.jl")
Expand Down Expand Up @@ -265,6 +267,8 @@ function run_timestep!(model::Model; update_func = update!, write_model_output =
if write_model_output
write_output(model)
end
# compute_water_balance!(model, model.water_balance.water_balance_vertical)
# compute_water_balance!(model, model.water_balance.water_balance_overland)
return nothing
end

Expand Down
4 changes: 4 additions & 0 deletions src/sbm_gwf_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -433,11 +433,15 @@ function initialize_sbm_gwf_model(config::Config)

routing = Routing(; subsurface_flow, overland_flow, river_flow)

# n = ?
# water_balance = WaterBalance(n, Float64)

model = Model(
config,
network,
routing,
land_hydrology,
# water_balance,
clock,
reader,
writer,
Expand Down
16 changes: 14 additions & 2 deletions src/sediment_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,20 @@ function initialize_sediment_model(config::Config)

routing = Routing(; overland_flow = overland_flow_sediment, river_flow = river_sediment)

model =
Model(config, network, routing, soilloss, clock, reader, writer, SedimentModel())
# n = ?
# water_balance = WaterBalance(n, Float64)

model = Model(
config,
network,
routing,
soilloss,
# water_balance,
clock,
reader,
writer,
SedimentModel(),
)

set_states!(model)
@info "Initialized model"
Expand Down
97 changes: 97 additions & 0 deletions src/water_balance.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
struct WaterBalanceVertical{T <: AbstractFloat} # Terms in [mm]
storage_total::Vector{T}
storage_total_prev::Vector{T}
inflow_total::Vector{T}
outflow_total::Vector{T}
balance_error::Vector{T}
relative_error::Vector{T}
end

function WaterBalanceVertical(n::Integer, float_type::Type{T}) where {T <: AbstractFloat}
WaterBalanceVertical(ntuple(_ -> fill(float_type(Nan), n), 6)...)
end

function compute_water_balance!(model, water_balance::WaterBalanceVertical)::Nothing
(;
storage_total,
storage_total_prev,
inflow_total,
outflow_total,
balance_error,
relative_error,
) = water_balance
(; vertical, lateral) = model

(; soil, runoff) = vertical
(; satwaterdepth, ustoredepth, actevap) = soil.variables
(; runoff_land, net_runoff_river) = runoff.variables

(; subsurface) = lateral
(; ssf, ssfin) = subsurface.variables
Copy link
Collaborator

Choose a reason for hiding this comment

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

Note that these variables refer to the LateralSSF struct (kinematic wave for lateral subsurface flow). It would be nice to also have water balance computations for the sbm_gwf model type that makes use of Darcy groundwater flow for a single unconfined layer (see also \src\sbm_gwf_model.jl and \src\groundwater\aquifer.jl).

(; flow_length, flow_width) = subsurface.parameters

storage_total .= satwaterdepth
Copy link
Collaborator

Choose a reason for hiding this comment

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

A few storage variables are missing:
get_snow_storage(snow) .+ get_snow_water(snow)
get_glacier_store(glacier) .* get_glacier_fraction(glacier)
get_water_depth(demand.paddy)
model.land.interception.variables.canopy_storage

Copy link
Collaborator

Choose a reason for hiding this comment

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

Lateral snow transport is often used when snow modelling is included, see also https://github.com/Deltares/Wflow.jl/blob/master/src/sbm.jl#L88. For the water balance this means the lateral snow fluxes need to be included.

storage_total .+= ustoredepth
Copy link
Collaborator

Choose a reason for hiding this comment

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

During model initialization, ustoredepth is not set based on ustorelayerdepth (state). As discussed, it could for example be added in the initialization of the model, for that the ustoredepth! function can be used after the states are set (after this line for the model type sbm: https://github.com/Deltares/Wflow.jl/blob/master/src/sbm_model.jl#L390).


inflow_total .= vertical.atmospheric_forcing.precipitation
inflow_total .+= runoff.variables.net_runoff_river
Copy link
Collaborator

Choose a reason for hiding this comment

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

net_runoff_river is not an inflow (it is an outflow). allocation refers to model.land.allocation.

Suggested change
inflow_total .+= runoff.variables.net_runoff_river
inflow_total .+= get_irrigation_allocated(allocation)

inflow_total .+= 1000.0 * ssfin ./ (flow_length .* flow_width)

outflow_total .= runoff_land
Copy link
Collaborator

Choose a reason for hiding this comment

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

net_runoff refers to model.land.soil.variables.net_runoff

Suggested change
outflow_total .= runoff_land
outflow_total .= net_runoff

outflow_total .+= net_runoff_river
Copy link
Collaborator

Choose a reason for hiding this comment

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

As part of water demand computations water can be abstracted: land.allocation.variables.act_groundwater_abst. This should be added to outflow_total. Suggest to use a wrapper method here similar to get_irrigation_allocated(allocation).

outflow_total .+= actevap
outflow_total .+= 1000.0 * ssf ./ (flow_length .* flow_width)

@. balance_error = (inflow_total - outflow_total - (storage_total - storage_total_prev))
@. relative_error = balance_error / ((inflow_total + outflow_total) / 2)

copyto!(storage_total_prev, storage_total)

#TODO : What to do with results?
Copy link
Collaborator

Choose a reason for hiding this comment

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

For now I think it is fine to compute the errors and these variables can be written as output (netCDF (gridded and scalar) or CSV).


return nothing
end

struct WaterBalanceOverLand{T <: AbstractFloat} # Terms in [?]
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would be good to have water balance computations for the available routing options (suggest for example to rename to WaterBalanceRouting if this struct is used to store water balance components for the different routing options).

volume_t_prev::Vector{T}
volume_rate::Vector{T}
water_in::Vector{T}
water_out::Vector{T}
balance_error::Vector{T}
relative_error::Vector{T}
end

function WaterBalanceOverLand(n::Integer, float_type::Type{T}) where {T <: AbstractFloat}
WaterBalanceOverLand(ntuple(_ -> fill(float_type(Nan), n), 6)...)
end

# function compute_water_balance!(model, water_balance::WaterBalanceOverLand)::Nothing
Copy link
Collaborator

Choose a reason for hiding this comment

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

For most routing options an internal time step (different from the model time step) is used. The variable volume is available and initialized (either cold or warm state). Outflow variables (e.g. q_av) are available for the model time step, but inflow variables are mostly only available at the internal time step.

Kinematic wave overland flow

  • outflow: q_av
  • inflow: qin + qlat * flow_length (internal time step), instead of qlat * flow_length, inwater` can also be used (valid for model time step)

Kinematic wave river flow

  • outflow: q_av
  • inflow: q_in + (qlat + _inflow) * flow_length (internal time step) (as for the kinematic wave overland flow, inwater (valid for model time step) can also be used (to replace qlat * flow_length)

Local inertial river flow (with optional floodplain routing scheme)
A cumulative volume error is computed, see also the code starting here https://github.com/Deltares/Wflow.jl/blob/master/src/routing/surface_local_inertial.jl#L479. I think it would be useful to have also error(s) available per model time step as part of water_balance.jl. If floodplain routing is included I would recommend to compute the water balance for river and floodplain (not separated).

Local inertial river and overland flow
A cumulative error is computed, see also the code starting here https://github.com/Deltares/Wflow.jl/blob/master/src/routing/surface_local_inertial.jl#L1033. For the water balance computation per model time step it is enough to consider land_v.volume.

When many variables are involved (as for local inertial routing) it probably makes sense to compute a net_flux.

Kinematic wave subsurface flow
outflow: ssf + exfiltwater * flow_length * flow_width
inflow: ssfin + recharge * flow_length
Note: see for units of variables https://github.com/Deltares/Wflow.jl/blob/master/src/routing/subsurface.jl#L87.

Groundwater flow
Here Q is used as the net flux, see also: https://github.com/Deltares/Wflow.jl/blob/master/src/groundwater/aquifer.jl#L432

# (; vertical, lateral) = model
# (; volume_t_prev, volume_rate, water_in, water_out, balance_error, relative_error) = water_balance_overland

# (; land, river) = lateral
# (; volume) = land.variables
# volume_t = volume

# water_in .= lateral.land.qin_av
# @. water_in += lateral.land.qlat_av * lateral.land.dl

# water_out .= lateral.land.q_av

# @. volume_rate = (volume_t - volume_t_previous) / vertical.dt

# @. balance_error = (water_in - water_out) - volume_rate

# #TODO : What to do with results?

# return nothing
# end

struct WaterBalance{T}
water_balance_vertical::WaterBalanceVertical{T}
water_balance_over_land::WaterBalanceOverLand{T}
end

function WaterBalance(n::Integer, float_type::Type{T}) where {T <: AbstractFloat}
WaterBalance(WaterBalanceVertical(n, float_type), WaterBalanceOverLand(n, float_type))
end
Loading