Source code for hydromodpy.watershed.hydraulic

# -*- coding: utf-8 -*-
"""
 * Copyright (C) 2023-2025 Alexandre Gauvain, Ronan Abhervé, Jean-Raynald de Dreuzy
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
 * which is available at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
"""

#%% LIBRAIRIES

# Python
import numpy as np
import whitebox
from os.path import dirname
from typing import Union
import os
import rasterio
from hydromodpy.tools import get_logger
wbt = whitebox.WhiteboxTools()
# wbt.set_compress_rasters(True)
wbt.verbose = False

logger = get_logger(__name__)

#%% CLASS

[docs] class Hydraulic: """ Update hydraulic properties of the groundwater flow model. """ def __init__(self, box_dem: str, nrow: int, ncol: int, nlay_init: int=1, cond_drain_init: float=864000, hk_init: float=8.64, sy_init: float=0.1, ss_init: float=1e-5, thick_init: float=50., bottom_init: float=None, hk_decay_init: float=0., sy_decay_init: float=0., ss_decay_init: float=0., lay_decay_init: float=1., verti_hk_init=None, verti_sy_init=None, verti_ss_init=None, vka_init: float=1., exdp_init: float=1. ): """ Parameters ---------- box_dem : str Path raster of maximal buffer extent of the model domain generated from geographic. nrow : int Number of rows of the model domain obtained from raster in geographic. ncol : int Number of columns of the model domain obtained from raster in geographic. nlay_init : int, optional Initial value. Vertical layer of the mesh. The default is 1. cond_drain_init : float, optional Initial value. Conductance value for the drain package applied on top. Considering a cell resolution of 100*100m, The default is 864000 [m3/day]. hk_init : float, optional Initial value. Hydraulic conductivity of the aaquifer. The default is 8.64 in [m/d]. sy_init : float, optional Initial value. Specific yield of the aquifer. The default is 0.1 [-], so 10%. ss_init : float, optional Initial value. Specifc storage of the aquifer. The default is 1e-5 (1/day). thick_init : float, optional Initial value. Constant aquifer thickness activated if bottom_init is None. The default is 50 m. bottom_init : float, optional Initial value. Apply a flat bottom at the aquifer from a elevation value. The default is None. hk_decay_init : float, optional Initial value. Alpha decay to modify the hydraulic conductivity exponentially decreasing whit depth (e.g. 1/30m). The default is 0. sy_decay_init : float, optional Initial value. Alpha decay to modify the specific yield exponentially decreasing whit depth (e.g. 1/60m). The default is 0. ss_decay_init : float, optional Initial value. Alpha decay to modify the specific storage exponentially decreasing whit depth (e.g. 1/120m). The default is 0. lay_decay_init : float, optional Initial value. Modify vertical layer thickness exponentially decreasing whith depth. The default is 1 (no change). verti_hk_init : list, optional Initial value. Apply hydraulic conductivity values between different thickness. The default is None. verti_sy_init : list, optional Initial value. Apply specific yield values between different thickness. The default is None. verti_ss_init : list, optional Initial value. Apply specific storage values between different thickness. The default is None. vka_init : list, optional Initial value. Ratio of horizontal to vertical hydraulic conductivity. The default is 1. """ logger.info('Initializing hydraulic module for parameter setup') self.box_dem = box_dem self.thick = thick_init self.bottom = bottom_init self.nrow = nrow self.ncol = ncol self.nlay = nlay_init self.lay_decay = lay_decay_init self.hk_value = hk_init self.sy_value = sy_init self.ss_value = ss_init self.hk_grid = np.ones((self.nrow, self.ncol)) self.sy_grid = np.ones((self.nrow, self.ncol)) self.calib_zones = np.ones((self.nrow, self.ncol)) self.hk_decay = hk_decay_init self.sy_decay = sy_decay_init self.ss_decay = ss_decay_init self.verti_hk = verti_hk_init self.verti_sy = verti_sy_init self.verti_ss = verti_ss_init self.cond_drain = cond_drain_init self.vka = vka_init self.exdp = exdp_init self.update_hk_decay() self.update_sy_decay() self.update_ss_decay() #%% UPDATE LATERAL HOMOGENEOUS
[docs] def update_nlay(self, nlay_value: int): """ Parameters ---------- nlay_value : int Number of vertical layer of the aquifer model mesh. """ self.nlay = nlay_value
[docs] def update_hk(self, hk_value: float): """ Parameters ---------- hyd_cond_value : float Hydraulic conductivity of the aquifer model. """ self.hk_value = hk_value
[docs] def update_vka(self, vka_value: float): """ Parameters ---------- vka : float Ratio of horizontal to vertical hydraulic conductivity. """ self.vka = vka_value
[docs] def update_exdp(self, exdp_value: float): """ Parameters ---------- exdp : float Extinction depth from the surface of the evapotranspiration. """ self.exdp = exdp_value
[docs] def update_sy(self, sy_value: float): """ Parameters ---------- sy_value : float Sspecifc yield of the aquifer model. """ self.sy_value = sy_value
[docs] def update_ss(self, ss_value: float): """ Parameters ---------- ss_value : float Specific storage of the aquifer model. """ self.ss_value = ss_value
[docs] def update_thick(self, thick_value: float): """ Parameters ---------- thick_value : float Constant thickness of the aquifer model. """ self.thick = thick_value
[docs] def update_bottom(self, bottom_value: float): """ Parameters ---------- bottom_value : float Flat bottom elevation of the aquifer model. """ self.bottom = bottom_value
[docs] def update_hk_decay(self, hk_decay_value: float=0, min_value: float=None, log_transf: bool=False, grad_elev: list=[]): """ Parameters ---------- hk_decay_value : float Exponential decay ratio of hydraulic conductivity K. For z=50m, if hk_decay_value=1/50, Kmax (or K0) divide by 2.7 at 50m. K(z) = Kmax*np.exp(-hk_decay_value*z) min_value : float If not None, the exponential decay stop until this minimal value Kmin. K(z) = Kmin-(Kmax-Kmin)*np.exp(-hk_decay_value*z) log_transf : bool If True, the log transform is applied to the formulation. log(K(z)) = log(Kmin)-(log(Kmax)-log(Kmin))*np.exp(-hk_decay_value*z) """ self.hk_decay = [hk_decay_value, min_value, log_transf, grad_elev]
[docs] def update_sy_decay(self, sy_decay_value: float=0, min_value: float=None, log_transf: bool=False, grad_elev: list=[]): """ Parameters ---------- sy_decay_value : float Idem por specific yield. See 'update_hk_decay'. """ self.sy_decay = [sy_decay_value, min_value, log_transf, grad_elev]
[docs] def update_ss_decay(self, ss_decay_value: float=0, min_value: float=None, log_transf: bool=False, grad_elev: list=[]): """ Parameters ---------- ss_decay_value : float Idem por specific stotage. See 'update_hk_decay'. """ self.ss_decay = [ss_decay_value, min_value, log_transf, grad_elev]
[docs] def update_lay_decay(self, lay_decay_value: Union[float, int]): """ Parameters ---------- thick_exp_value : float Exponential decay ratio of vertical layer mesh thickness increasing with depath. The default value without decay is 1. """ self.lay_decay = lay_decay_value
[docs] def update_cond_drain(self, cond_drain_value: float): """ Parameters ---------- cond_drain_value : float Drain conductance value at the surface of the aquifer model. """ self.cond_drain = cond_drain_value
[docs] def update_hk_vertical(self, verti_hk_value: list): """ Parameters ---------- verti_hk_value : list List of hydraulic conductivity values with associated vertical depth. For example: [ [1, [0, 20]], [0.5, [20,80]] ] 1 m/d between 0 and 20 m depth, and 0.5 m/d between 20 and 80 m depth. """ self.verti_hk = verti_hk_value # None or [ [1e-5, [0, 20]],
# [1e-6, [20,80]] ]
[docs] def update_sy_vertical(self, verti_sy_value: list): """ Parameters ---------- verti_sy_value : list Idem for specific yield. See 'update_hk_vertical'. """ self.verti_sy = verti_sy_value # None or [ [0.5/100, [0, 20]],
# [0/100, [20,80]] ]
[docs] def update_ss_vertical(self, verti_ss_value: list): """ Parameters ---------- verti_ss_value : list Idem for specific storage. See 'update_hk_vertical'. """ self.verti_ss = verti_ss_value # None or [ [0.5/100, [0, 20]],
# [0/100, [20,80]] ] #%% UPDATE LATERAL HETEROGENEOUS
[docs] def update_calib_zones(self, zones: np.ndarray): """ Updates the :attr:`calib_zones` zone number with :data:`zone`. The array values must be :class:`int` and start at 1. :param zones: localisation of the calibration zones in the DEM. """ self.calib_zones = zones
[docs] def update_calib_zones_from_shp(self, shp_path, default_zone=1): """ Shapefile must be with different features. Field must be "CALIB_ZONE" = 1,2,3,4 """ output = os.path.join(dirname(self.box_dem), 'calib_raster_zones.tif') wbt.vector_polygons_to_raster( shp_path, output, field="FID", #Field name should be changed , error : thread 'main' panicked at 'Error: Specified field is greater than the number of fields.' # nodata=default_zone, cell_size=None, base=self.box_dem) with rasterio.open(output) as src: raster_load = src.read(1) raster_load[raster_load<=-9999] = default_zone self.calib_zones = raster_load
[docs] def update_hk_from_calib_zones(self, num_zone: int, hk_value: float): """ Updates :attr:`hk_value` with a value :data:`hk_value` at the location of the :data:`num_zone` in the :attr:`calib_zones` :param num_zone: the zone number :param hk_value: hydraulic conductivy of the aquifer. """ self.hk_grid[self.calib_zones==num_zone] = hk_value # self.hk_grid = np.tile(self.hk_grid, (self.nlay, 1, 1)) self.hk_value = self.hk_grid.copy()
[docs] def update_sy_from_calib_zones(self, num_zone: int, sy_value: float): """ Updates :attr:`sy_value` with a value :data:`sy_value` at the location of the :data:`num_zone` in the :attr:`calib_zones` :param num_zone: the zone number :param sy_value: porosity of the aquifer. """ self.sy_grid[self.calib_zones==num_zone] = sy_value # self.sy_grid = np.tile(self.sy_grid, (self.nlay, 1, 1)) self.sy_value = self.sy_grid.copy()
[docs] def update_thick_from_calib_zones(self, num_zone: int, thick_value: float): """ Updates :attr:`thickness` with a value :data:`thickness_value` at the location of the :data:`num_zone` in the :attr:`calib_zones` :param num_zone: the zone number :param thickness_value: thickness of the aquifer. """ self.thick[self.calib_zones==num_zone] = thick_value
[docs] def update_hk_with_geology(self, geology_code, geology_array, hk_values): """ Updates :attr:`hk_value` with values in :data:`hk_values` at the location of the :data:`geology_code` in the :data:`geology_array` :param geology_code: list of geology entities. :type geology_code: :class:`list of int` :param geology_array: localisation of the geology entities in the DEM. :type geology_array: :class:`numpy.ndarray(int)` :param hk_values: hydraulic conductivity values for each geology code. Must be the same lenght of :data:`geology_code`. :type hk_values: :class:`list of float` """ self.hk_value = np.ones((self.nrow, self.ncol)) for i in range(0,len(geology_code)): self.hk_value[geology_array==geology_code[i]] = hk_values[i] self.hk_value = np.tile(self.hk_value, (self.nlay, 1, 1))
[docs] def update_sy_with_geology(self, geology_code, geology_array, sy_values): """ Updates :attr:`sy_value` with values in :data:`sy_values` at the location of the :data:`geology_code` in the :data:`geology_array` :param geology_code: list of geology entities. :type geology_code: :class:`list of int` :param geology_array: localisation of the geology entities in the DEM. :type geology_array: :class:`numpy.ndarray(int)` :param sy_values: specific yields values for each geology code. Must be the same lenght of :data:`geology_code`. :type sy_values: :class:`list of float` """ self.sy_value = np.ones((self.nrow, self.ncol)) for i in range(0,len(geology_code)): self.sy_value[geology_array==geology_code[i]] = sy_values[i] self.sy_value = np.tile(self.sy_value, (self.nlay, 1, 1))
#%% NOTES