Source code for httk.atomistic.models.trajectory.api
"""Define the canonical trajectory interface for httk-atomistic."""
from abc import ABC, abstractmethod
from collections import deque
from collections.abc import Iterator
from typing import Any
from httk.atomistic.models.species.species import Species
from httk.atomistic.models.structure.unitcell import UnitcellStructure
[docs]
class TrajectoryAPI(ABC):
"""Define the common, frame-oriented trajectory interface.
A trajectory has one constant composition for all frames. Trajectories whose
number or identity of sites changes are intentionally outside this interface.
"""
@property
[docs]
def nframes(self) -> int:
"""Count frames by streaming them in O(n) time.
Each call requires ``frames()`` to provide a fresh traversal; a one-shot backend must
override this default or cache its frames. Random-access backends override this default.
:return: The number of frames.
"""
return sum(1 for _ in self.frames())
[docs]
def frame(self, i: int) -> UnitcellStructure:
"""Stream to and return one frame in O(n) time.
Each call requires ``frames()`` to provide a fresh traversal; a one-shot backend must
override this default or cache its frames. Random-access backends override this default.
:param i: Frame index.
:return: The requested :class:`~httk.atomistic.models.structure.unitcell.UnitcellStructure`.
:raises IndexError: If ``i`` is before the first frame or past the end.
:raises TypeError: If ``i`` is not an integer.
"""
if not isinstance(i, int) or isinstance(i, bool):
raise TypeError("Trajectory frame index must be an integer")
if i < 0:
tail = deque(self.frames(), maxlen=-i)
if len(tail) == -i:
return tail[0]
else:
for index, frame in enumerate(self.frames()):
if index == i:
return frame
raise IndexError(f"Trajectory frame index {i} out of range")
@abstractmethod
[docs]
def frames(self) -> Iterator[UnitcellStructure]:
"""Iterate over the frames with a fresh traversal on every call.
A one-shot backend must override the streaming defaults or cache its frames.
:return: A fresh iterator of unit-cell structures.
"""
raise NotImplementedError
@property
@abstractmethod
[docs]
def species(self) -> tuple[Species, ...]:
"""Return the constant distinct species."""
raise NotImplementedError
@property
@abstractmethod
[docs]
def species_at_sites(self) -> tuple[str, ...]:
"""Return the constant species name at each site."""
raise NotImplementedError
@property
[docs]
def reference_frames(self) -> tuple[int, ...] | None:
"""Return stored reference-frame indexes, or ``None``."""
return None
@property
[docs]
def observable_names(self) -> tuple[str, ...]:
"""Return the names of available per-frame observables."""
return ()
[docs]
def observable(self, name: str) -> tuple[Any, ...]:
"""Return one observable's values by frame.
:param name: Observable name.
:return: The observable values in frame order.
:raises KeyError: If the observable is unavailable.
"""
if name not in self.observable_names:
raise KeyError(name)
raise KeyError(name)