Skip to content

Common Utilities and Configurations

This section covers common utilities, configurations, and shared components used across all hydrological models in hydromodel.

Model Configuration

Copyright (c) 2021-2022 Wenyu Ouyang. All rights reserved.

Parameter Utilities

Copyright (c) 2025 Wenyu Ouyang. All rights reserved.

detect_parameter_format(parameters, param_ranges)

Detect whether parameters are normalized (0-1 range) or in original scale.

Parameters

parameters : np.ndarray Model parameters array [basin, parameter] param_ranges : Dict[str, List[float]] Parameter ranges dictionary with min/max values for each parameter

Returns

bool True if parameters appear to be normalized (0-1 range), False otherwise

Source code in hydromodel/models/param_utils.py
def detect_parameter_format(
    parameters: np.ndarray, param_ranges: Dict[str, List[float]]
) -> bool:
    """
    Detect whether parameters are normalized (0-1 range) or in original scale.

    Parameters
    ----------
    parameters : np.ndarray
        Model parameters array [basin, parameter]
    param_ranges : Dict[str, List[float]]
        Parameter ranges dictionary with min/max values for each parameter

    Returns
    -------
    bool
        True if parameters appear to be normalized (0-1 range), False otherwise
    """
    # Check if all parameters are within [0, 1] range
    # Allow small tolerance for numerical precision
    tolerance = 1e-6

    # If any parameter is outside [0, 1] with tolerance, assume original scale
    if np.any(parameters < -tolerance) or np.any(parameters > 1 + tolerance):
        return False

    # Additional check: if parameters are suspiciously close to range boundaries
    # when interpreted as original values, they're likely normalized
    param_values = list(param_ranges.values())
    for i, param_range in enumerate(param_values):
        if i >= parameters.shape[1]:
            break
        param_col = parameters[:, i]
        min_val, max_val = param_range[0], param_range[1]

        # If parameters are all very close to 0-1 range but the actual range
        # is much larger, they're likely normalized
        if max_val - min_val > 2 and np.all(param_col <= 1.1):
            return True

    return True  # Default assumption: parameters are normalized

get_parameter_scales(param_ranges)

Extract parameter scales from param_ranges for backward compatibility.

Parameters

param_ranges : Dict[str, List[float]] Parameter ranges dictionary

Returns

Dict[str, List[float]] Dictionary mapping parameter names to [min, max] ranges

Source code in hydromodel/models/param_utils.py
def get_parameter_scales(
    param_ranges: Dict[str, List[float]],
) -> Dict[str, List[float]]:
    """
    Extract parameter scales from param_ranges for backward compatibility.

    Parameters
    ----------
    param_ranges : Dict[str, List[float]]
        Parameter ranges dictionary

    Returns
    -------
    Dict[str, List[float]]
        Dictionary mapping parameter names to [min, max] ranges
    """
    return param_ranges.copy()

normalize_parameters(parameters, param_ranges)

Convert parameters from original scale to normalized (0-1) scale.

Parameters

parameters : np.ndarray Model parameters in original scale [basin, parameter] param_ranges : Dict[str, List[float]] Parameter ranges dictionary

Returns

np.ndarray Parameters normalized to 0-1 scale

Source code in hydromodel/models/param_utils.py
def normalize_parameters(
    parameters: np.ndarray, param_ranges: Dict[str, List[float]]
) -> np.ndarray:
    """
    Convert parameters from original scale to normalized (0-1) scale.

    Parameters
    ----------
    parameters : np.ndarray
        Model parameters in original scale [basin, parameter]
    param_ranges : Dict[str, List[float]]
        Parameter ranges dictionary

    Returns
    -------
    np.ndarray
        Parameters normalized to 0-1 scale
    """
    normalized_params = np.zeros_like(parameters)
    param_list = list(param_ranges.values())

    for i, param_range in enumerate(param_list):
        min_val, max_val = param_range[0], param_range[1]
        normalized_params[:, i] = (parameters[:, i] - min_val) / (
            max_val - min_val
        )

    return normalized_params

process_parameters(parameters, param_ranges, normalized='auto')

Process model parameters to convert from normalized to original scale if needed.

This function provides a unified interface for parameter handling across all models, supporting both normalized (0-1 range) and original scale parameters.

Parameters

parameters : np.ndarray Model parameters array [basin, parameter] param_ranges : Dict[str, List[float]] Parameter ranges dictionary with min/max values for each parameter normalized : Union[bool, str], optional Parameter format specification: - "auto": Automatically detect parameter format (default) - True: Parameters are normalized (0-1 range), convert to original scale - False: Parameters are already in original scale, use as-is

Returns

np.ndarray Parameters in original scale, ready for model computation

Examples

param_ranges = {"K": [0.1, 1.0], "B": [0.1, 0.4]}

Normalized parameters

norm_params = np.array([[0.5, 0.8]]) # Will be converted to [0.55, 0.34] orig_params = process_parameters(norm_params, param_ranges, normalized=True)

Original scale parameters

orig_params = np.array([[0.55, 0.34]]) # Will be used as-is final_params = process_parameters(orig_params, param_ranges, normalized=False)

Source code in hydromodel/models/param_utils.py
def process_parameters(
    parameters: np.ndarray,
    param_ranges: Dict[str, List[float]],
    normalized: Union[bool, str] = "auto",
) -> np.ndarray:
    """
    Process model parameters to convert from normalized to original scale if needed.

    This function provides a unified interface for parameter handling across all models,
    supporting both normalized (0-1 range) and original scale parameters.

    Parameters
    ----------
    parameters : np.ndarray
        Model parameters array [basin, parameter]
    param_ranges : Dict[str, List[float]]
        Parameter ranges dictionary with min/max values for each parameter
    normalized : Union[bool, str], optional
        Parameter format specification:
        - "auto": Automatically detect parameter format (default)
        - True: Parameters are normalized (0-1 range), convert to original scale
        - False: Parameters are already in original scale, use as-is

    Returns
    -------
    np.ndarray
        Parameters in original scale, ready for model computation

    Examples
    --------
    >>> param_ranges = {"K": [0.1, 1.0], "B": [0.1, 0.4]}
    >>> # Normalized parameters
    >>> norm_params = np.array([[0.5, 0.8]])  # Will be converted to [0.55, 0.34]
    >>> orig_params = process_parameters(norm_params, param_ranges, normalized=True)
    >>>
    >>> # Original scale parameters
    >>> orig_params = np.array([[0.55, 0.34]])  # Will be used as-is
    >>> final_params = process_parameters(orig_params, param_ranges, normalized=False)
    """
    if parameters.shape[1] != len(param_ranges):
        raise ValueError(
            f"Parameter array has {parameters.shape[1]} columns but "
            f"param_ranges has {len(param_ranges)} parameters"
        )

    # Auto-detect parameter format if requested
    if normalized == "auto":
        normalized = detect_parameter_format(parameters, param_ranges)

    # If parameters are already in original scale, return as-is
    if not normalized:
        return parameters.copy()

    # Convert normalized parameters to original scale
    converted_params = np.zeros_like(parameters)
    param_list = list(param_ranges.values())

    for i, param_range in enumerate(param_list):
        min_val, max_val = param_range[0], param_range[1]
        converted_params[:, i] = min_val + parameters[:, i] * (
            max_val - min_val
        )

    return converted_params

validate_parameters(parameters, param_ranges, normalized=False)

Validate that parameters are within acceptable ranges.

Parameters

parameters : np.ndarray Model parameters array [basin, parameter] param_ranges : Dict[str, List[float]] Parameter ranges dictionary normalized : bool, optional Whether parameters are normalized (default: False)

Returns

bool True if all parameters are within valid ranges

Source code in hydromodel/models/param_utils.py
def validate_parameters(
    parameters: np.ndarray,
    param_ranges: Dict[str, List[float]],
    normalized: bool = False,
) -> bool:
    """
    Validate that parameters are within acceptable ranges.

    Parameters
    ----------
    parameters : np.ndarray
        Model parameters array [basin, parameter]
    param_ranges : Dict[str, List[float]]
        Parameter ranges dictionary
    normalized : bool, optional
        Whether parameters are normalized (default: False)

    Returns
    -------
    bool
        True if all parameters are within valid ranges
    """
    if normalized:
        # For normalized parameters, check 0-1 range
        return np.all(parameters >= 0) and np.all(parameters <= 1)
    else:
        # For original scale parameters, check against param_ranges
        param_list = list(param_ranges.values())
        for i, param_range in enumerate(param_list):
            if i >= parameters.shape[1]:
                break
            min_val, max_val = param_range[0], param_range[1]
            param_col = parameters[:, i]
            if np.any(param_col < min_val) or np.any(param_col > max_val):
                return False
        return True

Model Dictionary

rmse43darr(obs, sim)

RMSE for 3D array

Parameters

obs : np.ndarray observation data sim : np.ndarray simulation data

Returns

type description

Raises

ValueError description

Source code in hydromodel/models/model_dict.py
def rmse43darr(obs, sim):
    """RMSE for 3D array

    Parameters
    ----------
    obs : np.ndarray
        observation data
    sim : np.ndarray
        simulation data

    Returns
    -------
    _type_
        _description_

    Raises
    ------
    ValueError
        _description_
    """
    rmses = np.sqrt(np.nanmean((sim - obs) ** 2, axis=0))
    rmse = rmses.mean(axis=0)
    if np.isnan(rmse) or any(np.isnan(sim)):
        raise ValueError(
            "RMSE is nan or there are nan values in the simulation data, "
            "please check the input data."
        )
    # tolist is necessary for spotpy to get the value
    # otherwise the print will incur to an issue
    # https://github.com/thouska/spotpy/issues/319
    return rmse.tolist()