Source code for pywatershed.hydrology.prms_groundwater

from typing import Literal
from warnings import warn

import numpy as np

from ..base.adapter import adaptable, adapter_factory
from ..base.conservative_process import ConservativeProcess
from ..base.control import Control
from ..constants import nan, numba_num_threads
from ..parameters import Parameters

try:
    from ..prms_groundwater_f import calc_groundwater as _calculate_fortran

    has_prmsgroundwater_f = True
except ImportError:
    has_prmsgroundwater_f = False


[docs] class PRMSGroundwater(ConservativeProcess): """PRMS groundwater reservoir. Implementation based on PRMS 5.2.1 with theoretical documentation given in the PRMS-IV documentation: `Markstrom, S. L., Regan, R. S., Hay, L. E., Viger, R. J., Webb, R. M., Payn, R. A., & LaFontaine, J. H. (2015). PRMS-IV, the precipitation-runoff modeling system, version 4. US Geological Survey Techniques and Methods, 6, B7. <https://pubs.usgs.gov/tm/6b7/pdf/tm6-b7.pdf>`__ Args: control: a Control object discretization: a discretization of class Parameters parameters: a parameter object of class Parameters soil_to_gw: Portion of excess flow to the capillary reservoir that drains to the associated GWR for each HRU ssr_to_gw: Drainage from the gravity-reservoir to the associated GWR for each HRU dprst_seep_hru: Seepage from surface-depression storage to associated GWR for each HRU budget_type: one of ["defer", None, "warn", "error"] with "defer" being the default and defering to control.options["budget_type"] when available. When control.options["budget_type"] is not avaiable, budget_type is set to "warn". calc_method: one of ["fortran", "numba", "numpy"]. None defaults to "numba". verbose: Print extra information or not? """
[docs] def __init__( self, control: Control, discretization: Parameters, parameters: Parameters, soil_to_gw: adaptable, ssr_to_gw: adaptable, dprst_seep_hru: adaptable, dprst_flag: bool = None, budget_type: Literal["defer", None, "warn", "error"] = "defer", calc_method: Literal["fortran", "numba", "numpy"] = None, verbose: bool = None, ) -> None: super().__init__( control=control, discretization=discretization, parameters=parameters, ) self.name = "PRMSGroundwater" self._set_inputs(locals()) self._set_options(locals()) if self._dprst_flag is None: self._dprst_flag = True # This is a hacky dprst_flag == False approach. Improve design to # get rid of these inputs. if not self._dprst_flag: for kk, vv in self._input_variables_dict.items(): if vv is not None: continue self._input_variables_dict[kk] = adapter_factory( np.zeros(self._params.dimensions["nhru"]), variable_name=kk, control=self.control, ) self._set_budget() self._init_calc_method() return
[docs] @staticmethod def get_dimensions() -> tuple: return ("nhru",)
[docs] @staticmethod def get_parameters() -> tuple: return ( "hru_area", "hru_in_to_cf", "gwflow_coef", "gwsink_coef", "gwstor_init", "gwstor_min", )
[docs] @staticmethod def get_inputs() -> tuple: return ( "soil_to_gw", "ssr_to_gw", "dprst_seep_hru", )
[docs] @staticmethod def get_mass_budget_terms(): return { "inputs": [ "soil_to_gw", "ssr_to_gw", "dprst_seep_hru", ], "outputs": [ "gwres_flow", ], "storage_changes": [ "gwres_stor_change", ], }
[docs] @staticmethod def get_init_values() -> dict: return { "gwres_flow": nan, "gwres_flow_vol": nan, "gwres_sink": nan, "gwres_stor": nan, "gwres_stor_old": nan, "gwres_stor_change": nan, }
def _set_initial_conditions(self): # initialize groundwater reservoir storage self.gwres_stor[:] = self.gwstor_init.copy() self.gwres_stor_old[:] = self.gwstor_init.copy() return def _init_calc_method(self): if self._calc_method is None: self._calc_method = "numba" avail_methods = ["numpy", "numba", "fortran"] fortran_msg = "" if self._calc_method == "fortran" and not has_prmsgroundwater_f: _ = avail_methods.remove("fortran") fortran_msg = "\n(Fortran not available as installed)\n" if self._calc_method.lower() not in avail_methods: msg = ( f"Invalid calc_method={self._calc_method} for {self.name}. " f"{fortran_msg}" f"Setting calc_method to 'numba' for {self.name}" ) warn(msg) self._calc_method = "numba" if self._calc_method.lower() == "numba": import numba as nb numba_msg = f"{self.name} jit compiling with numba " nb_parallel = (numba_num_threads is not None) and ( numba_num_threads > 1 ) if nb_parallel: numba_msg += f"and using {numba_num_threads} threads" print(numba_msg, flush=True) self._calculate_gw = nb.njit( nb.types.UniTuple(nb.float64[:], 5)( nb.types.Array(nb.types.float64, 1, "C", readonly=True), nb.float64[:], nb.float64[:], nb.float64[:], nb.float64[:], nb.types.Array(nb.types.float64, 1, "C", readonly=True), nb.types.Array(nb.types.float64, 1, "C", readonly=True), nb.float64[:], nb.types.Array(nb.types.float64, 1, "C", readonly=True), ), fastmath=True, parallel=False, )(self._calculate_numpy) elif self._calc_method.lower() == "fortran": self._calculate_gw = _calculate_fortran else: self._calculate_gw = self._calculate_numpy return def _advance_variables(self) -> None: self.gwres_stor_old[:] = self.gwres_stor return def _calculate(self, simulation_time): self._simulation_time = simulation_time ( self.gwres_stor[:], self.gwres_flow[:], self.gwres_sink[:], self.gwres_stor_change[:], self.gwres_flow_vol[:], ) = self._calculate_gw( self.hru_area, self.soil_to_gw, self.ssr_to_gw, self.dprst_seep_hru, self.gwres_stor, self.gwflow_coef, self.gwsink_coef, self.gwres_stor_old, self.hru_in_to_cf, ) return @staticmethod def _calculate_numpy( gwarea, soil_to_gw, ssr_to_gw, dprst_seep_hru, gwres_stor, gwflow_coef, gwsink_coef, gwres_stor_old, hru_in_to_cf, ): soil_to_gw_vol = soil_to_gw * gwarea ssr_to_gw_vol = ssr_to_gw * gwarea dprst_seep_hru_vol = dprst_seep_hru * gwarea # todo: what about route order _gwres_stor = gwres_stor * gwarea _gwres_stor += soil_to_gw_vol + ssr_to_gw_vol + dprst_seep_hru_vol _gwres_flow = _gwres_stor * gwflow_coef _gwres_stor -= _gwres_flow _gwres_sink = _gwres_stor * gwsink_coef idx = np.where(_gwres_sink > _gwres_stor) _gwres_sink[idx] = _gwres_stor[idx] _gwres_stor -= _gwres_sink # convert most units back to self variables # output variables gwres_stor = _gwres_stor / gwarea # for some stupid reason this is left in acre-inches gwres_flow = _gwres_flow / gwarea gwres_sink = _gwres_sink / gwarea gwres_stor_change = gwres_stor - gwres_stor_old gwres_flow_vol = gwres_flow * hru_in_to_cf return ( gwres_stor, gwres_flow, gwres_sink, gwres_stor_change, gwres_flow_vol, )