######################################################################## EMLE-Engine: https://github.com/chemle/emle-engine## Copyright: 2023-2025## Authors: Lester Hedges <lester.hedges@gmail.com># Kirill Zinovjev <kzinovjev@gmail.com>## EMLE-Engine is free software: you can redistribute it and/or modify# it under the terms of the GNU General Public License as published by# the Free Software Foundation, either version 2 of the License, or# (at your option) any later version.## EMLE-Engine 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 General Public License for more details.## You should have received a copy of the GNU General Public License# along with EMLE-Engine. If not, see <http://www.gnu.org/licenses/>.#####################################################################"""XTB in-vacuo backend implementation."""__all__=["XTB"]importaseas_aseimportnumpyas_npfrom.._unitsimport_EV_TO_HARTREE,_BOHR_TO_ANGSTROMfrom._backendimportBackendas_Backend
[docs]@staticmethoddefcalculate(atomic_numbers,xyz,forces=True):""" Compute the energy and forces. Parameters ---------- atomic_numbers: numpy.ndarray, (N_BATCH, N_QM_ATOMS,) The atomic numbers of the atoms in the QM region. xyz: numpy.ndarray, (N_BATCH, N_QM_ATOMS, 3) The coordinates of the atoms in the QM region in Angstrom. forces: bool Whether to calculate and return forces. Returns ------- energy: float The in-vacuo energy in Eh. forces: numpy.ndarray The in-vacuo forces in Eh/Bohr. """ifnotisinstance(atomic_numbers,_np.ndarray):raiseTypeError("'atomic_numbers' must be of type 'numpy.ndarray'")ifnotisinstance(xyz,_np.ndarray):raiseTypeError("'xyz' must be of type 'numpy.ndarray'")iflen(atomic_numbers)!=len(xyz):raiseValueError(f"Length of 'atomic_numbers' ({len(atomic_numbers)}) does not "f"match length of 'xyz' ({len(xyz)})")# Convert to batched NumPy arrays.iflen(atomic_numbers.shape)==1:atomic_numbers=_np.expand_dims(atomic_numbers,axis=0)xyz=_np.expand_dims(xyz,axis=0)fromxtb.ase.calculatorimportXTBas_XTB# Lists to store results.results_energy=[]results_forces=[]# Loop over batches.fori,(atomic_numbers_i,xyz_i)inenumerate(zip(atomic_numbers,xyz)):iflen(atomic_numbers_i)!=len(xyz_i):raiseValueError(f"Length of 'atomic_numbers' ({len(atomic_numbers_i)}) does not "f"match length of 'xyz' ({len(xyz_i)}) for index {i}")# Create an ASE atoms object.atoms=_ase.Atoms(numbers=atomic_numbers_i,positions=xyz_i,)# Create the calculator.atoms.calc=_XTB(method="GFN2-xTB")# Get the energy.results_energy.append(atoms.get_potential_energy()*_EV_TO_HARTREE)ifforces:results_forces.append(atoms.get_forces()*_EV_TO_HARTREE*_BOHR_TO_ANGSTROM)# Convert to NumPy arrays.results_energy=_np.array(results_energy)results_forces=_np.array(results_forces)returnresults_energy,results_forcesifforceselseresults_energy