Source code for httk.atomistic.models.prototype.derived
"""Lazy adapters from assigned structures to anonymous prototypes."""
from collections.abc import Sequence
from functools import cached_property
from typing import Any, Self
from httk.core import unwrap
from httk.atomistic.models.protostructure.backend import ProtostructureBackend
from httk.atomistic.models.protostructure.occupation import WyckoffOccupation
from httk.atomistic.models.protostructure.view_base import ProtostructureViewBase
from httk.atomistic.models.prototype.backend import PrototypeBackend
from httk.atomistic.models.prototype.notation import canonical_label_map
from httk.atomistic.models.prototype.occupation import PrototypeOccupation
from httk.atomistic.models.prototype.prototype import Prototype
from httk.atomistic.models.species.species import Species
from httk.atomistic.models.structure.asu import ASUStructure, FundamentalDomainStructure, WyckoffSite
from httk.atomistic.models.structuretype.fundamental import FundamentalDomainTemplate
def _prototype_to_structure(prototype):
"""Return the exact dummy-species structure represented by a prototype representative."""
structure = prototype._domain
if isinstance(structure, ASUStructure):
return structure
return ASUStructure(
structure.cell,
structure.spacegroup,
structure.wyckoff_sites,
structure.species,
transform=structure.transform,
coordinate_precision=structure.coordinate_precision,
)
def _anonymous_template_from_structure(structure: FundamentalDomainStructure) -> FundamentalDomainTemplate:
by_name: dict[str, list[str]] = {}
for site in structure.wyckoff_sites:
by_name.setdefault(site.species, []).append(site.wyckoff)
labels = canonical_label_map({name: tuple(sorted(letters)) for name, letters in by_name.items()})
species = tuple(Species(label, ("X",), (1,), labels=(label,)) for label in labels.values())
sites = tuple(
WyckoffSite(site.wyckoff, site.free_params, labels[site.species], representative=site.representative)
for site in structure.wyckoff_sites
)
return FundamentalDomainTemplate(
structure.cell, structure.spacegroup, sites, species, structure.coordinate_precision
)
def _anonymous_occupations(occupations: Sequence[WyckoffOccupation]) -> list[PrototypeOccupation]:
by_name: dict[str, list[str]] = {}
for occupation in occupations:
by_name.setdefault(occupation.species.name, []).append(occupation.wyckoff)
relabel = canonical_label_map({name: tuple(sorted(letters)) for name, letters in by_name.items()})
return [PrototypeOccupation(o.wyckoff, relabel[o.species.name]) for o in occupations]
[docs]
class DerivedPrototype(PrototypeBackend):
"""Erase assigned Protostructure occupations to anonymous labels lazily.
The source discriminator is preserved verbatim. An exact retained representative
is converted to an anonymous fundamental-domain template without approximating
its geometry.
"""
kind = "prototype"
@classmethod
def _backend_adopt(cls, obj: Any, **hints: Any) -> Self | None:
if hints and hints.get("kind", "prototype") != "prototype":
return None
if isinstance(obj, (ProtostructureBackend, ProtostructureViewBase)):
return cls(obj, **hints)
return None
def __init__(self, obj: Any, **hints: Any) -> None:
if not isinstance(obj, (ProtostructureBackend, ProtostructureViewBase)):
raise TypeError("DerivedPrototype requires a Protostructure source")
self._source = obj
@cached_property
def _derived(self) -> Prototype:
source = self._source
value = source._backend if isinstance(source, ProtostructureViewBase) else source
occupations = _anonymous_occupations(value.occupations)
representative = value.representative
anon_rep = None if representative is None else _anonymous_template_from_structure(representative)
return Prototype(value.spacegroup, occupations, representative=anon_rep, discriminator=value.discriminator)
[docs]
def resolve(self) -> Prototype:
return self._derived
@property
def spacegroup(self):
return self._derived.spacegroup
@property
def occupations(self):
return self._derived.occupations
@property
def representative(self):
return self._derived.representative
@property
def discriminator(self):
return self._derived.discriminator
[docs]
def unwrap(self) -> Any:
return unwrap(self._source)