Source code for httk.atomistic.models.sites.view
"""
A view presenting any sites backend as a Sites object (the class representation).
"""
import fractions
from functools import cached_property
from typing import Any, Self
from httk.core import FracVector, unwrap
from httk.atomistic.models._vector_guards import to_fracvector, to_precision
from httk.atomistic.models.sites.api import SitesAPI
from httk.atomistic.models.sites.backend import SitesBackend
from httk.atomistic.models.sites.like import SitesLike
from httk.atomistic.models.sites.sites import Sites
from httk.atomistic.models.sites.view_base import SitesViewBase
[docs]
class SitesView(SitesViewBase, Sites):
r"""
A view presenting an underlying sites backend as a ``Sites`` object.
This view is a genuine ``Sites``, so it can be passed anywhere a Sites is accepted.
Its state is built lazily on first access from the backend.
:param obj: The sites-like object to present.
:param \**hints: Backend-selection hints.
"""
_backend: SitesBackend
def __new__(cls, obj: SitesLike, **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: SitesLike, **hints: Any) -> None:
pass
# Validate then assign: failed fills leave no partial presentation state, and fills must not
# read shadowed attributes or they recurse.
def _fill_reduced_coords(self) -> None:
coords = to_fracvector(self._backend.reduced_coords)
if coords.dim != () and not (len(coords.dim) == 2 and coords.dim[1] == 3):
raise ValueError("Sites reduced_coords must be an Nx3 vector-like")
object.__setattr__(self, "_reduced_coords", coords)
def _fill_precision(self) -> None:
object.__setattr__(self, "_precision", to_precision(self._backend.precision))
@cached_property
def _reduced_coords(self) -> FracVector: # type: ignore[override] # pyright: ignore[reportIncompatibleVariableOverride]
self._fill_reduced_coords()
return self.__dict__["_reduced_coords"]
@cached_property
def _precision(self) -> fractions.Fraction | None: # type: ignore[override] # pyright: ignore[reportIncompatibleVariableOverride]
self._fill_precision()
return self.__dict__["_precision"]
[docs]
def reduced_coords_floats(self) -> list[list[float]]:
"""Present the backend's native float coordinates, else the validated exact ones.
:return: The reduced coordinates as float rows.
"""
# Only a backend that overrides the float accessor may bypass the exact fill: that fill
# is where the Nx3 validation lives.
backend = self._backend
if type(backend).reduced_coords_floats is not SitesAPI.reduced_coords_floats:
return backend.reduced_coords_floats()
return super().reduced_coords_floats()
@property
[docs]
def num_sites(self) -> int:
"""Return the number of coordinate rows without forcing the exact fill when unfilled.
:return: The number of sites.
"""
if "_reduced_coords" in self.__dict__:
return super().num_sites
return self._backend.num_sites
def __len__(self) -> int:
"""Return the number of sites.
:return: The number of coordinate rows.
"""
return self.num_sites
[docs]
def unwrap(self) -> Any:
"""Return the raw object behind the backend.
:return: The unwrapped source object.
"""
return unwrap(self._backend)
def __reduce__(self) -> tuple[type[Self], tuple[SitesBackend]]:
"""Rebuild the view from its backend during pickling.
:return: The view constructor and its backend argument.
"""
return type(self), (self._backend,)
[docs]
def unview(self) -> Sites:
"""Return this presentation as standalone exact sites.
:return: The exact sites representation.
"""
# The folded design makes a genuine Sites backend exactly the presented value: reuse it.
backend = self._backend
if type(backend) is Sites:
return backend
return Sites(self._reduced_coords, self._precision)