"""ORCA files opened as documents: outputs and Hessian files (#86).
These files hold atoms and coordinates, not a chemical graph. The bonds and
bond orders are inferred from the coordinates with the stated charge
(:mod:`vibesketch.document.coordinates`), a structure whose bond orders do not
fit is left out rather than drawn with guessed bonds, and the document records
that its graph was inferred. Each geometry an optimisation printed is kept as
a conformer of its species, the last first.
ORCA's output vocabulary: ORCA 6.1 manual,
https://www.faccts.de/docs/orca/6.1/manual/contents/quickstartguide/hellowater.html
"""
from __future__ import annotations
import re
from vibesketch.document.coordinates import Structure
__all__ = ["OrcaUnreadable", "hessian_structures", "output_structures"]
[docs]
class OrcaUnreadable(ValueError):
"""The data is not an ORCA file this reader recognises."""
def _text(data: str | bytes, what: str) -> str:
if isinstance(data, str):
return data
try:
return data.decode("utf-8")
except UnicodeDecodeError:
raise OrcaUnreadable(f"an ORCA {what} must be UTF-8 text") from None
def _stated(text: str, pattern: str, what: str) -> int:
"""The one value ORCA states for ``what``; refused when absent or contradictory."""
values = {int(m) for m in re.findall(pattern, text, re.M)}
if len(values) != 1:
raise OrcaUnreadable(
f"the ORCA output states {'no' if not values else 'more than one'} {what}"
)
return values.pop()
[docs]
def output_structures(data: str | bytes) -> tuple[list[Structure], list[str]]:
"""Each geometry an ORCA 6 output printed, with its charge and multiplicity, and warnings."""
from vibesketch.document.results import _coordinate_blocks, _same
text = _text(data, "output")
if not re.search(r"^\s*Program Version\s+6\.", text, re.M):
raise OrcaUnreadable("not an ORCA 6 output: it names no ORCA 6 program version")
charge = _stated(text, r"^\s*Total Charge\s+Charge\s+\.+\s+(-?\d+)\s*$", "total charge")
multiplicity = _stated(text, r"^\s*Multiplicity\s+Mult\s+\.+\s+(\d+)\s*$", "multiplicity")
frames: list[tuple[list[str], list[list[float]]]] = []
for _, (symbols, xyz) in _coordinate_blocks(text):
if frames and frames[-1][0] == symbols and _same(frames[-1][1], xyz):
continue # ORCA prints the geometry it ends on more than once
frames.append((symbols, xyz))
if not frames:
raise OrcaUnreadable("the ORCA output prints no Cartesian coordinates in angstrom")
warnings: list[str] = []
if "ORCA TERMINATED NORMALLY" not in text:
warnings.append("the ORCA run did not terminate normally; its last geometry is shown")
if "GEOMETRY OPTIMIZATION" in text and "THE OPTIMIZATION HAS CONVERGED" not in text:
warnings.append("the optimisation did not converge; its last geometry is shown")
structures = [
Structure(
id=f"geometry {number}" if len(frames) > 1 else "the geometry",
symbols=symbols,
positions=xyz,
charge=charge,
multiplicity=multiplicity,
method="orca-output",
)
for number, (symbols, xyz) in enumerate(frames, start=1)
]
return structures, warnings
[docs]
def hessian_structures(data: str | bytes) -> tuple[list[Structure], list[str]]:
"""The geometry of an ORCA Hessian file, which states no charge or multiplicity."""
from vibesketch.document.results import ResultRefused, _hessian_geometry
raw = data.encode("utf-8") if isinstance(data, str) else data
try:
symbols, xyz = _hessian_geometry(raw)
except ResultRefused as error:
raise OrcaUnreadable(str(error)) from None
structure = Structure(
id="the Hessian file", symbols=symbols, positions=xyz, charge=0, method="orca-hess"
)
return [structure], [
"an ORCA Hessian file states no charge or multiplicity: it is read as neutral, and its "
"spin follows from its electrons"
]