Source code for httk.atomistic.models.cell.view
"""
A view presenting any cell backend as a Cell (the class representation).
"""
import fractions
from functools import cached_property
from typing import Any, Self
from httk.core import SurdScalar, SurdVector, unwrap
from httk.atomistic.models._vector_guards import to_periodicity, to_precision, to_surdscalar, to_surdvector
from httk.atomistic.models.cell.backend import CellBackend
from httk.atomistic.models.cell.cell import Cell
from httk.atomistic.models.cell.like import CellLike
from httk.atomistic.models.cell.view_base import CellViewBase
[docs]
class CellView(CellViewBase, Cell):
r"""
A view presenting an underlying cell backend as a ``Cell``.
This view is a genuine ``Cell``, so it can be passed anywhere a Cell is accepted.
Its state is built lazily on first access from the backend.
:param obj: The cell-like object to present.
:param \**hints: Backend-selection hints.
"""
_backend: CellBackend
def __new__(cls, obj: CellLike, **hints: Any) -> Self:
if isinstance(obj, cls):
return obj
backend = cls._prepare_backend(obj, hints)
instance = super().__new__(cls)
instance._backend = backend
# Derived-value caches are checked by Cell's ordinary properties, so initialize them
# eagerly without materializing any backend presentation state.
instance._basis_cache = None
instance._metric_cache = None
instance._lengths_cache = None
instance._angles_cache = None
instance._volume_cache = None
return instance
def __init__(self, obj: CellLike, **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_unscaled_basis(self) -> None:
unscaled = to_surdvector(self._backend.unscaled_basis)
if unscaled.dim != (3, 3):
raise ValueError("Cell basis must be a 3x3 vector-like")
if unscaled.det().sign() == 0:
raise ValueError(
"Cell basis must be non-degenerate (its three vectors must span three "
"dimensions); a zero or linearly dependent row cannot be a cell, and for a "
"non-periodic direction it should be a unit vector rather than a zero one"
)
object.__setattr__(self, "_unscaled_basis", unscaled)
def _fill_scale(self) -> None:
scale = to_surdscalar(self._backend.scale)
if scale.sign() <= 0:
raise ValueError("Cell scale must be strictly positive")
object.__setattr__(self, "_scale", scale)
def _fill_precision(self) -> None:
object.__setattr__(self, "_precision", to_precision(self._backend.precision))
def _fill_periodicity(self) -> None:
object.__setattr__(self, "_periodicity", to_periodicity(self._backend.periodicity))
@cached_property
def _unscaled_basis(self) -> SurdVector: # type: ignore[override] # pyright: ignore[reportIncompatibleVariableOverride]
self._fill_unscaled_basis()
return self.__dict__["_unscaled_basis"]
@cached_property
def _scale(self) -> SurdScalar: # type: ignore[override] # pyright: ignore[reportIncompatibleVariableOverride]
self._fill_scale()
return self.__dict__["_scale"]
@cached_property
def _precision(self) -> fractions.Fraction | None: # type: ignore[override] # pyright: ignore[reportIncompatibleVariableOverride]
self._fill_precision()
return self.__dict__["_precision"]
@cached_property
def _periodicity(self) -> tuple[bool, bool, bool]: # type: ignore[override] # pyright: ignore[reportIncompatibleVariableOverride]
self._fill_periodicity()
return self.__dict__["_periodicity"]
[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[CellBackend]]:
"""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) -> Cell:
"""Return this presentation as a standalone cell.
:return: The exact cell representation.
"""
# The folded design makes a genuine Cell backend exactly the presented value: reuse it.
backend = self._backend
if type(backend) is Cell:
return backend
return Cell(self._unscaled_basis, self._scale, self._precision, self._periodicity)