Utils Checks#

checks #

CLASS DESCRIPTION
Interval

Represents a mathematical interval with bounds and inclusivity.

RangeValidationError

Custom exception for range validation.

FUNCTION DESCRIPTION
check_in_interval

Validate that a value is within the specified interval.

check_isinstance

Check that all elements in X are of the specified type.

check_same_shape

Check that two tensors have the same shape along specified axis.

check_same_len

Check that two array-like objects have the same length.

check_same_device

Check that two tensor objects are on the same device.

check_square_shape

Check that tensor(s) have square shape (n_rows == n_cols).

check_ndim

Check that tensor(s) have the specified number of dimensions.

check_2d

Check that tensor(s) are 2-dimensional.

check_1d

Check that tensor(s) are 1-dimensional.

check_array_type

Checks if X is a torch.Tensor or np.ndarray (array-like type).

check_is_tensor

Checks if X is a torch.Tensor and if not, raises a TypeError.

check_to_tensor

Convert array-like input to torch.Tensor.

check_sample_weights

Check that weights can be used as sample weights.

experimental

This is a decorator which can be used to mark functions as experimental.

Classes#

Interval(left: float, right: float, left_closed: bool, right_closed: bool) dataclass #

Represents a mathematical interval with bounds and inclusivity.

METHOD DESCRIPTION
contains

Check if value is within the interval.

Functions#
contains(value: float) -> bool #

Check if value is within the interval.

Source code in spectre/utils/checks.py
def contains(self, value: float) -> bool:
    """Check if value is within the interval."""
    left_side = (
        (value > self.left) if not self.left_closed else (value >= self.left)
    )
    right_side = (
        (value < self.right) if not self.right_closed else (value <= self.right)
    )
    return left_side and right_side

RangeValidationError(value: float, interval: Interval | None = None, min_val: float | None = None, max_val: float | None = None) #

Bases: ValueError

Custom exception for range validation.

Initialize RangeValidationError.

PARAMETER DESCRIPTION
value

The invalid value.

TYPE: float

interval

The interval constraint that was violated, by default None.

TYPE: Interval | None DEFAULT: None

min_val

Minimum allowed value (for backward compatibility), by default None.

TYPE: float | None DEFAULT: None

max_val

Maximum allowed value (for backward compatibility), by default None.

TYPE: float | None DEFAULT: None

Source code in spectre/utils/checks.py
def __init__(
    self,
    value: float,
    interval: Interval | None = None,
    min_val: float | None = None,
    max_val: float | None = None,
) -> None:
    """Initialize `RangeValidationError`.

    Parameters
    ----------
    value : float
        The invalid value.
    interval : Interval | None, optional
        The interval constraint that was violated, by default None.
    min_val : float | None, optional
        Minimum allowed value (for backward compatibility), by default None.
    max_val : float | None, optional
        Maximum allowed value (for backward compatibility), by default None.
    """
    self.value = value
    self.interval = interval
    self.min_val = min_val
    self.max_val = max_val

    if interval is not None:
        super().__init__(f"Value {value} not in interval {interval}")
    else:
        super().__init__(f"Value {value} out of range [{min_val}, {max_val}]")

Functions#

parse_interval(interval_str: str) -> Interval #

Parse mathematical interval notation into an Interval object.

Supports standard mathematical interval notation: - [a, b]: closed interval (a ≤ x ≤ b) - (a, b): open interval (a < x < b) - [a, b): left-closed, right-open (a ≤ x < b) - (a, b]: left-open, right-closed (a < x ≤ b) - Infinite bounds: (-∞, b), (a, ∞), (-∞, ∞)

PARAMETER DESCRIPTION
interval_str

Interval in mathematical notation, e.g., "[0, 1)", "(0, ∞)".

TYPE: str

RETURNS DESCRIPTION
Interval

Parsed interval object.

RAISES DESCRIPTION
ValueError

If the interval string cannot be parsed.

Examples:

>>> parse_interval("[0, 1]")
Interval(left=0.0, right=1.0, left_closed=True, right_closed=True)
>>> parse_interval("(0, ∞)")
Interval(left=0.0, right=inf, left_closed=False, right_closed=False)
Source code in spectre/utils/checks.py
def parse_interval(interval_str: str) -> Interval:
    """
    Parse mathematical interval notation into an Interval object.

    Supports standard mathematical interval notation:
    - [a, b]: closed interval (a ≤ x ≤ b)
    - (a, b): open interval (a < x < b)
    - [a, b): left-closed, right-open (a ≤ x < b)
    - (a, b]: left-open, right-closed (a < x ≤ b)
    - Infinite bounds: (-∞, b), (a, ∞), (-∞, ∞)

    Parameters
    ----------
    interval_str : str
        Interval in mathematical notation, e.g., "[0, 1)", "(0, ∞)".

    Returns
    -------
    Interval
        Parsed interval object.

    Raises
    ------
    ValueError
        If the interval string cannot be parsed.

    Examples
    --------
    >>> parse_interval("[0, 1]")
    Interval(left=0.0, right=1.0, left_closed=True, right_closed=True)

    >>> parse_interval("(0, ∞)")
    Interval(left=0.0, right=inf, left_closed=False, right_closed=False)
    """
    # Remove whitespace
    interval_str = interval_str.strip()

    # Check for valid bracket format
    pattern = r"^([\[\(])([^,]+),\s*([^,]+)([\]\)])$"
    match = re.match(pattern, interval_str)

    if not match:
        raise ValueError(
            f"Invalid interval format: '{interval_str}'. "
            f"Expected format like '[a, b]', '(a, b)', etc."
        )

    left_bracket, left_str, right_str, right_bracket = match.groups()

    # Parse bounds
    def parse_bound(bound_str: str) -> float:
        bound_str = bound_str.strip()
        if bound_str in ["-∞", "-inf", "-infinity"]:
            return -math.inf
        elif bound_str in ["∞", "inf", "infinity"]:
            return math.inf
        else:
            try:
                return float(bound_str)
            except ValueError:
                raise ValueError(f"Cannot parse bound: '{bound_str}'")

    left = parse_bound(left_str)
    right = parse_bound(right_str)
    left_closed = left_bracket == "["
    right_closed = right_bracket == "]"

    return Interval(left, right, left_closed, right_closed)

check_in_interval(value: float, interval: str | Interval) -> None #

Validate that a value is within the specified interval.

PARAMETER DESCRIPTION
value

Value to validate.

TYPE: float

interval

Interval constraint. Can be a string in mathematical notation (e.g., "[0, 1)") or an Interval object.

TYPE: str | Interval

RAISES DESCRIPTION
RangeValidationError

If the value is not within the specified interval.

Examples:

>>> check_in_interval(0.5, "[0, 1]")
# No error - value is valid
>>> check_in_interval(1.5, "[0, 1)")
RangeValidationError: Value 1.5 not in interval [0, 1)
Source code in spectre/utils/checks.py
def check_in_interval(value: float, interval: str | Interval) -> None:
    """
    Validate that a value is within the specified interval.

    Parameters
    ----------
    value : float
        Value to validate.
    interval : str | Interval
        Interval constraint. Can be a string in mathematical notation
        (e.g., "[0, 1)") or an Interval object.

    Raises
    ------
    RangeValidationError
        If the value is not within the specified interval.

    Examples
    --------
    >>> check_in_interval(0.5, "[0, 1]")
    # No error - value is valid

    >>> check_in_interval(1.5, "[0, 1)")
    RangeValidationError: Value 1.5 not in interval [0, 1)
    """
    if isinstance(interval, str):
        interval = parse_interval(interval)

    if not interval.contains(value):
        raise RangeValidationError(value, interval)

check_isinstance(X: list | Any, type: Any) -> None #

Check that all elements in X are of the specified type.

PARAMETER DESCRIPTION
X

Single object or list/tuple of objects to check.

TYPE: list | Any

type

Expected type for all elements.

TYPE: Any

Source code in spectre/utils/checks.py
def check_isinstance(X: list | Any, type: Any) -> None:
    """
    Check that all elements in X are of the specified type.

    Parameters
    ----------
    X : list | Any
        Single object or list/tuple of objects to check.
    type : Any
        Expected type for all elements.
    """
    if not isinstance(X, (list, tuple)):
        val = isinstance(X, type)
    else:
        val = all(isinstance(x, type) for x in X)

    if not val:
        raise TypeError(f"Not all instances in `X` are of type `{type}`.")

check_same_shape(X: np.ndarray | torch.Tensor, Y: np.ndarray | torch.Tensor, axis: int | None = None) -> None #

Check that two tensors have the same shape along specified axis.

PARAMETER DESCRIPTION
X

First tensor to compare.

TYPE: ndarray | Tensor

Y

Second tensor to compare.

TYPE: ndarray | Tensor

axis

Axis along which to compare shapes. If None, compares full shapes.

TYPE: int | None DEFAULT: None

Source code in spectre/utils/checks.py
def check_same_shape(
    X: np.ndarray | torch.Tensor,
    Y: np.ndarray | torch.Tensor,
    axis: int | None = None,
) -> None:
    """
    Check that two tensors have the same shape along specified axis.

    Parameters
    ----------
    X : np.ndarray | torch.Tensor
        First tensor to compare.
    Y : np.ndarray | torch.Tensor
        Second tensor to compare.
    axis : int | None, optional
        Axis along which to compare shapes. If None, compares full shapes.
    """
    if axis is not None and isinstance(axis, int):
        X_shape = X.shape[axis]
        Y_shape = Y.shape[axis]
    else:
        X_shape = X.shape
        Y_shape = Y.shape

    if X_shape != Y_shape:
        raise ValueError(
            f"Tensors `X` and `Y` are expected to have the same shape along "
            f"`axis={axis}`, but are {X_shape} and {Y_shape}."
        )

check_same_len(X: list | np.ndarray | torch.Tensor, Y: list | np.ndarray | torch.Tensor) -> None #

Check that two array-like objects have the same length.

PARAMETER DESCRIPTION
X

First array-like object.

TYPE: list | ndarray | Tensor

Y

Second array-like object.

TYPE: list | ndarray | Tensor

Source code in spectre/utils/checks.py
def check_same_len(
    X: list | np.ndarray | torch.Tensor,
    Y: list | np.ndarray | torch.Tensor,
) -> None:
    """
    Check that two array-like objects have the same length.

    Parameters
    ----------
    X : list | np.ndarray | torch.Tensor
        First array-like object.
    Y : list | np.ndarray | torch.Tensor
        Second array-like object.
    """
    if len(X) != len(Y):
        raise ValueError(
            f"Array-like `X` and `Y` are expected to have the same length, but are "
            f"{len(X)} and {len(Y)}, respectively."
        )

check_same_device(X: torch.Tensor, Y: torch.Tensor) -> None #

Check that two tensor objects are on the same device.

PARAMETER DESCRIPTION
X

First tensor object.

TYPE: Tensor

Y

Second tensor object.

TYPE: Tensor

Source code in spectre/utils/checks.py
def check_same_device(X: torch.Tensor, Y: torch.Tensor) -> None:
    """
    Check that two tensor objects are on the same device.

    Parameters
    ----------
    X : torch.Tensor
        First tensor object.
    Y : torch.Tensor
        Second tensor object.
    """
    if X.device != Y.device:
        raise ValueError(
            f"Tensors `X` and `Y` are expected to be on the same device, but are "
            f"{X.device} and {Y.device}, respectively."
        )

check_same_dtype(X: torch.Tensor, Y: torch.Tensor) -> None #

Check that two tensor objects have the same dtype.

PARAMETER DESCRIPTION
X

First tensor object.

TYPE: Tensor

Y

Second tensor object.

TYPE: Tensor

Source code in spectre/utils/checks.py
def check_same_dtype(X: torch.Tensor, Y: torch.Tensor) -> None:
    """
    Check that two tensor objects have the same dtype.

    Parameters
    ----------
    X : torch.Tensor
        First tensor object.
    Y : torch.Tensor
        Second tensor object.
    """
    if X.dtype != Y.dtype:
        raise TypeError(
            f"Tensors `X` and `Y` are expected to have the same dtype, but are "
            f"{X.dtype} and {Y.dtype}, respectively."
        )

check_square_shape(X: np.ndarray | torch.Tensor | list) -> None #

Check that tensor(s) have square shape (n_rows == n_cols).

PARAMETER DESCRIPTION
X

Tensor or list of tensors to check for square shape.

TYPE: ndarray | Tensor | list

Source code in spectre/utils/checks.py
def check_square_shape(X: np.ndarray | torch.Tensor | list) -> None:
    """
    Check that tensor(s) have square shape (n_rows == n_cols).

    Parameters
    ----------
    X : np.ndarray | torch.Tensor | list
        Tensor or list of tensors to check for square shape.
    """
    if isinstance(X, (torch.Tensor, np.ndarray)):
        X = [X]
    for k in range(len(X)):
        if X[k].shape[0] != X[k].shape[1]:
            raise ValueError(
                f"Expected square shape, but got {X[k].shape[0]} != {X[k].shape[1]} "
                f"for tensor {k}."
            )

check_ndim(X: np.ndarray | torch.Tensor | list, ndim: int = 1) -> None #

Check that tensor(s) have the specified number of dimensions.

PARAMETER DESCRIPTION
X

Tensor or list of tensors to check.

TYPE: ndarray | Tensor | list

ndim

Expected number of dimensions, by default 1.

TYPE: int DEFAULT: 1

Source code in spectre/utils/checks.py
def check_ndim(X: np.ndarray | torch.Tensor | list, ndim: int = 1) -> None:
    """
    Check that tensor(s) have the specified number of dimensions.

    Parameters
    ----------
    X : np.ndarray | torch.Tensor | list
        Tensor or list of tensors to check.
    ndim : int, optional
        Expected number of dimensions, by default 1.
    """
    if isinstance(X, (torch.Tensor, np.ndarray)):
        X = [X]
    for k in range(len(X)):
        if X[k].ndim != ndim:
            raise ValueError(
                f"Expected `X[{k}].ndim={ndim}`, but got {X[k].ndim} instead."
            )

check_2d(X: np.ndarray | torch.Tensor | list) -> None #

Check that tensor(s) are 2-dimensional.

PARAMETER DESCRIPTION
X

Tensor or list of tensors to check.

TYPE: ndarray | Tensor | list

Source code in spectre/utils/checks.py
def check_2d(X: np.ndarray | torch.Tensor | list) -> None:
    """
    Check that tensor(s) are 2-dimensional.

    Parameters
    ----------
    X : np.ndarray | torch.Tensor | list
        Tensor or list of tensors to check.
    """
    if isinstance(X, (torch.Tensor, np.ndarray)):
        X = [X]
    for k in range(len(X)):
        if X[k].ndim != 2:
            raise ValueError(
                f"Expected tensor {k} to be 2d, but got {X[k].ndim}d instead."
            )

check_1d(X: np.ndarray | torch.Tensor | list) -> None #

Check that tensor(s) are 1-dimensional.

PARAMETER DESCRIPTION
X

Tensor or list of tensors to check.

TYPE: ndarray | Tensor | list

Source code in spectre/utils/checks.py
def check_1d(X: np.ndarray | torch.Tensor | list) -> None:
    """
    Check that tensor(s) are 1-dimensional.

    Parameters
    ----------
    X : np.ndarray | torch.Tensor | list
        Tensor or list of tensors to check.
    """
    if isinstance(X, (torch.Tensor, np.ndarray)):
        X = [X]
    for k in range(len(X)):
        if X[k].ndim != 1:
            raise ValueError(
                f"Expected tensor {k} to be 1d, but got {X[k].ndim}d instead."
            )

check_array_type(X: ArrayLike) -> None #

Checks if X is a torch.Tensor or np.ndarray (array-like type).

PARAMETER DESCRIPTION
X

Input data.

TYPE: Union[ndarray, Tensor]

Source code in spectre/utils/checks.py
def check_array_type(X: ArrayLike) -> None:
    """
    Checks if X is a torch.Tensor or np.ndarray (array-like type).

    Parameters
    ----------
    X : Union[np.ndarray, torch.Tensor]
        Input data.
    """
    if not isinstance(X, (torch.Tensor, np.ndarray)):
        raise TypeError(
            f"Expected `X` to be a list of `torch.Tensor` or `np.ndarray` but got "
            f"{type(X)}."
        )

check_is_tensor(X: Any) -> None #

Checks if X is a torch.Tensor and if not, raises a TypeError.

PARAMETER DESCRIPTION
X

Input data.

TYPE: Any

Source code in spectre/utils/checks.py
def check_is_tensor(X: Any) -> None:
    """
    Checks if X is a torch.Tensor and if not, raises a TypeError.

    Parameters
    ----------
    X : Any
        Input data.
    """
    if not torch.is_tensor(X):
        raise TypeError(
            f"Expected `X` to be a `torch.Tensor` but got {type(X).__name__}."
        )

check_to_tensor(X: ArrayLike, dtype: torch.dtype = torch.float32, device: torch.device | None = None) -> torch.Tensor #

Convert array-like input to torch.Tensor.

Handles both numpy arrays and existing torch tensors, converting them to the specified dtype and device.

PARAMETER DESCRIPTION
X

Input array (numpy array or torch tensor).

TYPE: ArrayLike

dtype

Target data type for the tensor, by default torch.float32.

TYPE: dtype DEFAULT: float32

device

Target device for the tensor. If None, keeps original device. By default None.

TYPE: device | None DEFAULT: None

RETURNS DESCRIPTION
Tensor

Converted tensor with specified dtype and device.

RAISES DESCRIPTION
TypeError

If X is not a torch.Tensor or np.ndarray.

Source code in spectre/utils/checks.py
def check_to_tensor(
    X: ArrayLike, dtype: torch.dtype = torch.float32, device: torch.device | None = None
) -> torch.Tensor:
    """
    Convert array-like input to torch.Tensor.

    Handles both numpy arrays and existing torch tensors, converting them
    to the specified dtype and device.

    Parameters
    ----------
    X : ArrayLike
        Input array (numpy array or torch tensor).

    dtype : torch.dtype, optional
        Target data type for the tensor, by default torch.float32.

    device : torch.device | None, optional
        Target device for the tensor. If None, keeps original device.
        By default None.

    Returns
    -------
    torch.Tensor
        Converted tensor with specified dtype and device.

    Raises
    ------
    TypeError
        If X is not a torch.Tensor or np.ndarray.
    """
    if not isinstance(X, ArrayLike):
        raise TypeError(
            f"Expected `X` to be a `torch.Tensor` or `np.ndarray` but got {type(X)}."
        )

    # Convert to tensor if numpy array, or cast existing tensor
    if isinstance(X, np.ndarray):
        tensor = torch.from_numpy(X).to(dtype=dtype)
    else:  # Already a torch.Tensor
        tensor = X.to(dtype=dtype)

    if device is not None:
        tensor = tensor.to(device)

    return tensor

check_from_tensor(X: torch.Tensor) -> np.ndarray #

Convert torch.Tensor to numpy array.

PARAMETER DESCRIPTION
X

Input tensor

TYPE: Tensor

RETURNS DESCRIPTION
ndarray

Converted numpy array on CPU.

Source code in spectre/utils/checks.py
def check_from_tensor(X: torch.Tensor) -> np.ndarray:
    """
    Convert torch.Tensor to numpy array.

    Parameters
    ----------
    X : torch.Tensor
        Input tensor

    Returns
    -------
    np.ndarray
        Converted numpy array on CPU.
    """
    if not torch.is_tensor(X):
        raise TypeError(
            f"Expected `X` to be a `torch.Tensor` but got {type(X).__name__}."
        )
    if X.device.type == "cpu":
        return X.detach().numpy()
    elif X.device.type == "cuda":
        return X.detach().cpu().numpy()

check_all_positive(X: torch.Tensor) -> None #

Check that all elements in the array-like input are positive.

PARAMETER DESCRIPTION
X

Input array.

TYPE: Tensor

RAISES DESCRIPTION
ValueError

If any element in X is not positive.

Source code in spectre/utils/checks.py
def check_all_positive(X: torch.Tensor) -> None:
    """
    Check that all elements in the array-like input are positive.


    Parameters
    ----------
    X : torch.Tensor
        Input array.


    Raises
    ------
    ValueError
        If any element in X is not positive.
    """
    if not torch.all(X > 0):
        raise ValueError("All elements in `X` must be positive.")

check_all_non_negative(X: torch.Tensor) -> None #

Check that all elements in the array-like input are non-negative.

PARAMETER DESCRIPTION
X

Input array.

TYPE: Tensor

RAISES DESCRIPTION
ValueError

If any element in X is negative.

Source code in spectre/utils/checks.py
def check_all_non_negative(X: torch.Tensor) -> None:
    """
    Check that all elements in the array-like input are non-negative.


    Parameters
    ----------
    X : torch.Tensor
        Input array.


    Raises
    ------
    ValueError
        If any element in X is negative.
    """
    if not torch.all(X >= 0):
        raise ValueError("All elements in `X` must be non-negative.")

check_sample_weights(weights: torch.Tensor) -> None #

Check that weights can be used as sample weights.

PARAMETER DESCRIPTION
weights

Input array.

TYPE: Tensor

RAISES DESCRIPTION
ValueError

If any element in weights is negative.

Source code in spectre/utils/checks.py
def check_sample_weights(weights: torch.Tensor) -> None:
    """
    Check that `weights` can be used as sample weights.

    Parameters
    ----------
    weights : torch.Tensor
        Input array.

    Raises
    ------
    ValueError
        If any element in weights is negative.
    """
    if torch.isnan(weights).any():
        raise ValueError("All elements in `weights` must be finite, got NaN.")
    if torch.isinf(weights).any():
        raise ValueError("All elements in `weights` must be finite, got inf.")
    if (weights < 0).any():
        raise ValueError("All elements in `weights` must be non-negative.")

experimental(reason: str | F) -> Callable[[F], F] | F #

experimental(reason: str) -> Callable[[F], F]
experimental(reason: F) -> F

This is a decorator which can be used to mark functions as experimental. It will result in a warning being emitted when the function is used.

PARAMETER DESCRIPTION
reason

Additional comment for the decorator, or the function being decorated.

TYPE: str | Callable

RETURNS DESCRIPTION
Callable | function

Decorated function or decorator.

Source code in spectre/utils/checks.py
def experimental(reason: str | F) -> Callable[[F], F] | F:
    """
    This is a decorator which can be used to mark functions as experimental.
    It will result in a warning being emitted when the function is used.

    Parameters
    ----------
    reason : str | Callable
        Additional comment for the decorator, or the function being decorated.

    Returns
    -------
    Callable | function
        Decorated function or decorator.
    """
    if isinstance(reason, str):

        def decorator(func1):
            if inspect.isclass(func1):
                fmt1 = "Call to experimental class {name} ({reason})."
            else:
                fmt1 = "Call to experimental function {name} ({reason})."

            @functools.wraps(func1)
            def new_func1(*args, **kwargs):
                warnings.simplefilter("always", UserWarning)
                warnings.warn(
                    fmt1.format(name=func1.__name__, reason=reason),
                    category=UserWarning,
                    stacklevel=2,
                )
                warnings.simplefilter("default", UserWarning)
                return func1(*args, **kwargs)

            return new_func1

        return decorator

    elif inspect.isclass(reason) or inspect.isfunction(reason):
        func2 = reason

        if inspect.isclass(func2):
            fmt2 = "Call to experimental class {name}."
        else:
            fmt2 = "Call to experimental function {name}."

        @functools.wraps(func2)
        def new_func2(*args, **kwargs):
            warnings.simplefilter("always", UserWarning)
            warnings.warn(
                fmt2.format(name=func2.__name__),
                category=UserWarning,
                stacklevel=2,
            )
            warnings.simplefilter("default", UserWarning)
            return func2(*args, **kwargs)

        return new_func2

    else:
        raise TypeError(repr(type(reason)))