""" Atomic could be placed at the atomic site. """
from ..common.decorators import cached_property
import copy
[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
@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):
"""
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._check_n_electrons()
[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
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()
[docs]
def copy(self):
return copy.copy(self)
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]
@classmethod
def to_atomic_type(cls, value):
if isinstance(value, cls):
return value
return cls(value)
[docs]
def is_vacuum(self):
return self.atomic_number == 0