Source code for xtb.qcschema.harness

# This file is part of xtb.
#
# Copyright (C) 2020 Sebastian Ehlert
#
# xtb is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# xtb is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with xtb.  If not, see <https://www.gnu.org/licenses/>.
"""Integration with the `QCArchive infrastructure <http://docs.qcarchive.molssi.org>`_.

This module provides a way to translate QCSchema or QCElemental Atomic Input
into a format understandable by the ``xtb`` API which in turn provides the
calculation results in a QCSchema compatible format.

The ``xtb`` model supports any method accepted by ``xtb.utils.get_method``.

Supported keywords are

======================== =========== ============================================
 Keyword                  Default     Description
======================== =========== ============================================
 accuracy                 1.0         Numerical accuracy of the calculation
 electronic_temperature   300.0       Electronic temperatur for TB methods
 max_iterations           250         Iterations for self-consistent evaluation
 solvent                  "none"      GBSA implicit solvent model
======================== =========== ============================================
"""

from typing import Union
from tempfile import NamedTemporaryFile
from ..libxtb import VERBOSITY_MUTED, get_api_version
from ..interface import Calculator, XTBException
from ..utils import get_method, get_solvent
import qcelemental as qcel


_keywords = [
    "accuracy",
    "electronic_temperature",
    "max_iterations",
    "solvent",
    "verbosity"
]


[docs]def run_qcschema( input_data: Union[dict, qcel.models.AtomicInput] ) -> qcel.models.AtomicResult: """Perform a calculation based on an atomic input model. Example ------- >>> from xtb.qcschema.harness import run_qcschema >>> import qcelemental as qcel >>> atomic_input = qcel.models.AtomicInput( ... molecule = qcel.models.Molecule( ... symbols = ["O", "H", "H"], ... geometry = [ ... 0.00000000000000, 0.00000000000000, -0.73578586109551, ... 1.44183152868459, 0.00000000000000, 0.36789293054775, ... -1.44183152868459, 0.00000000000000, 0.36789293054775 ... ], ... ), ... driver = "energy", ... model = { ... "method": "GFN2-xTB", ... }, ... keywords = { ... "accuracy": 1.0, ... "max_iterations": 50, ... }, ... ) ... >>> atomic_result = run_qcschema(atomic_input) >>> atomic_result.return_result -5.070451354848316 """ if not isinstance(input_data, qcel.models.AtomicInput): atomic_input = qcel.models.AtomicInput(**input_data) else: atomic_input = input_data ret_data = atomic_input.dict() provenance = { "creator": "xtb", "version": get_api_version(), "routine": "xtb.qcschema.run_qcschema", } _method = get_method(atomic_input.model.method) if _method is None: ret_data.update( success=False, return_result=0.0, provenance=provenance, properties={}, error=qcel.models.ComputeError( error_type="input_error", error_message="Invalid method {} provided in model".format( atomic_input.model.method ), ), ) return qcel.models.AtomicResult(**ret_data) verbosity = atomic_input.keywords.get("verbosity", "full") fd = None output = None success = True try: calc = Calculator( _method, atomic_input.molecule.atomic_numbers, atomic_input.molecule.geometry, atomic_input.molecule.molecular_charge, atomic_input.molecule.molecular_multiplicity - 1, ) if "solvent" in atomic_input.keywords: calc.set_solvent(get_solvent(atomic_input.keywords["solvent"])) if "accuracy" in atomic_input.keywords: calc.set_accuracy(atomic_input.keywords["accuracy"]) if "max_iterations" in atomic_input.keywords: calc.set_max_iterations(atomic_input.keywords["max_iterations"]) if "electronic_temperature" in atomic_input.keywords: calc.set_electronic_temperature( atomic_input.keywords["electronic_temperature"] ) # Work out how verbose the printing from xtb should be verbosity = calc.set_verbosity(verbosity) if verbosity > VERBOSITY_MUTED: fd = NamedTemporaryFile() calc.set_output(fd.name) # Perform actual calculation res = calc.singlepoint() calc.release_output() # Check if the calculation has generated a wavefunction _wfn = res.get_number_of_orbitals() > 0 # First we access properties that should be always available properties = { "return_energy": res.get_energy(), "scf_dipole_moment": res.get_dipole(), } extras = {"xtb": {"return_gradient": res.get_gradient()}} # Charges, bond order and so on are stored in the wavefunction if _wfn: extras["xtb"]["mulliken_charges"] = res.get_charges() extras["xtb"]["mayer_indices"] = res.get_bond_orders() if atomic_input.driver == "energy": return_result = properties["return_energy"] elif atomic_input.driver == "gradient": return_result = extras["xtb"]["return_gradient"] elif atomic_input.driver == "properties": return_result = { "dipole": properties["scf_dipole_moment"], } if _wfn: return_result["mulliken_charges"] = extras["xtb"]["mulliken_charges"] return_result["mayer_indices"] = extras["xtb"]["mayer_indices"] else: return_result = 0.0 success = False ret_data.update( error=qcel.models.ComputeError( error_type="input_error", error_message="Calculation succeeded but invalid driver request provided", ), ) ret_data['extras'].update(extras) except XTBException as ee: success = False ret_data.update( error=qcel.models.ComputeError( error_type="runtime_error", error_message=str(ee), ), ) return_result = 0.0 properties = {} if fd is not None: output = fd.read().decode() fd.close() ret_data.update( provenance=provenance, success=success, properties=properties, return_result=return_result, ) return qcel.models.AtomicResult(**ret_data, stdout=output)