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

"""The immutable assigned-species geometrical-classification value."""

from collections.abc import Sequence
from typing import Any, ClassVar

from httk.atomistic.models.protostructure.backend import ProtostructureBackend
from httk.atomistic.models.protostructure.occupation import WyckoffOccupation
from httk.atomistic.models.structure.asu import FundamentalDomainStructure
from httk.atomistic.symmetry.spacegroup import Spacegroup


[docs] class Protostructure(ProtostructureBackend): """Store occupied Wyckoff positions with explicit geometrical-class information. At least one of an exact representative or a nonempty discriminator is required. Both participate in equality and content identity. Use ``BareProtostructure`` for the broader Wyckoff-only classification; recognizing a structure yields that level. :param spacegroup: The standard-setting space group or its IT number. :param occupations: The occupied Wyckoff positions and their species. :param representative: An optional exact standard-setting class anchor. :param discriminator: An optional external class discriminator. """ _spacegroup: Spacegroup _occupations: tuple[WyckoffOccupation, ...] kind: ClassVar[str] = "protostructure" def __init__( self, spacegroup: Spacegroup | int | None = None, occupations: Sequence[WyckoffOccupation | tuple[str, Any]] | None = None, *, representative: FundamentalDomainStructure | None = None, discriminator: str | None = None, ) -> None: base_supplied = spacegroup is not None or occupations is not None if representative is not None: _validate_representative(representative) if not base_supplied: spacegroup = representative.spacegroup representative_species = {species.name: species for species in representative.species} occupations = tuple( (site.wyckoff, representative_species[site.species]) for site in representative.wyckoff_sites ) if spacegroup is None or occupations is None: raise ValueError("Protostructure needs spacegroup and occupations or a representative") if discriminator is not None and (not isinstance(discriminator, str) or not discriminator): raise ValueError("Protostructure discriminator must be a non-empty string when given") from httk.atomistic.models.bareprotostructure.bareprotostructure import BareProtostructure bare = BareProtostructure(spacegroup, occupations) self._spacegroup, self._occupations = bare.spacegroup, bare.occupations if representative is not None and base_supplied: expected = Protostructure(representative=representative) if (self._spacegroup, self._occupations) != (expected.spacegroup, expected.occupations): raise ValueError("Protostructure base disagrees with its representative") if representative is None and discriminator is None: raise ValueError( "Protostructure requires a representative or discriminator; use BareProtostructure for Wyckoff-only values" ) self._representative = representative self._discriminator = discriminator @property def spacegroup(self) -> Spacegroup: """Return the standard-setting space group.""" return self._spacegroup @property def occupations(self) -> tuple[WyckoffOccupation, ...]: """Return the canonical occupied Wyckoff positions.""" return self._occupations @property def representative(self) -> FundamentalDomainStructure | None: return self._representative @property def discriminator(self) -> str | None: return self._discriminator def __eq__(self, other: object) -> bool: if not isinstance(other, Protostructure): return NotImplemented return (self._spacegroup, self._occupations, self._representative, self._discriminator) == ( other._spacegroup, other._occupations, other._representative, other._discriminator, ) def __hash__(self) -> int: return hash((self._spacegroup, self._occupations, self._discriminator)) def __repr__(self) -> str: pairs = ", ".join(f"{occupation.wyckoff}:{occupation.species.name}" for occupation in self._occupations) parts = [f"{self._spacegroup.setting!r}, {pairs}"] if self._representative is not None: parts.append("representative=...") if self._discriminator is not None: parts.append(f"discriminator={self._discriminator!r}") return f"Protostructure({', '.join(parts)})"
def _validate_representative(representative: FundamentalDomainStructure) -> None: if not isinstance(representative, FundamentalDomainStructure): raise TypeError("Protostructure representative must be a FundamentalDomainStructure") if not representative.spacegroup.is_standard_setting: raise ValueError("Protostructure representative must record Wyckoff data in the IT standard setting") if not representative.transform.is_identity(): raise ValueError("Protostructure representative must use an identity setting transform") if representative.assemblies is not None: raise ValueError("Protostructure representative cannot carry assemblies") if representative.molecular: raise ValueError("Protostructure representative cannot be molecular") if any(site.moment is not None for site in representative.wyckoff_sites): raise ValueError("Protostructure representative cannot carry site moments")