"""Runtime input validation for public entry points.
These helpers enforce the type, dtype, and shape invariants that the numerical
routines assume, so that user mistakes surface as clear ``TypeError`` /
``ValueError`` messages at the library boundary.
Only array validation lives here, because it bundles several easy-to-get-wrong
steps (isinstance, dtype kind, shape/size) behind one clear message.
Convention (mirroring SciPy): public functions validate their own inputs; private
``_``-prefixed compute routines trust their callers and use plain ``assert`` only
for internal invariants that cannot be triggered by user input.
"""
from typing import Any, cast
import numpy as np
from numpy.typing import NDArray
from dolphindes.types import BoolGrid
__all__ = [
"validate_bool_mask",
"validate_numeric_array",
]
[docs]
def validate_bool_mask(
mask: object,
name: str,
*,
shape: tuple[int, ...] | None = None,
size: int | None = None,
) -> BoolGrid:
"""
Validate that ``mask`` is a boolean NumPy array of the expected extent.
Parameters
----------
mask : object
The value to validate.
name : str
Name of the argument, used in error messages.
shape : tuple of int, optional
If given, the exact shape ``mask`` must have.
size : int, optional
If given, the exact number of elements ``mask`` must have. Useful when
the rank is allowed to vary but the total extent is fixed.
Returns
-------
numpy.ndarray of bool
The validated ``mask`` (returned unchanged for convenient chaining).
Raises
------
TypeError
If ``mask`` is not a ``numpy.ndarray``.
ValueError
If ``mask`` is not of boolean dtype or does not match ``shape``/``size``.
"""
if not isinstance(mask, np.ndarray):
raise TypeError(
f"{name} must be a numpy.ndarray, got {type(mask).__name__}."
)
if mask.dtype != np.bool_:
raise ValueError(
f"{name} must have boolean dtype, got dtype {mask.dtype}. "
"Construct it with e.g. np.zeros(shape, dtype=bool)."
)
if shape is not None and mask.shape != shape:
raise ValueError(f"{name} must have shape {shape}, got {mask.shape}.")
if size is not None and mask.size != size:
raise ValueError(f"{name} must have {size} elements, got {mask.size}.")
return cast(BoolGrid, mask)
[docs]
def validate_numeric_array(
arr: object,
name: str,
*,
shape: tuple[int, ...] | None = None,
size: int | None = None,
) -> NDArray[Any]:
"""
Validate that ``arr`` is a numeric NumPy array of the expected extent.
A numeric dtype is any integer, unsigned-integer, floating, or complex
dtype; real arrays are accepted where a complex field is expected because
they are a valid special case.
Parameters
----------
arr : object
The value to validate.
name : str
Name of the argument, used in error messages.
shape : tuple of int, optional
If given, the exact shape ``arr`` must have.
size : int, optional
If given, the exact number of elements ``arr`` must have.
Returns
-------
numpy.ndarray
The validated ``arr`` (returned unchanged for convenient chaining).
Raises
------
TypeError
If ``arr`` is not a ``numpy.ndarray``.
ValueError
If ``arr`` is not numeric or does not match ``shape``/``size``.
"""
if not isinstance(arr, np.ndarray):
raise TypeError(f"{name} must be a numpy.ndarray, got {type(arr).__name__}.")
if arr.dtype.kind not in "iufc":
raise ValueError(
f"{name} must have a numeric (integer, float, or complex) dtype, "
f"got dtype {arr.dtype}."
)
if shape is not None and arr.shape != shape:
raise ValueError(f"{name} must have shape {shape}, got {arr.shape}.")
if size is not None and arr.size != size:
raise ValueError(f"{name} must have {size} elements, got {arr.size}.")
return cast(NDArray[Any], arr)