""" This file contains SPRKKRAtoms - an enhanced version of Atoms to be used
with SPRKKR """
from ase import Atoms
from ..bindings.spglib import SpacegroupInfo
import numpy as np
from ..sprkkr.sites import Site
from ..common.misc import numpy_index
from typing import List, Union
[docs]
class SPRKKRAtoms(Atoms):
""" ASE Atoms object extended by the data necessary for SPR-KKR calculations """
sites_array_name = 'sprkkr_sites_data'
[docs]
def __init__(self, *args, symmetry=True, potential=None, **kwargs):
"""
Creates SPRKKRAtoms atoms
Parameters
----------
*args: list
The positionals arguments of ase.Atoms.__init__
symmetry: bool
The symmetry will be computed when the sites property will be initialized.
I.e., the by-symmetry-equal atomic sites will share the same sites object.
**kwargs: dict
The named arguments of ase.Atoms.__init__
"""
self._init(symmetry, potential)
super().__init__(*args, **kwargs)
[docs]
def _init(self, symmetry=True, potential=None):
""" The initialization of the additional (not-in-ASE) properties. To be used
by constructor and by promote_ase_atoms"""
self._symmetry = symmetry
self._potential = potential
self._regions = {}
@property
def regions(self):
return self._regions
[docs]
def add_region(self, region):
"""
Add a region of a given name
"""
self._regions[region.name] = region
region.set_atoms(self, False)
[docs]
def remove_region(self, name):
del self._regions[name]
[docs]
def set_regions(self, regions:List):
self._regions = { r.name: r for r in regions }
for region in self._regions.values():
region.set_atoms(self)
@property
def symmetry(self):
"""
Whether the sites property is/will be generated using symmetry, i.e.
whether the Sites objects in the sites property will be shared among
symmetric atomic sites.
"""
return self._symmetry
@symmetry.setter
def symmetry(self, value):
"""
Recomputes the sites with enabled/disabled symmetry if the value of the property
has changed.
"""
if self._symmetry == value:
return
self._symmetry = value
try:
self.get_array(SPRKKRAtoms.sites_array_name)
self._init_sites()
except KeyError:
pass
[docs]
def compute_sites_symmetry(self, consider_old=False, symmetry_precision=1e-5):
""" SPRKKR has some properties shared by all by-symmetry-equal sites.
This method initializes _sites property, that holds these properties:
makes identical all the atoms on the "symmetry identical positions" with
the same atomic number.
The method is called automatically when the sites property is firstly accessed.
The effect of the method is the nearly same as setting the symmetry property.
However, setting the symmetry property on an 'already symmetrized' object has
no effect, while this methods always recompute the sites property.
Parameters
----------
spacegroup: Spacegroup
If not None, the given spacegroup is used for determining the symmetry,
instead of the one determined by cell geometry.
atomic_numbers: [ int ]
Atomic numbers used to determine the spacegroup (if it is not given) to compute
the symmetry. The atomic numbers can be ''virtual'', just to denote the equivalence
of the sites.
The array should have the same length as the number of atoms in the unit cell.
If None, self.symbols are used.
consider_old: bool
If True, and self.sites is not None, the non-symmetry-equivalent sites won't
be equivalent in the newly computed symmetry.
symmetry_precision: float
A threshold for spatial error for the symmetry computing. See spglib.get_spacegroup
"""
self._symmetry = True
return SPRKKRAtoms._init_sites(**locals())
[docs]
def _init_sites(self, consider_old=False, symmetry_precision=1e-5):
""" See compute_sites_symmetry - this metod does just the same, but it does not set the symmetry property.
All the hard work: finding the sites symmetry and distinguishing which sites are equivalent
is done in SpacegroupInfo.from_atoms. Here are only the sites created according to the
infomation obtained from the method.
"""
sg_info = SpacegroupInfo.from_atoms(self, consider_old=consider_old, precision=symmetry_precision)
sites = np.empty(len(self), dtype=object)
occupation = self.info.get('occupancy', {})
used = set()
try:
old_sites=self.get_array(SPRKKRAtoms.sites_array_name)
except KeyError:
old_sites=None
if sg_info.spacegroup:
uniq, umap = np.unique(sg_info.equivalent_sites.mapping, return_inverse=True)
for i in range(len(uniq)):
index = umap == i
if old_sites is not None:
# first non-none of the given index
possible = (i for i in old_sites[index] if i)
try:
site = next(filter(None, possible), None)
if site in used:
site = site.copy()
elif site:
used.add(site)
except StopIteration:
site = None
else:
site = None
if not site:
symbol = self.symbols[ numpy_index(umap,i)]
occ = None
for ai in np.where(index)[0]:
occ = occupation.get(ai, None) or \
occupation.get(str(ai), None)
if occ:
symbol = occ
break
site = Site(self, symbol)
sites[index] = site
self.sites = sites
else:
for i in range(len(self)):
if old_sites is not None and old_sites[i]:
site=old_sites[i]
if site in used:
site = site.copy()
else:
used.add(site)
else:
symbol = occupation.get(i, None) or \
occupation.get(str(i), None) or \
self.symbols[i]
site = Site(self, symbol)
sites[i] = site
self.set_sites(sites, sg_info)
return sites
[docs]
def cancel_sites_symmetry(self):
""" Cancel the use of symmetry in the structure, i.e., makes the Site object
uniqe (not shared) for each atomic site.
Calling this method is nearly equivalent to setting the symmetry property
to False, however, this method always recompute the sites object, while
setting symmetry=False recomputes the sites property only if it was previously
set to True.
"""
self._symmetry = False
self._init_sites()
return self.sites
@property
def sites(self):
""" The sites property holds all the information for the SPR-KKR package:
atomic types (including number of semicore and valence electrons),
occupancy, symmetries, meshes...
Some of the properties are stored in the ASE atoms properties
(e.g. occupancy, atomic symbol), however, ASE is not able to hold them
all and/or to describe fully the SPR-KKR options; thus, these properties
are hold in this array.
The changes made on this array are reflected (as is possible)
to the ASE properties, but the opposite does not hold - to reflect the changes
in these properties please create a new Atoms object with given properties.
"""
try:
return self.get_array(SPRKKRAtoms.sites_array_name, copy=False)
except KeyError:
return self._init_sites()
@sites.setter
def sites(self, v):
""" Set the sites property and updatei/clear all the other dependent
properties (symbols, occupancy, spacegroup_info) according to the sites. """
self.set_sites(v)
[docs]
def are_sites_inited(self):
return SPRKKRAtoms.sites_array_name in self.arrays
[docs]
def set_sites(self, sites:np.ndarray, spacegroup_info:Union[SpacegroupInfo, bool, None]=None):
""" Set the sites property and update all other dependent
properties (symbols, occupancy) according to the sites.
Unlike ``sites`` setter, this method allow also set the spacegoup_info
containing the computed informations about the symmetry.
Parameters
----------
sites
The array of the :class:`ase2sprkkr.sprkkr.sites.Site` objects.
sg_info
Information, about the symmetry.
If None is given, no info is available, the symmetry will be determined if needed.
If False, there is no symmetry.
If True, retain the current symmetry.
"""
an = np.zeros(len(sites), dtype=int)
occ = {}
for i,j in enumerate(sites):
occ[i] = j.occupation.as_dict
an[i] = j.occupation.primary_atomic_number
self.set_atomic_numbers(an)
self.info['occupancy'] = occ
if spacegroup_info is True:
# retain the current symmetry
pass
elif spacegroup_info is False:
self.info['spacegroup_info'] = SpacegroupInfo(None)
elif spacegroup_info:
self.info['spacegroup_info'] = spacegroup_info
elif 'spacegroup_info' in self.info:
# None have been given as spacegroup_info argument, delete any information about the symmetry
del self.info['spacegroup_info']
self.set_array(SPRKKRAtoms.sites_array_name, sites)
@property
def spacegroup_info(self):
if not 'spacegroup_info' in self.info:
self._init_sites()
return self.info['spacegroup_info']
def __getitem__(self, i):
out = super().__getitem__(i)
if isinstance(out, Atoms) and 'spacegroup_info' in out.info:
del out.info['spacegroup_info']
return out
@property
def potential(self):
if self._potential is None:
self._potential = potentials.Potential.from_atoms(self)
return self._potential
[docs]
def reset_sprkkr_potential(self):
for i in self.sites:
i.reset()
if self._potential:
self._potential.reset(update_atoms=False)
self._potential.set_from_atoms()
[docs]
def extend(self, other):
out = super().extend(other)
if self.symmetry:
try:
self.get_array(SPRKKRAtoms.sites_array_name, copy=False)
except KeyError:
return out
self._init_sites(consider_old=True)
return out
[docs]
def copy(self):
out = super().copy()
SPRKKRAtoms.promote_ase_atoms(out)
if self.are_sites_inited():
out.set_sites(Site.copy_sites(out.sites), True)
return out
def __add__(self, other):
if self.are_sites_inited():
SPRKKRAtoms.promote_ase_atoms(other)
other.sites
out=super().__add__(other)
out.set_sites(Site.copy_sites(out.sites), True)
return out
def __iadd__(self, other):
if self.are_sites_inited():
SPRKKRAtoms.promote_ase_atoms(other)
other.sites
return super().__iadd__(other)
# at the last - to avoid circular imports
from ..potentials import potentials # NOQA: E402