Source code for httk.atomistic.models.bareprototype.view
"""Lazy prototype recognition and presentation view."""
from typing import Any, Self
from httk.core import MISSING, unwrap
from httk.atomistic.models.bareprototype.backend import BarePrototypeBackend
from httk.atomistic.models.bareprototype.bareprototype import BarePrototype
from httk.atomistic.models.bareprototype.recognized import RecognizedBarePrototype
from httk.atomistic.models.bareprototype.view_base import BarePrototypeViewBase
[docs]
class BarePrototypeView(BarePrototypeViewBase, BarePrototype):
r"""Recognize or project a lazy Wyckoff-only classification.
Existing bare values, refined classifications, plain labels, and structure-like
sources are accepted. Recognition hints are retained until first field access.
Projecting a refined source preserves it for view round-trips; ``unview()``
materializes a standalone bare value without geometrical refinement.
:param obj: Classification or structure-like source.
:param \*\*hints: Backend-selection and recognition hints.
"""
_backend: BarePrototypeBackend
_resolved_prototype: BarePrototype | None
_tolerance: float | None
_limit_denominator: int | None
_DEFERRED_FIELDS = frozenset({"_spacegroup", "_occupations"})
def __new__(
cls,
obj: Any = MISSING,
*,
tolerance: float | None = None,
limit_denominator: int | None = None,
**hints: Any,
) -> Self:
if obj is MISSING: # pickle/copy rebuild an empty instance; __setstate__ restores it
return super().__new__(cls)
if isinstance(obj, cls):
if any(value is not None for value in (tolerance, limit_denominator)) or hints:
raise ValueError("BarePrototypeView rewrapping does not accept recognition arguments")
return obj
backend_hints = dict(hints)
if tolerance is not None:
backend_hints["tolerance"] = tolerance
if limit_denominator is not None:
backend_hints["limit_denominator"] = limit_denominator
backend = cls._prepare_backend(obj, backend_hints)
if not isinstance(backend, RecognizedBarePrototype):
if not isinstance(backend, BarePrototypeBackend):
raise TypeError(f"Cannot recognize {type(backend).__name__} as a prototype source")
if any(value is not None for value in (tolerance, limit_denominator)) or hints:
raise ValueError("BarePrototypeView recognition arguments cannot be used with a prototype")
instance = super().__new__(cls)
instance._backend = backend
instance._resolved_prototype = None
instance._tolerance = tolerance
instance._limit_denominator = limit_denominator
return instance
def __init__(self, obj: Any, **hints: Any) -> None:
pass
def __getattribute__(self, name: str) -> Any:
if name in type(self)._DEFERRED_FIELDS:
namespace = object.__getattribute__(self, "__dict__")
if name not in namespace:
object.__getattribute__(self, "_effective_prototype")()
return object.__getattribute__(self, name)
def _effective_prototype(self) -> BarePrototype:
cached = object.__getattribute__(self, "_resolved_prototype")
if cached is not None:
return cached
backend = object.__getattribute__(self, "_backend")
if type(backend) is BarePrototype:
resolved = backend
elif isinstance(backend, RecognizedBarePrototype) or hasattr(backend, "resolve"):
resolved = backend.resolve()
else:
# A generic backend can carry class identity even though its base
# Wyckoff data are all this view needs for presentation.
resolved = BarePrototype(
backend.spacegroup,
backend.occupations,
)
state = dict(resolved.__dict__)
state["_resolved_prototype"] = resolved
object.__getattribute__(self, "__dict__").update(state)
return resolved
[docs]
def unwrap(self) -> Any:
"""Return the raw object behind the backend.
:return: The unwrapped source object.
"""
return unwrap(self._backend)
[docs]
def unview(self) -> BarePrototype:
"""Return the recognized prototype as a standalone value.
:return: The prototype value.
"""
return self._effective_prototype()
def __getstate__(self) -> dict[str, Any]:
state: dict[str, Any] = {
"backend": self._backend,
"tolerance": self._tolerance,
"limit_denominator": self._limit_denominator,
}
if self._resolved_prototype is not None:
state["resolved"] = self._resolved_prototype
return state
def __setstate__(self, state: dict[str, Any]) -> None:
self._backend = state["backend"]
self._tolerance = state["tolerance"]
self._limit_denominator = state["limit_denominator"]
self._resolved_prototype = None
resolved = state.get("resolved")
if resolved is not None:
state_copy = dict(resolved.__dict__)
state_copy["_resolved_prototype"] = resolved
object.__getattribute__(self, "__dict__").update(state_copy)