Source code for httk.core.vectors.vector_frac_view

"""
A view presenting any vector backend as a FracVector (the exact-rational representation).
"""

from functools import cached_property
from typing import Any, Self

from httk.core.views import unwrap

from .fracvector import FracVector, Noms
from .vector_backend import VectorBackend
from .vector_like import VectorLike
from .vector_view import VectorView


[docs] class VectorFracView(VectorView, FracVector): r""" A view presenting an underlying vector backend as an exact :class:`~httk.core.vectors.fracvector.FracVector`. This view is a genuine FracVector, so it can be passed anywhere a FracVector is accepted, and it exposes the full exact-rational algebra (``det``/``inv``/``*``/...). It is built lazily on first access — adopting a frac backend's FracVector directly, otherwise converting from the backend's exact ``fractions`` interchange — so the round-trip is exactness-preserving for the frac and native backends. (numpy values are binary rationals, so a numpy source round-trips to the exact float64 rational, not necessarily the original decimal fraction.) Because inherited FracVector algebra builds its results with the low-level ``self.__class__._of(noms, denom)`` constructor, results built that way are plain (backend-less) FracVector values presented through this class. :param obj: The source value to present. :param \**hints: Backend-selection and view-conversion hints. """ _backend: VectorBackend def __new__(cls, obj: VectorLike, **hints: Any) -> Self: if isinstance(obj, cls): return obj backend = cls._prepare_backend(obj, hints) instance = super().__new__(cls) instance._backend = backend return instance def __init__(self, obj: VectorLike, **hints: Any) -> None: pass @property def fractions_exact(self) -> bool: """Return whether this view's Fraction interchange is exact.""" backend = getattr(self, "_backend", None) return True if backend is None else backend.fractions_exact def _fill_fractions(self) -> None: # Validate then assign: failed fills leave no partial presentation state, and fills must # not read shadowed attributes or they recurse. if "_backend" not in self.__dict__: return backend = self._backend if isinstance(backend, FracVector): built = backend else: built = FracVector(backend.fractions) FracVector._assign_raw(self, built.noms, built.denom) def _ensure_materialized(self) -> None: if "_backend" in self.__dict__ and "noms" not in self.__dict__: self._fill_fractions() @cached_property def noms(self) -> Noms: # type: ignore[override] # pyright: ignore[reportIncompatibleVariableOverride] """Return the materialized numerator data.""" self._fill_fractions() return self.__dict__["noms"] @cached_property def denom(self) -> int: # type: ignore[override] # pyright: ignore[reportIncompatibleVariableOverride] """Return the materialized common denominator.""" self._fill_fractions() return self.__dict__["denom"] @cached_property def _dim(self) -> tuple[int, ...] | None: # type: ignore[override] # pyright: ignore[reportIncompatibleVariableOverride] self._fill_fractions() return self.__dict__["_dim"]
[docs] def unwrap(self) -> Any: """Return the underlying unwrapped vector, or this value when no backend remains.""" backend = getattr(self, "_backend", None) if backend is None: return self return unwrap(backend)
[docs] def unview(self) -> Any: """Return a plain FracVector containing this view's presented data.""" # A frac backend already holds exactly the presented FracVector: reuse it. Otherwise # (converted or backend-less) build a plain FracVector reusing the materialized tuples. backend = getattr(self, "_backend", None) if isinstance(backend, FracVector): raw = backend if not isinstance(raw, VectorView): return raw return FracVector._of(self.noms, self.denom)