HYMOD_CLASSIC

Implementation

hydromodel 0.4.0 source rev 89d7a8e hymod; upstream numerical variant; initial states unchanged

  • Time step: daily

  • Backend: installed Python dependencies

  • Calibrated parameters: 5

  • Temperature required: no

Inspect the exact implementation.

Parameters and initial configuration

Parameter

Supported calibration range

Default

cmax

1.0 to 500.0

250.5

bexp

0.1 to 2.0

1.05

alpha

0.1 to 0.99

0.545

ks

0.001 to 0.1

0.0505

kq

0.1 to 0.99

0.545

Ranges/defaults are implementation contracts, not universal priors or a recommended basin calibration. Consult source comments for parameter units and coupling.

Governing equations

The following source is the exact model kernel used by this adapter. For MARRMoT it includes the state derivative and each referenced flux function; the solver and routing are described above. Original notices and source citations are retained in the files.

# -*- coding: utf-8 -*-
import numpy as np
from numba import jit

from basinforge._vendor.hydromodel.model_config import get_model_param_config
from basinforge._vendor.hydromodel.param_utils import process_parameters


def hymod(
    p_and_e,
    parameters,
    warmup_length=30,
    return_state=False,
    normalized_params="auto",
    **kwargs,
):
    """
    Run Hymod model

    See https://www.proc-iahs.net/368/180/2015/piahs-368-180-2015.pdf for a scientific paper:
    Quan, Z.; Teng, J.; Sun, W.; Cheng, T. & Zhang, J. (2015): Evaluation of the HYMOD model
    for rainfall–runoff simulation using the GLUE method. Remote Sensing and GIS for Hydrology
    and Water Resources, 180 - 185, IAHS Publ. 368. DOI: 10.5194/piahs-368-180-2015.

    Parameters
    ----------
    p_and_e
        precipitation and potential evapotranspiration, 3-dim variable: [time, basin, feature=1]
    parameters
         five parameters: cmax, bexp, alpha, ks, kq
    warmup_length
        the length of warmup period
    return_state
        if True, return x_slow, x_quick, x_loss, else only return streamflow
    normalized_params
        parameter format specification:
        - "auto": automatically detect if parameters are normalized (0-1) or original scale (default)
        - True: parameters are normalized (0-1 range), will be converted to original scale
        - False: parameters are already in original scale, use as-is

    Returns
    -------
    Union[list, np.array]
        streamflow, x_slow, x_quick, x_loss or streamflow
    """
    model_param_dict = get_model_param_config("hymod", kwargs)
    # params
    param_ranges = model_param_dict["param_range"]

    # Process parameters using unified parameter handling
    processed_params = process_parameters(
        parameters, param_ranges, normalized=normalized_params
    )

    # Extract individual parameters from processed array
    cmax = processed_params[:, 0]
    bexp = processed_params[:, 1]
    alpha = processed_params[:, 2]
    ks = processed_params[:, 3]
    kq = processed_params[:, 4]
    if warmup_length > 0:
        # set no_grad for warmup periods
        p_and_e_warmup = p_and_e[0:warmup_length, :, :]
        _, _, x_slow, x_quick, x_loss = hymod(
            p_and_e_warmup,
            parameters,
            warmup_length=0,
            return_state=True,
            **kwargs,
        )
    else:
        # Initialize slow tank state
        # x_slow = 2.3503 / (ks * 22.5)
        x_slow = np.full(
            (p_and_e.shape[1], 1), 0.0
        )  # --> works ok if calibration data starts with low discharge
        # Initialize state(s) of quick tank(s)
        x_quick = np.full((p_and_e.shape[1], 3), 0.0)
        # HYMOD PROGRAM IS SIMPLE RAINFALL RUNOFF MODEL
        x_loss = np.full((p_and_e.shape[1], 1), 0.0)
    precip = p_and_e[warmup_length:, :, 0]
    pet = p_and_e[warmup_length:, :, 1]
    t = 0
    output = np.full(precip.shape, 0.0)
    # START PROGRAMMING LOOP WITH DETERMINING RAINFALL - RUNOFF AMOUNTS
    while t <= precip.shape[0] - 1:
        pval = precip[t, :]
        pet_val = pet[t, :]
        # Compute excess precipitation and evaporation
        er1, er2, x_loss = excess(x_loss, cmax, bexp, pval, pet_val)
        # Calculate total effective rainfall
        et = er1 + er2
        #  Now partition ER between quick and slow flow reservoirs
        uq = alpha * et
        us = (1 - alpha) * et
        # Route slow flow component with single linear reservoir
        x_slow, qs = linres(x_slow, us, ks)
        # Route quick flow component with linear reservoirs
        inflow = uq

        for i in range(3):
            # Linear reservoir
            x_quick[:, i], outflow = linres(x_quick[:, i], inflow, kq)
            inflow = outflow

        # Compute total flow for timestep
        output[t, :] = qs + outflow
        t += 1
    streamflow = np.expand_dims(output, axis=2)
    if return_state:
        return streamflow, et, x_slow, x_quick, x_loss
    return streamflow, et


# @jit
@jit(nopython=True)
def power(x, y):
    x = np.abs(x)  # Needed to capture invalid overflow with negative values
    return x**y


# @jit
@jit(nopython=True)
def linres(x_slow, inflow, rs):
    # Linear reservoir
    x_slow = (1 - rs) * x_slow + (1 - rs) * inflow
    outflow = (rs / (1 - rs)) * x_slow
    return x_slow, outflow


# @jit
@jit(nopython=True)
def excess(x_loss, cmax, bexp, pval, pet_val):
    # this function calculates excess precipitation and evaporation
    xn_prev = x_loss
    ct_prev = cmax * (
        1 - power((1 - ((bexp + 1) * (xn_prev) / cmax)), (1 / (bexp + 1)))
    )
    # Calculate Effective rainfall 1
    er1 = np.maximum((pval - cmax + ct_prev), 0.0)
    pval = pval - er1
    dummy = np.minimum(((ct_prev + pval) / cmax), 1)
    xn = (cmax / (bexp + 1)) * (1 - power((1 - dummy), (bexp + 1)))

    # Calculate Effective rainfall 2
    er2 = np.maximum(pval - (xn - xn_prev), 0)

    # Alternative approach
    evap = (
        1 - (((cmax / (bexp + 1)) - xn) / (cmax / (bexp + 1)))
    ) * pet_val  # actual ET is linearly related to the soil moisture state
    xn = np.maximum(xn - evap, 0)  # update state

    return er1, er2, xn


Simulation

from basinforge import Basin, get_model

basin = Basin.from_csv("basin.csv", basin_id="A", area_km2=1200,
                       q_unit="m3/s", timestep="daily")
q_mm = get_model("HYMOD_CLASSIC").simulate(basin)
q_m3s = basin.to_m3s(q_mm)

Supply your actual data and catchment area; temperature-dependent models require a temperature column. For non-daily models, choose an appropriate warmup in model steps.

See calibration, input requirements, sources and verification limitations.