######################################################################## 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/>.#####################################################################"""Rascal in-vacuo backend implementation."""__all__=["Rascal"]importaseas_aseimportosas_osimportpickleas_pickleimportnumpyas_npfrom.._unitsimport_EV_TO_HARTREE,_BOHR_TO_ANGSTROMfrom._backendimportBackendas_Backend
[docs]classRascal(_Backend):""" Rascal in-vacuo backend implementation. """def__init__(self,model):""" Constructor. Parameters ---------- model: str The path to the Rascal model file. """ifnotisinstance(model,str):raiseTypeError("'model' must be of type 'str'")# Convert to an absolute path.abs_model=_os.path.abspath(model)# Make sure the model file exists.ifnot_os.path.isfile(abs_model):raiseIOError(f"Unable to locate Rascal model file: '{model}'")# Load the model.try:self._model=_pickle.load(open(abs_model,"rb"))except:raiseIOError(f"Unable to load Rascal model file: '{model}'")# Try to get the SOAP parameters from the model.try:soap=self._model.get_representation_calculator()except:raiseValueError("Unable to extract SOAP parameters from Rascal model!")# Create the Rascal calculator.try:fromrascal.models.asemdimportASEMLCalculatoras_ASEMLCalculatorself._calculator=_ASEMLCalculator(self._model,soap)except:raiseRuntimeError("Unable to create Rascal calculator!")
[docs]defcalculate(self,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)# 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,)# The calculator requires periodic box information so we translate the atoms# so that the lowest (x, y, z) position is zero, then set the cell to the# maximum position.atoms.positions-=_np.min(atoms.positions,axis=0)atoms.cell=_np.max(atoms.positions,axis=0)# Run the calculation.self._calculator.calculate(atoms)# Get the energy.results_energy.append(self._calculator.results["energy"][0]*_EV_TO_HARTREE)ifforces:results_forces.append(self._calculator.results["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