"""The potentials built into Asap."""
__docformat__ = "restructuredtext en"
from asap3.Internal.Builtins import _asap
from asap3.Internal.CheckArray import check_parameter_array, _ChkLJarray
import asap3
import numpy as np
import ase
import ase.calculators.emt
import ase.data
from ase.calculators.calculator import BaseCalculator, all_changes
from copy import copy
import sys
EMTParameters = _asap.EMTParameters
EMTDefaultParameters = _asap.EMTDefaultParameters
EMTRasmussenParameters = _asap.EMTRasmussenParameters
#EMTVariableParameters = _asap.EMTVariableParameters
#EMThcpParameters = _asap.EMThcpParameters
BrennerPotential = _asap.BrennerPotential
#IntVector = asap.IntVector
#DoubleVector = asap.DoubleVector
# class FullNeighborList(asap.NeighborList2):
# "A full neighbor list object."
# def __init__(self, atoms, *args):
# asap.NeighborList2.__init__(self, atoms, *args)
# self.EnableFullNeighborLists()
# self.atoms = atoms # Keep the atoms alive.
# self.CheckAndUpdateNeighborList()
# class NeighborCellLocator(asap.NeighborCellLocator):
# "A Neighbor list object."
# def __init__(self, atoms, *args):
# asap.NeighborCellLocator.__init__(self, atoms, *args)
# self.atoms = atoms # Keep the atoms alive.
# self.CheckAndUpdateNeighborList()
# class NeighborList(asap.NeighborList2):
# "A Neighbor list object."
# def __init__(self, atoms, *args):
# asap.NeighborList2.__init__(self, atoms, *args)
# self.atoms = atoms # Keep the atoms alive.
# self.CheckAndUpdateNeighborList()
def get_cell_heights(atoms):
"Get the heights of the unit cells (perpendicular to the surfaces)"
heights = np.zeros(3)
cell = atoms.get_cell()
for i in range(3):
direction = np.cross(cell[(i+1) % 3], cell[(i+2) % 3])
heights[i] = abs(np.dot(cell[i], direction) / np.sqrt(np.dot(direction,direction)))
return heights
def smallest_pbc_direction(atoms):
smallest = 1.0e20 # Very big
pbc = atoms.get_pbc()
h = get_cell_heights(atoms)
for i in range(3):
if pbc[i] and h[i] < smallest:
smallest = h[i]
return smallest
class AsapPotential(BaseCalculator):
"""Mixin for all Asap potentials."""
implemented_properties = [
'energy',
'free_energy',
'forces',
'stress',
'stresses',
'energies',
]
_stresscomp = np.array([[0, 5, 4], [5, 1, 3], [4, 3, 2]])
def set_atoms_mixin(self, atoms):
"""Set the atoms. This mixin fixes compatibility with ASE 3.18.0 and earlier."""
if not hasattr(atoms, "get_global_number_of_atoms"):
atoms.get_global_number_of_atoms = atoms.get_number_of_atoms
def set_atoms(self, atoms, *args):
"""Set the atoms. This default version fixes compatibility with ASE 3.18.0 and earlier."""
self.set_atoms_mixin(atoms)
# Now call the potential.
super().set_atoms(atoms, *args)
def _get_stress(self, atoms):
stress = self.get_virial(atoms)
if not getattr(atoms, "_ase_handles_dynamic_stress", False):
p = atoms.get_momenta()
masses = atoms.get_masses()
invmass = 1.0 / masses
for alpha in range(3):
for beta in range(alpha, 3):
stress[self._stresscomp[alpha,beta]] -= (p[:,alpha] * p[:,beta] * invmass).sum()
stress /= atoms.get_volume()
return stress
def _get_stresses(self, atoms):
stresses = self.get_virials(atoms)
if hasattr(self, 'get_atomic_volumes'):
invvol = 1.0 / self.get_atomic_volumes()
else:
invvol = atoms.get_global_number_of_atoms() / atoms.get_volume()
if not getattr(atoms, "_ase_handles_dynamic_stress", False):
p = atoms.get_momenta()
invmass = 1.0 / atoms.get_masses()
for alpha in range(3):
for beta in range(alpha, 3):
stresses[:,self._stresscomp[alpha,beta]] -= p[:,alpha] * p[:,beta] * invmass
for i in range(6):
stresses[:,i] *= invvol
return stresses
def calculation_required(self, atoms, properties) -> bool:
return self._cpp_class.calculation_required(self, atoms, properties)
def calculate(
self,
atoms=None,
properties=['energy'],
system_changes=all_changes,
) -> None:
methods = {
'energies': self._cpp_class.get_potential_energies,
'forces': self._cpp_class.get_forces,
'stress': AsapPotential._get_stress,
'stresses': AsapPotential._get_stresses,
}
if 'free_energy' in properties and 'energy' in properties:
# Don't call get_potential_energy twice
properties = [x for x in properties if x != 'free_energy']
for prop in properties:
if prop in {'energy', 'free_energy'}:
energy = self._cpp_class.get_potential_energy(self, atoms)
self.results['energy'] = self.results['free_energy'] = energy
else:
self.results[prop] = methods[prop](self, atoms)
def get_name(self):
return "asap3." + self._get_name()
def get_neighborlist(self):
"""Return the neighbor locator"""
return self._get_neighborlist()
def _get_parameters(self):
return {}
def _set_parameters(self, params):
"""Setting parameters not supported per default."""
if params: # OK if nothing to set.
raise NotImplementedError('Setting parameters not supported for {self.get_name()}')
name = property(get_name, doc="Name of the potential")
parameters = property(_get_parameters, _set_parameters, doc='Setting/getting parameters not supported.')
class EMT(AsapPotential, _asap.EMT):
"""The Effective Medium Theory potential.
Parameters
----------
parameters : EMTParameterProvider, optional
The parameter provider. By default, standard parameters are used
for the supported metals (Ni, Cu, Pd, Ag, Pt, Au).
minimum_image : bool, optional
Whether to use the minimum image convention. If not given, it is
decided from the cutoff and the size of the computational box.
verbose : bool, optional
Print information while calculating. Defaults to the global Asap
verbosity.
"""
def __init__(self, parameters=None, minimum_image=None, verbose=None):
AsapPotential.__init__(self)
self._cpp_class = _asap.EMT
if parameters is None:
parameters = EMTDefaultParameters()
if verbose is None:
verbose = _asap.verbose
_asap.EMT.__init__(self, parameters, verbose)
self.verbose = verbose
self.absolute_max_cutoff = parameters.get_max_cutoff_beforeinit()
self._set_atoms_called = False
self.use_minimum_image = minimum_image
def set_atoms(self, atoms, *args):
self.set_atoms_mixin(atoms)
if not self._set_atoms_called:
if self.use_minimum_image is None:
self.use_minimum_image = smallest_pbc_direction(atoms) >= 2.1 * self.absolute_max_cutoff
if not self.use_minimum_image:
#sys.stderr.write("Asap.EMT: Disabling minimum image convention.\n")
self._use_imageatoms()
self._set_atoms_called = True
_asap.EMT.set_atoms(self, atoms, *args)
def get_atomic_volumes(self):
return self._get_atomic_volumes()
class MonteCarloEMT(AsapPotential, _asap.MonteCarloEMT):
"""The Effective Medium Theory potential optimized for Monte Carlo.
This version is optimized for Monte Carlo simulations, at the price of
somewhat slower ordinary energy and force calculations and a larger
memory footprint. In addition, this version cannot be parallelized.
Parameters
----------
parameters : EMTParameterProvider, optional
The parameter provider. By default, standard parameters are used
for the supported metals (Ni, Cu, Pd, Ag, Pt, Au).
verbose : bool, optional
Print information while calculating. Defaults to the global Asap
verbosity.
"""
def __init__(self, parameters=None, verbose=None):
AsapPotential.__init__(self)
self._cpp_class = _asap.MonteCarloEMT
if parameters is None:
parameters = EMTDefaultParameters()
if verbose is None:
verbose = _asap.verbose
self.verbose = verbose
_asap.MonteCarloEMT.__init__(self, parameters, verbose)
class EMT2013(AsapPotential, _asap.EMT2013):
"""The Effective Medium Theory version 2011 potential.
Parameters
----------
parameters : dict
The keys are elements (atomic numbers or chemical symbols), the
values are dictionaries with parameters. The names of the
parameters are ``'eta2'``, ``'lambda'``, ``'kappa'``, ``'E0'``,
``'V0'``, ``'S0'`` and ``'n0'``.
no_new_elements : bool, optional
If ``True``, the user promises that no new elements are introduced
after the first time the calculator is attached to an Atoms object,
and only elements present at that instant are initialized. If
``False`` (the default), all elements present in ``parameters`` are
initialized. This may cause a performance penalty, as neighbor
lists will be large enough to accommodate the largest atoms
supported.
minimum_image : bool, optional
Whether to use the minimum image convention. If not given, it is
decided from the cutoff and the size of the computational box.
verbose : bool, optional
Print information while calculating. Defaults to the global Asap
verbosity.
Raises
------
ValueError
If ``parameters`` does not contain a ``'Calculator'`` item
identifying it as an ``EMT2013_V1`` parameter set.
"""
def __init__(self, parameters, no_new_elements=False, minimum_image=None, verbose=None):
AsapPotential.__init__(self)
self._cpp_class = _asap.EMT2013
try:
par_ver = parameters['Calculator']
except KeyError:
raise ValueError("Dictionary with parameters should contain a" +
" Calculator item - be careful not to use an " +
"obsolete parameter definition !!")
if par_ver != "EMT2013_V1":
raise ValueError("The parameters appear to be for a calculator " +
"of type " + par_ver +
" (expected EMT2013_V1).")
params = {}
max_s0 = 0.0
for k in parameters.keys():
if k == "Calculator":
continue # Skip the version specification.
if isinstance(k, str):
z = ase.data.atomic_numbers[k]
else:
z = k
params[z] = copy(parameters[k])
params[z]['mass'] = ase.data.atomic_masses[z]
s0 = params[z]['S0']
if s0 > max_s0:
max_s0 = s0
if verbose is None:
verbose = _asap.verbose
self.verbose = verbose
_asap.EMT2013.__init__(self, params, no_new_elements, verbose)
beta = 1.809399790563555 # ((16*pi/3)^(1./3.))/sqrt(2)
self.maxcut = 0.5 * (np.sqrt(1.5) + np.sqrt(2.)) * np.sqrt(2.) * beta * max_s0 * 1.05
self._set_atoms_called = False
self.use_minimum_image = minimum_image
def set_atoms(self, atoms, *args):
self.set_atoms_mixin(atoms)
if not self._set_atoms_called:
if self.use_minimum_image is None:
self.use_minimum_image = smallest_pbc_direction(atoms) >= 2.5 * self.maxcut
if not self.use_minimum_image:
#sys.stderr.write("Asap.EMT2013: Disabling minimum image convention.\n")
self._use_imageatoms()
self._set_atoms_called = True
_asap.EMT2013.set_atoms(self, atoms, *args)
def get_atomic_volumes(self):
return self._get_atomic_volumes()
def EMT2011(parameters):
"""EMT2011 has been renamed EMT2013, please use the new version."""
sys.stderr.write("\nASAP Warning: EMT2011 is deprecated, use EMT2013 instead.\n")
return EMT2013(parameters)
class RGL(AsapPotential, _asap.RGL):
"""Calculator based on the RGL/Gupta semi-empirical tight-binding potential.
Parameters
----------
elements : sequence or dict
The elements that will be supported. Alternatively, the parameters
may be given here as a dictionary of the form
``{'Pt': [p, q, a, xi, r0], ('Pt','Y'): [...], ('Y','Y'): [...]}``.
p, q, a, xi, r0 : numpy.ndarray or float, optional
The potential parameters, listed in 2D arrays with each dimension
equal to the number of elements. Only the upper triangular part is
used. If there is only one element, the parameters may be numbers.
cutoff : float, optional
The distance at which the cutoff function starts, in nearest-neighbor
distances. The default is 1.73.
delta : float, optional
The length of the cutoff function, in nearest-neighbor distances.
The default is 0.15.
debug : bool, optional
Print the parameters as they are interpreted. The default is
``False``.
verbose : bool, optional
Print information while calculating. Defaults to the global Asap
verbosity.
Notes
-----
The cutoff defaults are set to include the 3rd and not the 4th nearest
neighbors. The expected nearest-neighbor distance is calculated from the
parameters.
"""
def __init__(self, elements, p=None, q=None, a=None, xi=None, r0=None,
cutoff=1.73, delta=0.15, debug=False, verbose=None):
AsapPotential.__init__(self)
self._cpp_class = _asap.RGL
# Check if parameters is given as a dictionary
if isinstance(elements, dict):
parameters = elements.copy()
# Get elements
elements = []
elementmap = {}
for key in parameters.keys():
if isinstance(key, str):
symbols = [key]
elif isinstance(key, tuple) and len(key) == 2:
symbols = list(key)
else:
raise KeyError('Key must either be a string or a tuple with ' +
'two elements.')
for s in symbols:
if not s in elements:
elementmap[s] = len(elements)
elements.append(s)
n = len(elements)
# Get parameters
p = np.zeros((n, n))
q = np.zeros((n, n))
a = np.zeros((n, n))
xi = np.zeros((n, n))
r0 = np.zeros((n, n))
visit = np.zeros(n)
for key in parameters.keys():
if isinstance(key, str):
i = j = elementmap[key]
elif isinstance(key, tuple) and len(key) == 2:
i = elementmap[key[0]]
j = elementmap[key[1]]
else:
raise KeyError('Key must either be a string or a tuple with ' +
'two elements.')
visit[i] += 1
visit[j] += 1
p[i,j] = parameters[key][0]
q[i,j] = parameters[key][1]
a[i,j] = parameters[key][2]
xi[i,j] = parameters[key][3]
r0[i,j] = parameters[key][4]
if np.any(visit != n + 1):
raise ValueError('For some elements there are either too few or ' +
'too many parameters.')
# Interpret elements
if not isinstance(elements, (tuple, list, np.ndarray)):
elements = [elements]
n = len(elements)
for i, symbol in enumerate(elements):
if isinstance(symbol, str):
elements[i] = ase.data.atomic_numbers[symbol]
elements = np.array(elements)
# Interpret parameters
p = check_parameter_array(n, "p", p)
q = check_parameter_array(n, "q", q)
a = check_parameter_array(n, "a", a)
xi = check_parameter_array(n, "xi", xi)
r0 = check_parameter_array(n, "r0", r0)
# Calculate cutoff
a1 = (np.log(np.sqrt(12) * a * p / (xi * q)) / (p - q) + 1) * r0
if not np.all((1.0 < a1) & (a1 < 10.0)):
raise ValueError('Unreasonable parameters - the nearest neighbor ' +
f'distance span [{a1.min():.3f}, {a1.max():.3f}]')
rcs = cutoff * a1.max()
rcd = delta * a1.max()
rce = rcs + rcd
#print "RGL cutoff: %.4f-%.4f" % (rcs, rcd)
# Calculate parameters for the cutoff function
qf = -xi * np.exp(-q * (rcs / r0 - 1)) / rcd**3
qd = q / r0 * rcd
q5 = (12.0*qf - 6.0*qf*qd + qf*qd*qd) / (2.0 * rcd**2)
q4 = (15.0*qf - 7.0*qf*qd + qf*qd*qd) / rcd
q3 = (20.0*qf - 8.0*qf*qd + qf*qd*qd) / 2.0
pf = -a * np.exp(-p * (rcs / r0 - 1)) / rcd**3
pd = p / r0 * rcd
p5 = (12.0*pf - 6.0*pf*pd + pf*pd*pd) / (2.0 * rcd**2)
p4 = (15.0*pf - 7.0*pf*pd + pf*pd*pd) / rcd
p3 = (20.0*pf - 8.0*pf*pd + pf*pd*pd) / 2.0
if debug:
print("RGL potential parameters")
print("p:", p)
print("q:", q)
print("a:", a)
print("xi:", xi)
print("r0:", r0)
print("rcs:", rcs)
print("rce:", rce)
print("q5:", q5)
print("q4:", q4)
print("q3:", q3)
print("p5:", p5)
print("p4:", p4)
print("p3:", p3)
if verbose is None:
verbose = _asap.verbose
self.verbose = verbose
_asap.RGL.__init__(self, elements, p, q, a, xi * xi, r0, p3, p4, p5,
q3, q4, q5, rcs, rce, verbose)
Gupta = RGL
[docs]
class LennardJones(AsapPotential, _asap.LennardJones):
"""Lennard-Jones potential.
Parameters
----------
elements : sequence of int
The elements that will be supported, given as atomic numbers.
epsilon : numpy.ndarray or float
The Lennard-Jones energy parameter, as a 2D array with each
dimension equal to the number of elements; only the lower triangular
part is used. A 1D array obtained by flattening the corresponding 2D
array is also accepted, but this is deprecated and incurs less
runtime testing. If there is only one element, a number may be
given.
sigma : numpy.ndarray or float
The Lennard-Jones length parameter, in the same format as
``epsilon``.
rCut : float, optional
The cutoff distance. A negative value (the default) selects the
built-in default cutoff.
modified : bool, optional
Whether the potential should be shifted so no jump occurs at the
cutoff. The default is ``True``.
verbose : bool, optional
Print information while calculating. Defaults to the global Asap
verbosity.
"""
def __init__(self, elements, epsilon, sigma, rCut=-1.0, modified=True, verbose=None):
AsapPotential.__init__(self)
self._cpp_class = _asap.LennardJones
try:
numelements = len(elements)
except TypeError:
numelements = 1
elements = [elements]
epsilon = _ChkLJarray(epsilon, numelements, "epsilon")
sigma = _ChkLJarray(sigma, numelements, "sigma")
masses = [ase.data.atomic_masses[z] for z in elements]
if verbose is None:
verbose = _asap.verbose
self.verbose = verbose
_asap.LennardJones.__init__(self, numelements, elements, epsilon, sigma,
masses, rCut, modified, verbose)
# Atomic volumes disabled in the C code.
#def get_atomic_volumes(self):
# return self._get_atomic_volumes()
class RahmanStillingerLemberg(AsapPotential, _asap.RahmanStillingerLemberg):
"""Rahman Stillinger Lemberg potential (RSL2).
"""
def __init__(self, D0, R0, y, a1, b1, c1, a2, b2, c2, a3, b3, c3,
elements, rCut=1.0, verbose=None):
AsapPotential.__init__(self)
self._cpp_class = _asap.RahmanStillingerLemberg
try:
numelements = len(elements)
except TypeError:
numelements = 1
elements = [elements]
D0 = _ChkLJarray(D0, numelements, "D0")
R0 = _ChkLJarray(R0, numelements, "R0")
y = _ChkLJarray(y, numelements, "y")
a1 = _ChkLJarray(a1, numelements, "a1")
b1 = _ChkLJarray(b1, numelements, "b1")
c1 = _ChkLJarray(c1, numelements, "c1")
a2 = _ChkLJarray(a2, numelements, "a2")
b2 = _ChkLJarray(b2, numelements, "b2")
c2 = _ChkLJarray(c2, numelements, "c2")
a3 = _ChkLJarray(a3, numelements, "a3")
b3 = _ChkLJarray(b3, numelements, "b3")
c3 = _ChkLJarray(c3, numelements, "c3")
masses = [ase.data.atomic_masses[z] for z in elements]
if verbose is None:
verbose = _asap.verbose
self.verbose = verbose
_asap.RahmanStillingerLemberg.__init__(self, numelements,
D0, R0, y, a1, b1, c1, a2, b2, c2, a3, b3, c3,
elements, masses, rCut, verbose)
# def Ewald(q, elements, rCut=1.0):
# """Lennard-Jones potential.
# Parameters:
# elements: Lists the elements that will be supported.
# epsilon and sigma: The LJ parameters. 2D arrays with each
# dimension equal to the number of elements. Only the lower
# triangular part is used. Or a 1D array obtained by flattening the
# corresponding 2D array (this possibility is deprecated, and incurs
# less runtime testing). If there is only one element, then epsilon
# and sigma may be numbers.
# rCut: The cutoff distance. Default: XXX
# modified: Should the potential be shifted so no jump occurs at the
# cutoff. Default: True.
# """
# try:
# numelements = len(elements)
# except TypeError:
# numelements = 1
# elements = [elements]
# q = _ChkLJarray(q, numelements, "q")
# masses = [ase.data.atomic_masses[z] for z in elements]
# return _asap.Ewald(numelements, q, elements, masses, rCut)
class MetalOxideInterface(AsapPotential, _asap.MetalOxideInterface):
def __init__(self,
P, Q, A, xi, r0, RGL_cut,
q, kappa,
D, alpha, R0,
a, b, f0, oxide_cut,
beta, gamma, interface_cut, verbose=None):
AsapPotential.__init__(self)
self._cpp_class = _asap.MetalOxideInterface
if verbose is None:
verbose = _asap.verbose
self.verbose = verbose
_asap.MetalOxideInterface.__init__(self,
P, Q, A, xi, r0, RGL_cut,
q, kappa,
D, alpha, R0,
a, b, f0, oxide_cut,
beta, gamma, interface_cut, verbose)
self.max_cutoff = max(RGL_cut, oxide_cut, interface_cut)
self.atoms_intialized = False
self._set_atoms_called = False
def set_atoms(self, atoms, *args):
self.set_atoms_mixin(atoms)
if not self._set_atoms_called:
if smallest_pbc_direction(atoms) < 2.05 * self.max_cutoff:
print('Using image atoms')
self._use_imageatoms()
self._set_atoms_called = True
if hasattr(atoms, 'ghosts'):
atoms.ghosts['assignment'] = np.zeros((0,),dtype=np.int32)
if hasattr(atoms, 'ghosts'):
atoms.ghosts['monolayer'] = np.zeros((0,),dtype=np.int32)
_asap.MetalOxideInterface.set_atoms(self, atoms, *args)
class MetalOxideInterface2(AsapPotential, _asap.MetalOxideInterface2):
def __init__(self,
P, Q, A, xi, r0, RGL_cut,
q, kappa,
D, alpha, R0,
a, b, f0, oxide_cut,
E, rho0, l0,
B, C,
interface_cut, verbose=None):
AsapPotential.__init__(self)
self._cpp_class = _asap.MetalOxideInterface2
if verbose is None:
verbose =_asap.verbose
self.verbose = verbose
_asap.MetalOxideInterface2.__init__(self,
P, Q, A, xi, r0, RGL_cut,
q, kappa,
D, alpha, R0,
a, b, f0, oxide_cut,
E, rho0, l0,
B, C,
interface_cut, verbose)
self.max_cutoff = max(RGL_cut, oxide_cut, interface_cut)
self.atoms_intialized = False
self._set_atoms_called = False
def set_atoms(self, atoms, *args):
self.set_atoms_mixin(atoms)
if not self._set_atoms_called:
if smallest_pbc_direction(atoms) < 2.05 * self.max_cutoff:
print('Using image atoms')
self._use_imageatoms()
self._set_atoms_called = True
if hasattr(atoms, 'ghosts'):
atoms.ghosts['assignment'] = np.zeros((0,),dtype=np.int32)
if hasattr(atoms, 'ghosts'):
atoms.ghosts['monolayer'] = np.zeros((0,),dtype=np.int32)
_asap.MetalOxideInterface2.set_atoms(self, atoms, *args)
class Morse(AsapPotential, _asap.Morse):
"""Morse potential.
Parameters
----------
elements : sequence of int
The elements that will be supported, given as atomic numbers.
epsilon : numpy.ndarray or float
The Morse energy parameter, as a 2D array with each dimension equal
to the number of elements; only the lower triangular part is used.
A 1D array obtained by flattening the corresponding 2D array is also
accepted, but this is deprecated and incurs less runtime testing.
If there is only one element, a number may be given.
alpha : numpy.ndarray or float
The Morse decay parameter, in the same format as ``epsilon``.
rmin : numpy.ndarray or float
The Morse equilibrium distance, in the same format as ``epsilon``.
rCut : float, optional
The cutoff distance. A negative value (the default) selects the
built-in default cutoff.
modified : bool, optional
Whether the potential should be shifted so no jump occurs at the
cutoff. The default is ``True``.
verbose : bool, optional
Print information while calculating. Defaults to the global Asap
verbosity.
"""
def __init__(self, elements, epsilon, alpha, rmin, rCut=-1.0, modified=True, verbose=None):
AsapPotential.__init__(self)
self._cpp_class = _asap.Morse
try:
numelements = len(elements)
except TypeError:
numelements = 1
elements = [elements]
epsilon = _ChkLJarray(epsilon, numelements, "epsilon", "Morse")
alpha = _ChkLJarray(alpha, numelements, "alpha", "Morse")
rmin = _ChkLJarray(rmin, numelements, "rmin", "Morse")
if verbose is None:
verbose = _asap.verbose
self.verbose = verbose
_asap.Morse.__init__(self, elements, epsilon, alpha,
rmin, rCut, modified, verbose)
# Disable ase.EMT to prevent catastrophic loss of performance.
ase.calculators.emt.EMT.disabled = "Disabled by loading asap3."
# Register if an OpenKIMcalculator is available
OpenKIMsupported = hasattr(_asap, 'OpenKIMcalculator')