Source code for httk.atomistic.models.trajectory.record
"""Expose bounded trajectory storage records as backends."""
from collections.abc import Iterator
from functools import cached_property
from typing import Any, Self, cast
from httk.atomistic.models.species.species import Species
from httk.atomistic.models.species.view import SpeciesView
from httk.atomistic.models.structure.unitcell import UnitcellStructure
from httk.atomistic.models.structure.unitcell_view import UnitcellStructureView
from httk.atomistic.models.trajectory.backend import TrajectoryBackend
from httk.atomistic.storage.records import (
ObservableSummaryRecord,
TrajectoryRecord,
)
[docs]
class RecordTrajectory(TrajectoryBackend):
r"""Expose only the bounded summary and stored reference frames.
Full frame data is deliberately absent from
:class:`httk.atomistic.storage.records.TrajectoryRecord`.
Callers must use ``source_locator`` to reopen the original source for any
frame that is not one of the stored reference frames.
:param obj: The bounded trajectory storage record.
:param \**hints: Backend-selection hints.
"""
_record: TrajectoryRecord
@classmethod
def _backend_adopt(cls, obj: Any, **hints: Any) -> Self | None:
r"""Adopt a trajectory record.
:param obj: The source object to adopt.
:param \**hints: Backend-selection hints.
:return: An initialized backend, or ``None`` when this backend declines ``obj``.
"""
if hints and hints.get("kind", "record") != "record":
return None
if not isinstance(obj, TrajectoryRecord):
return None
return cls(obj, **hints)
def __init__(self, obj: TrajectoryRecord, **hints: Any) -> None:
self._record = obj
@property
[docs]
def nframes(self) -> int:
"""Return the total number of source frames."""
return self._record.nframes
@cached_property
[docs]
def species(self) -> tuple[Species, ...]: # pyright: ignore[reportIncompatibleMethodOverride]
"""Return the stored distinct species."""
return tuple(SpeciesView(cast(Any, value), kind="record") for value in self._record.species)
@property
[docs]
def species_at_sites(self) -> tuple[str, ...]:
"""Return the stored species name at each site."""
return self._record.species_at_sites
@property
[docs]
def reference_frames(self) -> tuple[int, ...]:
"""Return the indexes of frames stored in the bounded record."""
return self._record.reference_frame_indexes
@property
[docs]
def observable_names(self) -> tuple[str, ...]:
"""Return the names of summarized observables."""
return tuple(value.name for value in self._record.observable_summaries)
@property
[docs]
def observable_summaries(self) -> tuple[ObservableSummaryRecord, ...]:
"""Return the stored per-observable summaries."""
return self._record.observable_summaries
def _stored_index(self, i: int) -> int:
if not isinstance(i, int):
raise TypeError("Trajectory frame index must be an integer")
normalized = i + self.nframes if i < 0 else i
if not 0 <= normalized < self.nframes:
raise IndexError(
f"Trajectory frame index {i} out of range; frame data is not stored, "
f"use source_locator={self._record.source_locator!r} to recover it"
)
if normalized not in self.reference_frames:
raise IndexError(
f"Trajectory frame {normalized} is not stored in the bounded record; "
f"use source_locator={self._record.source_locator!r} to recover it"
)
return self.reference_frames.index(normalized)
[docs]
def frame(self, i: int) -> UnitcellStructure:
"""Return a stored reference frame by source index.
:param i: Source frame index.
:return: The stored reference frame.
:raises IndexError: If the frame is not stored in the bounded record.
:raises TypeError: If the index is not an integer.
"""
return UnitcellStructureView(self._record.reference_frame_structures[self._stored_index(i)])
[docs]
def frames(self) -> Iterator[UnitcellStructure]:
"""Reject iteration because full frames are not stored.
:return: Never; reopen the source at ``source_locator`` instead.
:raises RuntimeError: Always, because the record stores no full frame sequence.
"""
raise RuntimeError(
"Trajectory frame data is not stored in the record; use "
f"source_locator={self._record.source_locator!r} to recover the full trajectory"
)
[docs]
def observable(self, name: str) -> tuple[Any, ...]:
"""Reject per-frame access to summarized observable values.
:param name: Observable name.
:return: Never; reopen the source at ``source_locator`` instead.
:raises KeyError: If the observable is unavailable.
:raises RuntimeError: If the observable is summarized but not stored per frame.
"""
if name not in self.observable_names:
raise KeyError(name)
raise RuntimeError(
"Trajectory observable values are summarized but not stored per frame; use "
f"source_locator={self._record.source_locator!r} to recover them"
)
[docs]
def unwrap(self) -> TrajectoryRecord:
"""Return the bounded storage record."""
return self._record