Source code for httk.atomistic.models.prototype.api

"""The anonymous geometrical-class prototype interface."""

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Self, cast

from httk.atomistic.models.formula.formulatype_view import FormulatypeView
from httk.atomistic.models.prototype.notation import pearson_symbol, render_prototype_label

if TYPE_CHECKING:
    from httk.atomistic.models.prototype.backend import PrototypeBackend
    from httk.atomistic.models.prototype.label import PrototypeLabel
    from httk.atomistic.models.prototype.like import PrototypeLike
    from httk.atomistic.models.prototype.occupation import PrototypeOccupation
    from httk.atomistic.models.structuretype.fundamental import FundamentalDomainTemplate
    from httk.atomistic.symmetry.comparison_cache import StructureComparisonCache
    from httk.atomistic.symmetry.spacegroup import Spacegroup


[docs] class PrototypeAPI(ABC): """Common interface for anonymous standard-setting Wyckoff prototypes.""" @property @abstractmethod def spacegroup(self) -> "Spacegroup": raise NotImplementedError @property @abstractmethod def occupations(self) -> tuple["PrototypeOccupation", ...]: raise NotImplementedError @property def representative(self) -> "FundamentalDomainTemplate | None": """Return an optional retained exact representative.""" return None @property def discriminator(self) -> str | None: """Return an optional geometrical-class discriminator.""" return None
[docs] def multiplicities(self) -> tuple[int, ...]: return tuple(self.spacegroup.wyckoff_position(value.wyckoff).multiplicity for value in self.occupations)
@property def nsites_conventional(self) -> int: return sum(self.multiplicities()) @property def pearson_symbol(self) -> str: return pearson_symbol(self.spacegroup, self.nsites_conventional) @property def anonymous_formula(self) -> FormulatypeView: return FormulatypeView(cast("PrototypeBackend", self)) @property def label(self) -> "PrototypeLabel": from httk.atomistic.models.prototype.label import PrototypeLabel return PrototypeLabel(cast("PrototypeBackend", self)) @property def prototype(self) -> Self: return self def _prototype_label_text(self) -> str: return render_prototype_label(self.spacegroup, [(value.wyckoff, value.label) for value in self.occupations])
[docs] def similar( self, other: "PrototypeLike", delta: float, *, use_numpy: bool = False, cache: "StructureComparisonCache | None" = None, ) -> bool: """Return whether two prototypes have compatible geometry within ``delta``. The base identity (space group, anonymous occupations, and any discriminators present on both) must agree; when both sides retain a geometrical representative, their total Cartesian atom travel must not exceed ``delta``. A prototype-like or protostructure-like ``other`` is erased through :class:`~httk.atomistic.PrototypeView` first, so a bare protostructure compares against its anonymous prototype. :param other: The prototype-like or protostructure-like value to compare against. :param delta: The non-negative finite Cartesian travel budget. :param use_numpy: Use temporary NumPy float64 geometry for approximate comparison; requires the ``numpy`` extra and may change ties or near-threshold decisions. Retained representatives and their identities remain exact. :param cache: Optional caller-scoped cache for reusable comparison preparation. :return: Whether the two values are compatible within ``delta``. :raises TypeError: If ``delta`` is not a real number. :raises ValueError: If ``delta`` is negative or non-finite. """ import math from numbers import Real if not isinstance(delta, Real) or isinstance(delta, bool): raise TypeError("delta must be a finite non-negative real") if not math.isfinite(delta) or delta < 0: raise ValueError("delta must be a finite non-negative real") from httk.atomistic.models.prototype.backend import PrototypeBackend from httk.atomistic.models.prototype.prototype import Prototype from httk.atomistic.models.prototype.view import PrototypeView from httk.atomistic.models.prototype.view_base import PrototypeViewBase resolved: PrototypeBackend if isinstance(other, PrototypeViewBase): resolved = other.unview() elif isinstance(other, PrototypeBackend): resolved = other else: try: resolved = PrototypeView(other).unview() except (TypeError, ValueError): return False left: PrototypeBackend = self if isinstance(self, Prototype) else PrototypeView(self).unview() if left.spacegroup != resolved.spacegroup or left.occupations != resolved.occupations: return False if ( left.discriminator is not None and resolved.discriminator is not None and left.discriminator != resolved.discriminator ): return False if left.representative is None or resolved.representative is None: return True from httk.atomistic.models.prototype.derived import _prototype_to_structure from httk.atomistic.symmetry.paths import NoCommonRepresentation, _structure_within_delta, structure_delta if cache is None: first = _prototype_to_structure(left.representative) second = _prototype_to_structure(resolved.representative) else: first = cache._structure( left.representative, lambda: _prototype_to_structure(left.representative), kind="anonymous", ) second = cache._structure( resolved.representative, lambda: _prototype_to_structure(resolved.representative), kind="anonymous", ) try: if use_numpy and cache is not None: return _structure_within_delta(first, second, delta, use_numpy=True, cache=cache) if use_numpy: return _structure_within_delta(first, second, delta, use_numpy=True) if cache is not None: return structure_delta(first, second, cache=cache) <= delta return structure_delta(first, second) <= delta except NoCommonRepresentation: return False