Source code for httk.atomistic.models.protostructure.view

"""Lazy protostructure recognition and presentation view."""

from typing import Any, Self

from httk.core import MISSING, unwrap

from httk.atomistic.models.protostructure.backend import ProtostructureBackend
from httk.atomistic.models.protostructure.protostructure import Protostructure
from httk.atomistic.models.protostructure.recognized import RecognizedProtostructure
from httk.atomistic.models.protostructure.view_base import ProtostructureViewBase
from httk.atomistic.models.structure.asu import FundamentalDomainStructure


def _has_existing_asu(source: Any) -> bool:
    if isinstance(source, FundamentalDomainStructure):
        return True
    from httk.atomistic.models.structure.asu_view import ASUStructureView

    if isinstance(source, ASUStructureView):
        return True
    return isinstance(getattr(source, "_view", None), ASUStructureView)


[docs] class ProtostructureView(ProtostructureViewBase, Protostructure): r"""Recognize a lazy standard-setting protostructure view. Recognition accepts optional ``setting``, ``standard``, ``transform``, ``tolerance``, and ``limit_denominator`` values through the recognition hints. :param obj: The structure-like or protostructure-like source. :param \*\*hints: Backend-selection and recognition hints. """ _backend: ProtostructureBackend _resolved_protostructure: Protostructure | None _setting: Any _standard: Any _transform: Any _tolerance: float | None _limit_denominator: int | None _DEFERRED_FIELDS = frozenset({"_spacegroup", "_occupations"}) def __new__( cls, obj: Any = MISSING, *, setting: Any = None, standard: Any = None, transform: Any = None, 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 (setting, standard, transform, tolerance, limit_denominator)) or hints ): raise ValueError("ProtostructureView rewrapping does not accept recognition arguments") return obj # Prototype-family inputs have dummy species; report the domain mismatch before backend probing. from httk.atomistic.models.prototype.backend import AnonymousStructureBackend from httk.atomistic.models.prototype.view_base import AnonymousStructureViewBase if isinstance(obj, (AnonymousStructureBackend, AnonymousStructureViewBase)): raise TypeError( "a prototype/anonymous structure carries dummy species; a protostructure needs the real ones" ) recognition_values = (setting, standard, transform, tolerance, limit_denominator) backend_hints = dict(hints) for name, value in zip( ("setting", "standard", "transform", "tolerance", "limit_denominator"), recognition_values ): if value is not None: backend_hints[name] = value backend = cls._prepare_backend(obj, backend_hints) if isinstance(backend, RecognizedProtostructure): structure = backend._structure if _has_existing_asu(structure) and any(value is not None for value in recognition_values): raise ValueError("ProtostructureView recognition arguments cannot be used with an existing ASU") else: if not isinstance(backend, ProtostructureBackend): raise TypeError(f"Cannot recognize {type(backend).__name__} as a protostructure source") if any(value is not None for value in recognition_values) or hints: raise ValueError("ProtostructureView recognition arguments cannot be used with a protostructure") instance = super().__new__(cls) instance._backend = backend instance._resolved_protostructure = None instance._setting = setting instance._standard = standard instance._transform = transform instance._tolerance = tolerance instance._limit_denominator = limit_denominator return instance instance = super().__new__(cls) instance._backend = backend instance._resolved_protostructure = None instance._setting = setting instance._standard = standard instance._transform = transform 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_protostructure")() return object.__getattribute__(self, name) def _effective_protostructure(self) -> Protostructure: cached = object.__getattribute__(self, "_resolved_protostructure") if cached is not None: return cached backend = object.__getattribute__(self, "_backend") if type(backend) is Protostructure: resolved = backend elif isinstance(backend, RecognizedProtostructure): resolved = backend.resolve() else: resolved = Protostructure(backend.spacegroup, backend.occupations) state = dict(resolved.__dict__) state["_resolved_protostructure"] = 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) -> Protostructure: """Return the recognized protostructure as a standalone value. :return: The protostructure value. """ return self._effective_protostructure()
def __getstate__(self) -> dict[str, Any]: state = { "backend": self._backend, "setting": self._setting, "standard": self._standard, "transform": self._transform, "tolerance": self._tolerance, "limit_denominator": self._limit_denominator, } if self._resolved_protostructure is not None: state["resolved"] = self._resolved_protostructure return state def __setstate__(self, state: dict[str, Any]) -> None: self._backend = state["backend"] self._setting = state["setting"] self._standard = state["standard"] self._transform = state["transform"] self._tolerance = state["tolerance"] self._limit_denominator = state["limit_denominator"] self._resolved_protostructure = None resolved = state.get("resolved") if resolved is not None: state_copy = dict(resolved.__dict__) state_copy["_resolved_protostructure"] = resolved object.__getattribute__(self, "__dict__").update(state_copy)