Source code for httk.atomistic.models.protopattern.protopattern
"""The immutable geometry-free, element-free protopattern value."""
from collections.abc import Sequence
from typing import ClassVar
from httk.atomistic.models.formula.notation import anonymous_symbol
from httk.atomistic.models.protopattern.backend import ProtopatternBackend
from httk.atomistic.models.protopattern.notation import canonical_label_map
from httk.atomistic.models.protopattern.occupation import ProtopatternOccupation
from httk.atomistic.symmetry.spacegroup import Spacegroup
[docs]
class Protopattern(ProtopatternBackend):
"""Store a standard-setting space group and its class-partitioned Wyckoff letters.
A protopattern is space group plus occupied Wyckoff letters plus a partition of those
occupations into anonymous species classes; it has no chemical elements and no
continuous degrees of freedom. Construction re-canonicalizes the class assignment by
the pinned group-ordering rule, so any permutation of the input class labels builds an
equal value that renders the identical label.
``Protopattern`` is the anonymous-species, Wyckoff-positions-only cell of the
material-information matrix:
====================== ============== ==============
Geometrical info Anonymous Assigned
====================== ============== ==============
Wyckoff positions only Protopattern Protostructure
Geometrical class Prototype Structuretype
Exact geometry CrystalPattern Structure
====================== ============== ==============
:param spacegroup: The standard-setting space group or its IT number.
:param occupations: The occupied Wyckoff positions and their anonymous class labels.
"""
_spacegroup: Spacegroup
_occupations: tuple[ProtopatternOccupation, ...]
[docs]
kind: ClassVar[str] = "protopattern"
def __init__(
self,
spacegroup: Spacegroup | int,
occupations: Sequence[ProtopatternOccupation | tuple[str, str]],
) -> None:
self._spacegroup = spacegroup if isinstance(spacegroup, Spacegroup) else Spacegroup.standard(spacegroup)
if not self._spacegroup.is_standard_setting:
raise ValueError(
f"Protopattern records Wyckoff data in the IT standard setting, but was given "
f"{self._spacegroup.setting}; pass Spacegroup.standard({self._spacegroup.it_number}) "
"instead"
)
raw = tuple(
value if isinstance(value, ProtopatternOccupation) else ProtopatternOccupation(value[0], value[1])
for value in occupations
)
if not raw:
raise ValueError("Protopattern occupations must be non-empty")
letters_by_label: dict[str, list[str]] = {}
for occupation in raw:
try:
self._spacegroup.wyckoff_position(occupation.wyckoff)
except KeyError as exc:
raise ValueError(str(exc)) from exc
letters_by_label.setdefault(occupation.label, []).append(occupation.wyckoff)
expected = {anonymous_symbol(index) for index in range(len(letters_by_label))}
if set(letters_by_label) != expected:
raise ValueError("Protopattern class labels must be consecutive anonymous symbols from 'A'")
relabel = canonical_label_map({label: tuple(sorted(letters)) for label, letters in letters_by_label.items()})
self._occupations = tuple(
sorted(
(ProtopatternOccupation(occupation.wyckoff, relabel[occupation.label]) for occupation in raw),
key=lambda value: (value.label, value.wyckoff),
)
)
@property
[docs]
def spacegroup(self) -> Spacegroup:
"""Return the standard-setting space group."""
return self._spacegroup
@property
[docs]
def occupations(self) -> tuple[ProtopatternOccupation, ...]:
"""Return the canonical class-partitioned occupied Wyckoff positions."""
return self._occupations
def __eq__(self, other: object) -> bool:
if not isinstance(other, Protopattern):
return NotImplemented
return (self._spacegroup, self._occupations) == (other._spacegroup, other._occupations)
def __hash__(self) -> int:
return hash((self._spacegroup, self._occupations))
def __repr__(self) -> str:
pairs = ", ".join(f"{occupation.wyckoff}:{occupation.label}" for occupation in self._occupations)
return f"Protopattern({self._spacegroup.setting!r}, {pairs})"