Source code for ase2sprkkr.sprkkr.atomic_types

""" Atomic could be placed at the atomic site. """

from ..common.decorators import cached_property
import copy
from .radial import RadialPotential, RadialCharge
from .moments import Moments
from typing import Dict


[docs] class AtomicType: """ Atomic type represent a type of atom, that could be placed at the atomic site. It can be either a real chemical element, or vacuum pseudoelement. It also determine the number of electrons and valence electrons. """ _mendeleev_module = None
[docs] @classmethod def to_atomic_type(cls, value, for_mesh=None): if isinstance(value, cls): return value.for_mesh(for_mesh) return cls(value, mesh=for_mesh)
@cached_property def mendeleev(self): if self.atomic_number == 0: raise Exception("Vaccuum (pseudo)atom is not in Mendeleev package database") if not AtomicType._mendeleev_module: try: import mendeleev except ImportError as e: raise ImportError("Cannot import Mendeleev package to guess the atomic type properties") from e AtomicType._mendeleev_module = mendeleev return AtomicType._mendeleev_module.element(self.atomic_number or self.symbol)
[docs] def __init__(self, symbol, atomic_number=None, n_core=None, n_valence=None, n_semicore=None, n_electrons=None, mesh=None): """ Parameters ---------- symbol: str or int Chemical symbol, e.g. Fe, N, .... If int is given, attempt to guess the symbol using third party library is done atomic_number: int Atomic number If it's zero, an attempt to guess it from the chemical symbol is done. n_core: int Number of core electrons If it's zero, an attempt to guess it from the chemical symbol/atomic number is done. n_valence: int Number of valence electrons If it's zero, an attempt to guess it from the chemical symbol/atomic_number is done. n_semicore: int Number of semicore electrons If it's zero, an attempt to guess it from the chemical symbol/atomic_number is done. n_electrons: int """ if isinstance(symbol, int): if atomic_number is not None and atomic_number != symbol: raise ValueError(f'Number of electrons in symbol ({symbol}) and atomic_number ({atomic_number}) differ') atomic_number = symbol symbol = None else: symbol = symbol atomic_number = atomic_number if atomic_number is None and symbol is None: raise ValueError("Unknown atomic type") if symbol == 'Vc' or atomic_number == 0 or \ (symbol == 'X' and atomic_number is None): self._symbol = 'Vc' self._atomic_number = 0 else: if symbol is None: self._atomic_number = atomic_number self._symbol = symbol if symbol is not None else self.mendeleev.symbol else: self._symbol = symbol self._atomic_number = None self._atomic_number = atomic_number if atomic_number is not None else self.mendeleev.atomic_number self._n_electrons = n_electrons self._n_valence = n_valence self._n_core = n_core self._n_semicore = n_semicore self._potential = None self._charge = None self._moments = None self._mesh = mesh self._check_n_electrons()
@property def mesh(self): return self._mesh @mesh.setter def mesh(self, mesh): if self._mesh is mesh: return self._mesh = mesh if self.potential: self.potential.mesh = mesh if self.charge: self.charge.mesh = mesh
[docs] def _check_n_electrons(self): if ( self._n_core is not None and self._n_valence is not None and self._n_semicore is not None and self._n_electrons is not None and self._n_core + self._n_valence + self._n_semicore != self._n_electrons ): raise ValueError(f"""The following atom setup is inconsistent: n_electrons: {self._n_electrons}, n_core: {self._n_core}, n_valence: {self._n_valence}, n_semicore: {self._n_semicore}""")
@property def atomic_number(self): return self._atomic_number
[docs] def _clear_symbol_cache(self): try: del self.mendeleev except AttributeError: pass try: del self.atomic_number except AttributeError: pass try: del self.symbol except AttributeError: pass
@atomic_number.setter def atomic_number(self, v): self._atomic_number = v self._clear_symbol_cache() self._symbol = self.mendeleev.symbol if v else 'Vc' @property def symbol(self): return self._symbol @symbol.setter def symbol(self, v): self._symbol = v self._atomic_number = None self._clear_symbol_cache() self._atomic_number = self.mendeleev.atomic_number if v not in ['X', 'Vc'] else 0 @property def n_electrons(self): if self._n_electrons is not None: return self._n_electrons if self._n_valence is not None and self._n_core is not None and self._n_semicore is not None: return self._n_valence + self._n_core + self._n_semicore return self.atomic_number @n_electrons.setter def n_electrons(self, v): self._n_electrons = v if self._potential: self._potential.z = v self.__check_n_electrons() @property def n_valence(self): if self._n_valence is not None: return self._n_valence if self._n_core is not None: return self.n_electrons - self._n_core if self.n_electrons == 0: return 0 return self.mendeleev.nvalence() @n_valence.setter def n_valence(self, v): self._n_valence = v self.__check_n_electrons() @property def n_core(self): if self._n_core is not None: self._n_core return self.n_electrons - self.n_valence - self.n_semicore @n_core.setter def n_core(self, v): self._n_core = v self.__check_n_electrons() @property def n_semicore(self): if self._n_semicore is not None: return self._n_semicore if self._n_core is not None and self._n_valence is not None: return self.n_electrons - self._n_core - self._n_valence return 0 @n_semicore.setter def n_semicore(self, v): self._n_semicore = v self.__check_n_electrons() def __repr__(self): return f"({self.atomic_number})" if self.symbol == 'X' else self.symbol def __str__(self): return self.__repr__()
[docs] def to_tuple(self): return (self.symbol, self.n_electrons, self.n_core, self.n_valence, self.n_semicore)
[docs] def is_vacuum(self): return self.atomic_number == 0
@property def potential(self): """ The radial potential data of the site """ return self._potential @potential.setter def potential(self, value): if value is None: self._potential = None return if not isinstance(value, RadialPotential): self._potential = RadialPotential(value, self._mesh, self.n_electrons) elif value.z is not None: self._potential = value.copy(self.mesh) self._potential.z = self._n_electrons else: self._potential = value self._potential.z = self._n_electrons @property def charge(self): """ The radial charge data of the site """ return self._charge @charge.setter def charge(self, value): if value is None: self._charge = None return if not isinstance(value, RadialCharge): self._charge = RadialCharge(value, self.mesh) else: self._charge = value.for_mesh(self.mesh) @property def moments(self): """ The moments data of the site """ return self._moments @moments.setter def moments(self, value): if not isinstance(value, Moments): value = Moments(*value) self._moments = value
[docs] def for_mesh(self, mesh): """ Return copy for a given mesh """ if mesh is None: return self smesh = self.mesh if smesh is mesh: return self if smesh is None: self.mesh = mesh return self return self.copy(mesh)
[docs] def copy(self, for_mesh=None): out = copy.copy(self) if out.potential is not None: out.potential = out.potential.copy(for_mesh) if out.charge is not None: out.charge = out.charge.copy(for_mesh) if out.moments is not None: out.moments = out.moments.copy() if for_mesh is not None: out._mesh = for_mesh return out
[docs] def remesh(self, mesh, map:'Dict[AtomicType,AtomicType]'): """ Return the copy for a given mesh, updating the informations in the map of the old: new objects, so the remeshed items will share the meshes. """ if self in map: return map[self] out = map[self] = self.for_mesh(mesh) return out
[docs] def has_converged_data(self): return self.potential and self.charge and self.moments