Source code for vibesketch.inputs.qcschema

"""The QCSchema molecule writer.

Writes a ``qcschema_molecule`` (schema version 2): symbols, geometry in bohr,
charge and multiplicity. It emits only the fields vibe-qc's QCSchema adapter
accepts (``vibeqc.qcschema.molecule_from_qcschema``), which itself rejects
any molecule feature it cannot represent; this writer holds itself to the
same rule from the other side.

A molecule carries no calculation, so every task but ``geometry`` is
refused. :class:`QcschemaInputWriter` writes the calculation: a
``qcschema_input`` (schema version 1) with the molecule, a driver and a
model, exactly as far as vibe-qc's adapter (``vibeqc.qcschema``) reads one:
the ``energy`` driver for a single point, ``hessian`` for frequencies (the
adapter computes Hessians for HF only), ``model`` with method and basis,
and empty ``keywords``. Whatever the adapter would reject is refused here
first: optimisations (an AtomicInput has no such driver), TDDFT, NMR, and
every keyword (dispersion, solvation, auxiliary basis, approximation,
resources, constraints).
"""

from __future__ import annotations

import json
from collections.abc import Sequence
from typing import Any, ClassVar

from vibesketch.document.model import JobSpec, Species, Task
from vibesketch.inputs.base import InputFile, InputWriter, first_conformer, needs_conformer
from vibesketch.units import ANGSTROM_TO_BOHR


[docs] class QcschemaMoleculeWriter(InputWriter): name: ClassVar[str] = "qcschema-molecule" description: ClassVar[str] = "QCSchema molecule (v2): geometry in bohr, charge, multiplicity" tasks: ClassVar[frozenset[Task]] = frozenset({"geometry"}) def _refusals(self, species: Species, spec: JobSpec) -> list[str]: reasons = needs_conformer(self.name, species) if spec.task != "geometry": reasons.append( "a QCSchema molecule carries no calculation; write the calculation with the " "'qcschema' writer (an input with model and driver) or the 'vibeqc' writer" ) return reasons
[docs] def molecule(self, species: Species) -> dict[str, Any]: """The ``qcschema_molecule`` object for ``species``.""" from vibesketch import __version__ conformer = first_conformer(species) geometry = [ coordinate * ANGSTROM_TO_BOHR for row in conformer.xyz_angstrom for coordinate in row ] return { "schema_name": "qcschema_molecule", "schema_version": 2, "symbols": list(conformer.symbols), "geometry": geometry, "molecular_charge": species.charge, "molecular_multiplicity": species.multiplicity, "provenance": { "creator": "vibe-sketch", "version": __version__, "routine": "vibesketch.inputs.qcschema", }, }
def _render(self, species: Species, spec: JobSpec) -> Sequence[InputFile]: text = json.dumps(self.molecule(species), indent=2) + "\n" return [InputFile(f"{species.id}.qcschema.json", text, "application/json")]
#: QCSchema drivers for the tasks the input writer can express. _DRIVERS: dict[Task, str] = {"sp": "energy", "freq": "hessian"}
[docs] class QcschemaInputWriter(InputWriter): name: ClassVar[str] = "qcschema" description: ClassVar[str] = "QCSchema input (v1) for vibe-qc: energy, or the HF Hessian" tasks: ClassVar[frozenset[Task]] = frozenset(_DRIVERS) fields: ClassVar[frozenset[str]] = frozenset({"method", "basis"}) def _refusals(self, species: Species, spec: JobSpec) -> list[str]: reasons = needs_conformer(self.name, species) if species.conformers and first_conformer(species).electron_count(species.charge) == 0: reasons.append(f"species {species.id} has no electrons: there is nothing to compute") if spec.task in ("opt", "opt+freq"): reasons.append( "a QCSchema AtomicInput has no optimisation driver; the 'vibeqc' writer " "writes optimisations" ) if spec.basis is None and spec.task != "geometry": reasons.append("vibe-qc's QCSchema adapter needs a basis set in the model") if spec.task == "freq" and (spec.method or "").lower() not in ("hf", "scf", "rhf", "uhf"): reasons.append( f"vibe-qc's QCSchema adapter computes Hessians for HF only, not {spec.method}; " "the 'vibeqc' writer covers other methods" ) return reasons def _render(self, species: Species, spec: JobSpec) -> Sequence[InputFile]: from vibesketch import __version__ document = { "schema_name": "qcschema_input", "schema_version": 1, "molecule": QcschemaMoleculeWriter().molecule(species), "driver": _DRIVERS[spec.task], "model": {"method": (spec.method or "").lower(), "basis": (spec.basis or "").lower()}, "keywords": {}, "provenance": { "creator": "vibe-sketch", "version": __version__, "routine": "vibesketch.inputs.qcschema", }, } text = json.dumps(document, indent=2) + "\n" return [InputFile(f"{species.id}.qcschema_input.json", text, "application/json")]