Source code for mbgdml.interfaces.ase

# MIT License
#
# Copyright (c) 2020-2023, Alex M. Maldonado
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from ase.calculators.calculator import Calculator
import numpy as np
import ray
from ..logger import GDMLLogger

log = GDMLLogger(__name__)

# pylint: disable-next=invalid-name
[docs]class mbeCalculator(Calculator): r"""ASE calculator for a many-body expansion predictor.""" implemented_properties = ["energy", "forces", "stress"] def __init__( self, mbe_pred, parameters=None, e_conv=1.0, f_conv=1.0, atoms=None, **kwargs ): """ Parameters ---------- mbe_pred : :obj:`mbgdml.mbe.mbePredict` Initialized many-body expansion predictor. parameters : :obj:`dict`, optional System and calculation properties. e_conv : :obj:`float`, default: ``1.0`` Model energy conversion factor to eV (required by ASE). f_conv : :obj:`float`, default: ``1.0`` Model forces conversion factor to eV/A (required by ASE). """ self.name = "mbGDML" Calculator.__init__(self, restart=None, label=None, atoms=atoms, **kwargs) self.mbe_pred = mbe_pred self.mbe_pred.use_voigt = True self.e_conv = e_conv self.f_conv = f_conv if parameters is None: parameters = {} self.parameters = parameters # pylint: disable-next=unused-argument, keyword-arg-before-vararg
[docs] def calculate(self, atoms=None, *args, **kwargs): r"""Predicts all available properties using the MBE predictor.""" is_periodic = bool(self.mbe_pred.periodic_cell) if atoms is not None: if is_periodic: atoms.wrap() self.atoms = atoms.copy() parameters = self.parameters entity_ids = parameters["entity_ids"] comp_ids = parameters["comp_ids"] predict_kwargs = {} if is_periodic: # Update cell vectors and cutoff cell_vectors = atoms.get_cell()[:] periodic_cell = self.mbe_pred.periodic_cell if self.mbe_pred.use_ray: periodic_cell = ray.get(periodic_cell) periodic_cell.cell_v = cell_vectors self.mbe_pred.periodic_cell = periodic_cell if not self.mbe_pred.compute_stress: self.mbe_pred.compute_stress = True E, F, *stress = self.mbe_pred.predict( atoms.get_atomic_numbers(), atoms.get_positions(), entity_ids, comp_ids, **predict_kwargs ) E = E[0] F = F.reshape(-1, 3) # Unit conversions E *= self.e_conv F *= self.f_conv self.results = {"energy": E, "forces": F} if is_periodic: stress = stress[0][0] self.results["stress"] = stress * self.e_conv
def todict(self, skip_default=True): defaults = self.get_default_parameters() dct = {} for key, value in self.parameters.items(): if hasattr(value, "todict"): value = value.todict() if skip_default: default = defaults.get(key, "_no_default_") if default != "_no_default_": continue if isinstance(value, np.ndarray): # For some reason ASE does not like loading comp_ids as arrays. # An error like "data type 'str128' not understood" will be # thrown. We just convert all string arrays to lists to avoid # this. if value.dtype.kind in {"U", "S"}: value = value.tolist() dct[key] = value return dct