Source code for httk.atomistic.structure
"""
The Simple structure representation for httk-atomistic.
"""
from collections.abc import Sequence
from typing import TYPE_CHECKING
from httk.core import SurdVector
from .cell import Cell
from .cell_class_view import CellClassView
from .cell_like import CellLike
from .sites import Sites
from .sites_class_view import SitesClassView
from .sites_like import SitesLike
from .species import Species
from .species_class_view import SpeciesClassView
from .species_like import SpeciesLike
if TYPE_CHECKING:
from .numeric_structure import NumericStructure
[docs]
class Structure:
"""
A crystal structure in the Simple representation.
A Structure holds a ``cell`` (a ``Cell`` of 3x3 cell vectors), ``sites`` (a ``Sites``
of Nx3 reduced coordinates), a list of ``species`` (each a ``Species``), and a
length-N ``species_at_sites`` giving the species name occupying each site. Inputs are
normalized on construction through the component families: the cell, sites, and each
species are passed through their ``*Like`` unions, and every ``species_at_sites`` name
must match one of the (uniquely named) species.
The numeric model is exact and split by purpose. The fractional frame — reduced coordinates
and symmetry — is rational and lives in ``sites`` as a :class:`~httk.core.FracVector`. The
Cartesian frame — where radicals such as the hexagonal ``sqrt(3)`` appear — is exact in the
squarefree-radical field: ``cell.basis`` is a :class:`~httk.core.SurdVector` and
:meth:`cartesian_sites` returns the exact Cartesian positions. Pure magnitudes (bond-length
comparisons) stay rational-exact via ``cell.metric()``. Floats appear only at the presentation
and JSON boundaries.
"""
_cell: Cell
_sites: Sites
_species: tuple[Species, ...]
_species_at_sites: tuple[str, ...]
def __init__(
self,
cell: CellLike,
sites: SitesLike,
species: Sequence[SpeciesLike],
species_at_sites: Sequence[str],
) -> None:
norm_cell = cell if isinstance(cell, Cell) else CellClassView(cell)
norm_sites = sites if isinstance(sites, Sites) else SitesClassView(sites)
norm_species = tuple(s if isinstance(s, Species) else SpeciesClassView(s) for s in species)
norm_species_at_sites = tuple(str(name) for name in species_at_sites)
if len(norm_species_at_sites) != len(norm_sites):
raise ValueError("Structure species_at_sites must have the same length as sites")
names = [s.name for s in norm_species]
if len(names) != len(set(names)):
raise ValueError("Structure species names must be unique")
known = set(names)
for name in norm_species_at_sites:
if name not in known:
raise ValueError(f"Structure species_at_sites references unknown species name: {name!r}")
self._cell = norm_cell
self._sites = norm_sites
self._species = norm_species
self._species_at_sites = norm_species_at_sites
@property
[docs]
def cell(self) -> Cell:
"""The cell (3x3 cell vectors) as a ``Cell``."""
return self._cell
@property
[docs]
def sites(self) -> Sites:
"""The site coordinates (Nx3 reduced coordinates) as a ``Sites``."""
return self._sites
@property
[docs]
def species(self) -> tuple[Species, ...]:
"""The distinct species of this structure."""
return self._species
@property
[docs]
def species_at_sites(self) -> tuple[str, ...]:
"""The species name occupying each site, in site order."""
return self._species_at_sites
[docs]
def cartesian_sites(self) -> SurdVector:
"""
The exact Cartesian site positions as an ``(N, 3)`` :class:`~httk.core.SurdVector`.
Under the row-vector convention this is ``reduced_coords * cell.basis`` (each Cartesian
position is the sum over lattice vectors ``sum_k reduced[k] * basis[k]``). The reduced
coordinates are rational (a ``FracVector``), the cell basis carries the radicals (a
``SurdVector``), so the product is exact in the surd field — the hexagonal ``sqrt(3)``
survives into the Cartesian positions.
"""
return SurdVector.create(self._sites.reduced_coords) * self._cell.basis
[docs]
def numeric(self) -> "NumericStructure":
"""A plain-numpy presentation of this structure (requires the ``httk-atomistic[numpy]`` extra)."""
from .numeric_structure import NumericStructure
return NumericStructure(self)
def __eq__(self, other: object) -> bool:
if not isinstance(other, Structure):
return NotImplemented
return (
self._cell == other._cell
and self._sites == other._sites
and self._species == other._species
and self._species_at_sites == other._species_at_sites
)
def __repr__(self) -> str:
return (
f"Structure(cell={self._cell!r}, sites={self._sites!r}, "
f"species={self._species!r}, species_at_sites={self._species_at_sites!r})"
)