Source code for httk.atomistic.structure_primitive

"""
Backend wrapping an spglib-like (lattice, positions, numbers) triple.
"""

from typing import Any

from ._vector_guards import is_basis_3x3, is_coords_nx3, try_surdvector
from .cell import Cell
from .elements import symbol_of
from .sites import Sites
from .species import Species
from .structure_backend import StructureBackend


def _is_number(value: Any) -> bool:
    return isinstance(value, (int, float)) and not isinstance(value, bool)


def _is_primitive_triple(obj: Any) -> bool:
    if not isinstance(obj, (list, tuple)) or len(obj) != 3:
        return False
    lattice, positions, numbers = obj
    if not is_basis_3x3(lattice) or not is_coords_nx3(positions):
        return False
    if not isinstance(numbers, (list, tuple)) or not all(_is_number(z) for z in numbers):
        return False
    positions_vector = try_surdvector(positions)
    nsites = positions_vector.dim[0] if positions_vector is not None and len(positions_vector.dim) == 2 else 0
    return len(numbers) == nsites


[docs] class StructurePrimitive(StructureBackend): """ Backend for a crystal structure backed by an spglib-like triple. The native representation is a length-3 ``(lattice, positions, numbers)`` list or tuple, where ``lattice`` is 3x3, ``positions`` is Nx3 reduced coordinates, and ``numbers`` is the length-N sequence of atomic numbers. The quartet is derived lazily and cached: ``cell`` is a ``Cell``, ``sites`` a ``Sites``, ``species`` one single-element ``Species`` per distinct atomic number, and ``unwrap`` returns the original triple. """ _raw: Any _lattice: Any _positions: Any _numbers: tuple[int, ...] _cell_cache: Cell | None _sites_cache: Sites | None _species_cache: tuple[Species, ...] | None _species_at_sites_cache: tuple[str, ...] | None # Cannot type annotate __new__ as `Self | None` for some reason def __new__(cls, obj: Any, **hints: Any) -> Any: if hints and hints.get("kind", "primitive") != "primitive": return None if not _is_primitive_triple(obj): return None return super().__new__(cls) def __init__(self, obj: Any, **hints: Any) -> None: lattice, positions, numbers = obj self._raw = obj self._lattice = lattice self._positions = positions self._numbers = tuple(int(z) for z in numbers) self._cell_cache = None self._sites_cache = None self._species_cache = None self._species_at_sites_cache = None @property
[docs] def cell(self) -> Cell: if self._cell_cache is None: self._cell_cache = Cell(self._lattice) return self._cell_cache
@property
[docs] def sites(self) -> Sites: if self._sites_cache is None: self._sites_cache = Sites(self._positions) return self._sites_cache
@property
[docs] def species(self) -> tuple[Species, ...]: if self._species_cache is None: distinct: list[int] = [] seen: set[int] = set() for z in self._numbers: if z not in seen: seen.add(z) distinct.append(z) self._species_cache = tuple( Species(name=symbol_of(z), chemical_symbols=(symbol_of(z),), concentration=(1.0,)) for z in distinct ) return self._species_cache
@property
[docs] def species_at_sites(self) -> tuple[str, ...]: if self._species_at_sites_cache is None: self._species_at_sites_cache = tuple(symbol_of(z) for z in self._numbers) return self._species_at_sites_cache
[docs] def unwrap(self) -> Any: return self._raw