Source code for vibesketch.inputs.orca_keywords

"""The ORCA keyword model: what vibe-sketch knows how to say in ORCA.

The ORCA writer translates a program-neutral job specification through these
tables and nothing else. A method, basis set, auxiliary basis set,
approximation, dispersion correction or solvent that is not here is refused
with the reason (AGENTS.md, ground rule 5); extending what ORCA jobs can do
means adding a row here, with a test.

Spellings follow the ORCA 6 manual. Names are matched without regard to case,
and a few common spellings map to ORCA's (``M06-2X`` to ``M062X``,
``D3(BJ)`` to ``D3BJ``).
"""

from __future__ import annotations

import re
from dataclasses import dataclass
from typing import Final, Literal

__all__ = [
    "APPROXIMATIONS",
    "Method",
    "approximation_keyword",
    "aux_basis_kind",
    "basis_family",
    "dispersion_keyword",
    "known_methods",
    "method_for",
    "solvent_name",
]

MethodClass = Literal["hf", "dft", "double-hybrid", "composite", "mp2", "coupled-cluster"]


[docs] @dataclass(frozen=True) class Method: """One ORCA method keyword and what it can do.""" keyword: str kind: MethodClass #: The method carries its own dispersion correction (D3, D4 or VV10). has_dispersion: bool = False #: ORCA has analytic second derivatives for it, so ``Freq`` works as written. analytic_hessian: bool = True
def _methods() -> dict[str, Method]: table: list[Method] = [Method("HF", "hf")] for keyword in ( "BP86", "BLYP", "PBE", "revPBE", "TPSS", "SCAN", "r2SCAN", "B3LYP", "B3PW91", "PBE0", "TPSSh", "TPSS0", "M06", "M062X", "BHandHLYP", "X3LYP", "CAM-B3LYP", "wB97X", "LC-BLYP", "r2SCAN0", "PW6B95", ): # fmt: skip table.append(Method(keyword, "dft")) for keyword in ("B97-D3", "wB97X-D3", "wB97X-D4", "wB97M-D4"): table.append(Method(keyword, "dft", has_dispersion=True)) for keyword in ("wB97X-V", "wB97M-V"): # VV10 nonlocal correlation table.append(Method(keyword, "dft", has_dispersion=True, analytic_hessian=False)) for keyword in ("B2PLYP", "DSD-PBEP86", "PWPB95"): table.append(Method(keyword, "double-hybrid", analytic_hessian=False)) for keyword in ("HF-3c", "PBEh-3c", "B97-3c", "r2SCAN-3c", "wB97X-3c"): table.append(Method(keyword, "composite", has_dispersion=True)) for keyword in ("MP2", "RI-MP2"): table.append(Method(keyword, "mp2", analytic_hessian=False)) for keyword in ("CCSD", "CCSD(T)", "DLPNO-CCSD", "DLPNO-CCSD(T)"): table.append(Method(keyword, "coupled-cluster", analytic_hessian=False)) known = {method.keyword.lower(): method for method in table} known["m06-2x"] = known["m062x"] return known _METHODS: Final = _methods()
[docs] def method_for(name: str) -> Method | None: """The ORCA method for ``name``, or ``None`` if vibe-sketch does not know it.""" return _METHODS.get(name.lower())
[docs] def known_methods() -> list[str]: """Every method keyword in the table, for refusals and listings.""" return sorted({method.keyword for method in _METHODS.values()}, key=str.lower)
#: Basis-set families by pattern; the canonical spelling is rebuilt from the match. _BASIS_FAMILIES: Final[list[tuple[str, re.Pattern[str]]]] = [ ("def2", re.compile(r"^(ma-)?def2-(m?SVPD?|TZVPP?D?|QZVPP?D?)$", re.IGNORECASE)), ("pople", re.compile( r"^(STO-3G|3-21G|6-31(1)?\+{0,2}G" # 6-31G, 6-311++G ... r"(\*{1,2}|\(\d?[df]{1,2}(,\d?[pd]{1,2})?\))?)$", # *, **, (d,p), (2df,2pd) re.IGNORECASE, )), ("dunning", re.compile(r"^(aug-)?cc-p(w?C)?V[DTQ56]Z$", re.IGNORECASE)), ("jensen", re.compile(r"^(aug-)?pc(seg|Sseg|J)?-[0-4]$", re.IGNORECASE)), ("x2c", re.compile(r"^x2c-(SVPall|TZVPall|TZVPPall|QZVPall)(-s)?$", re.IGNORECASE)), ] # fmt: skip
[docs] def basis_family(name: str) -> str | None: """The family of a basis set ORCA knows by this name, or ``None``.""" for family, pattern in _BASIS_FAMILIES: if pattern.match(name): return family return None
_AUX_PATTERNS: Final[list[tuple[str, re.Pattern[str]]]] = [ ("J", re.compile(r"^(def2|SARC|x2c)/J$", re.IGNORECASE)), ("JK", re.compile(r"^(def2|(aug-)?cc-pV[DTQ5]Z)/JK$", re.IGNORECASE)), ("C", re.compile(r"^((def2-(SVP|TZVPP?|QZVPP))|(aug-)?cc-pV[DTQ5]Z)/C$", re.IGNORECASE)), ("auto", re.compile(r"^AutoAux$", re.IGNORECASE)), ]
[docs] def aux_basis_kind(name: str) -> str | None: """``J``, ``JK``, ``C`` or ``auto`` for an auxiliary basis set ORCA knows, else ``None``.""" for kind, pattern in _AUX_PATTERNS: if pattern.match(name): return kind return None
#: RI approximations, and the auxiliary basis kinds each can use. APPROXIMATIONS: Final = { "RIJCOSX": ("J", "auto"), "RIJK": ("JK", "auto"), "RIJONX": ("J", "auto"), } _APPROXIMATION_ALIASES: Final = {"ri-j": "RIJONX", "rij": "RIJONX"}
[docs] def approximation_keyword(name: str) -> str | None: """ORCA's keyword for an RI approximation, or ``None``.""" lowered = name.lower() for keyword in APPROXIMATIONS: if keyword.lower() == lowered: return keyword return _APPROXIMATION_ALIASES.get(lowered)
_DISPERSION: Final = { "d2": "D2", "d3": "D3ZERO", "d3zero": "D3ZERO", "d3(0)": "D3ZERO", "d3bj": "D3BJ", "d3(bj)": "D3BJ", "d4": "D4", }
[docs] def dispersion_keyword(name: str) -> str | None: """ORCA's keyword for a dispersion correction, or ``None``.""" return _DISPERSION.get(name.lower())
#: Solvents ORCA's CPCM and SMD both know, by the name vibe-sketch accepts. _SOLVENTS: Final = { "water": "water", "h2o": "water", "methanol": "methanol", "ethanol": "ethanol", "acetonitrile": "acetonitrile", "mecn": "acetonitrile", "acetone": "acetone", "dmso": "DMSO", "dimethylsulfoxide": "DMSO", "dmf": "DMF", "n,n-dimethylformamide": "DMF", "thf": "THF", "tetrahydrofuran": "THF", "dichloromethane": "CH2Cl2", "ch2cl2": "CH2Cl2", "dcm": "CH2Cl2", "chloroform": "chloroform", "chcl3": "chloroform", "toluene": "toluene", "benzene": "benzene", "hexane": "hexane", "n-hexane": "hexane", "cyclohexane": "cyclohexane", "diethylether": "diethylether", "diethyl ether": "diethylether", "pyridine": "pyridine", "ammonia": "ammonia", "octanol": "octanol", "ethyl acetate": "ethylacetate", "ethylacetate": "ethylacetate", "carbon tetrachloride": "CCl4", "ccl4": "CCl4", "1,4-dioxane": "dioxane", "dioxane": "dioxane", }
[docs] def solvent_name(name: str) -> str | None: """ORCA's name for a solvent, or ``None``.""" return _SOLVENTS.get(name.lower().strip())