# SPDX-FileCopyrightText: 2023-present Rohit Goswami <rog32@hi.is>
#
# SPDX-License-Identifier: MIT
"""Shared typed result objects for parser outputs.
These records preserve mapping-style access for compatibility with existing
callers while giving parser APIs explicit, named return types.
"""
from __future__ import annotations
from collections.abc import Iterator, Mapping
from dataclasses import dataclass, field, fields
from typing import Any
import numpy as np
[docs]
@dataclass(frozen=True, slots=True)
class ParserAttrs(Mapping[str, Any]):
"""Named mapping for metadata-style parser records."""
data: dict[str, Any] = field(default_factory=dict)
[docs]
def __getitem__(self, key: str) -> Any:
return self.data[key]
[docs]
def __iter__(self) -> Iterator[str]:
return iter(self.data)
[docs]
def __len__(self) -> int:
return len(self.data)
[docs]
@dataclass(frozen=True, slots=True)
class ArrayGroup(Mapping[str, np.ndarray]):
"""Named mapping of arrays loaded from parser backends."""
data: dict[str, np.ndarray] = field(default_factory=dict)
[docs]
def __getitem__(self, key: str) -> np.ndarray:
return self.data[key]
[docs]
def __iter__(self) -> Iterator[str]:
return iter(self.data)
[docs]
def __len__(self) -> int:
return len(self.data)
[docs]
class DataclassMapping(Mapping[str, Any]):
"""Mixin exposing dataclass fields through the mapping protocol."""
[docs]
def __getitem__(self, key: str) -> Any:
for field_info in fields(self):
if field_info.name == key:
return getattr(self, key)
raise KeyError(key)
[docs]
def __iter__(self) -> Iterator[str]:
return (field_info.name for field_info in fields(self))
[docs]
def __len__(self) -> int:
return len(fields(self))
[docs]
@dataclass(frozen=True, slots=True)
class OrcaNebResult(DataclassMapping):
"""Structured ORCA NEB result with mapping-style compatibility."""
energies: np.ndarray
rmsd_r: np.ndarray | None = None
rmsd_p: np.ndarray | None = None
grad_r: np.ndarray | None = None
grad_p: np.ndarray | None = None
forces: list[np.ndarray | None] | None = None
converged: bool = False
n_images: int | None = None
barrier_forward: float | None = None
barrier_reverse: float | None = None
source: str = "unknown"
orca_version: str = "unknown"
[docs]
def __post_init__(self) -> None:
if self.n_images is None:
object.__setattr__(self, "n_images", len(self.energies))
[docs]
@classmethod
def from_mapping(cls, data: Mapping[str, Any]) -> OrcaNebResult:
"""Coerce a mapping-like ORCA payload into a typed result."""
def _maybe_array(key: str) -> np.ndarray | None:
values = data.get(key)
if values is None:
return None
return np.asarray(values)
forces = data.get("forces")
return cls(
energies=np.asarray(data.get("energies", [])),
rmsd_r=_maybe_array("rmsd_r"),
rmsd_p=_maybe_array("rmsd_p"),
grad_r=_maybe_array("grad_r"),
grad_p=_maybe_array("grad_p"),
forces=list(forces) if forces is not None else None,
converged=bool(data.get("converged", False)),
n_images=data.get("n_images"),
barrier_forward=data.get("barrier_forward"),
barrier_reverse=data.get("barrier_reverse"),
source=str(data.get("source", "unknown")),
orca_version=str(data.get("orca_version", "unknown")),
)
[docs]
@dataclass(frozen=True, slots=True)
class TrajectoryNebPath(DataclassMapping):
"""Structured ChemGP trajectory path arrays."""
images: np.ndarray
energies: np.ndarray
gradients: np.ndarray
f_para: np.ndarray
rxn_coord: np.ndarray
[docs]
@dataclass(frozen=True, slots=True)
class TrajectoryNebResult(DataclassMapping):
"""Structured ChemGP trajectory NEB result."""
path: TrajectoryNebPath
convergence: ArrayGroup
metadata: ParserAttrs
[docs]
@dataclass(frozen=True, slots=True)
class PlumedFesResult(DataclassMapping):
"""Structured PLUMED free-energy-surface result."""
fes: np.ndarray
hills: np.ndarray
rows: int
dimension: int
per: list[bool] | tuple[bool, ...]
x: np.ndarray
y: np.ndarray | None = None
pcv1: list[float] | tuple[float, ...] | None = None
pcv2: list[float] | tuple[float, ...] | None = None
[docs]
@dataclass(frozen=True, slots=True)
class PlumedMinimaResult(DataclassMapping):
"""Structured PLUMED minima result."""
minima: Any
fes_result: PlumedFesResult