Source code for httk.core.vectors.vector_numpy_backend

"""
Backend wrapping a numpy ndarray (numpy is an optional dependency, imported lazily).
"""

import fractions
from typing import Any, Self

from .vector_api import Fractions
from .vector_backend import VectorBackend


def _is_real_numeric(dtype: Any) -> bool:
    import numpy

    return any(numpy.issubdtype(dtype, kind) for kind in (numpy.bool_, numpy.integer, numpy.floating))


[docs] class VectorNumpyBackend(VectorBackend): r""" Backend for a vector backed by a :class:`numpy.ndarray`. numpy is an optional dependency (``httk-core[numpy]``) and is imported lazily; if numpy is not installed, this backend's ``_backend_adopt`` returns None so it is simply never selected. numpy float64 values are themselves binary rationals, so the ``fractions`` interchange is produced *exactly* from the array (each float becomes its exact rational value). Only the reverse direction (building a numpy view) is lossy. ``unwrap`` returns the wrapped array. :param obj: The source data to wrap. :param \**hints: Optional backend-selection hints. """ _array: Any @classmethod def _backend_adopt(cls, obj: Any, **hints: Any) -> Self | None: r"""Adopt a numpy array when numpy is available and hints match. :param obj: The object to adopt. :param \**hints: Backend-selection hints. :return: An initialized backend, or ``None`` when ``obj`` is not accepted. """ if hints and hints.get("kind", "numpy") != "numpy": return None try: import numpy except ImportError: return None if not isinstance(obj, numpy.ndarray): return None return cls(obj, **hints) def __init__(self, obj: Any, **hints: Any) -> None: self._array = obj @property def fractions(self) -> Fractions: """Return the array in the exact Fraction interchange format.""" def rec(x: Any) -> Fractions: if isinstance(x, list): return tuple(rec(e) for e in x) return fractions.Fraction(x) return rec(self._array.tolist()) @property def dim(self) -> tuple[int, ...]: """Return the array shape.""" return tuple(self._array.shape)
[docs] def to_floats(self) -> Any: """Return floats through a numpy fast path for real numeric dtypes. Other dtypes fall back to :meth:`~httk.core.vectors.vector_api.VectorAPI.to_floats` so the exact hub conversion and its error semantics are preserved. :return: The rendered value. """ if _is_real_numeric(self._array.dtype): return self._array.astype(float).tolist() return super().to_floats()
[docs] def to_float(self) -> float: """Return a scalar float through numpy for real numeric 0-D arrays. Non-scalar arrays raise the same :class:`TypeError` as the hub implementation. Other scalar dtypes fall back to :meth:`~httk.core.vectors.vector_api.VectorAPI.to_float` for its exact conversion and error semantics. :return: The rendered scalar value. :raises TypeError: If the value is not scalar. """ if self.dim != (): raise TypeError(f"to_float: expected a scalar, got shape {self.dim}") if _is_real_numeric(self._array.dtype): return float(self._array) return super().to_float()
[docs] def unwrap(self) -> Any: """Return the wrapped numpy array.""" return self._array