Source code for ase2sprkkr.sprkkr.occupations

""" More atomic types can be on one site. """
from __future__ import annotations

import numpy as np
from .atomic_types import AtomicType
from ..common.misc import OrderedDict
from typing import Dict
from collections.abc import Iterable
from .sites import Site


[docs]class Occupation: """ Occupation of the atomic site, given by AtomicType : value The value determine the probability, that a given atomic type will be found on a given place (which can be used e.g. for computing alloys). """
[docs] def __init__(self, dct:Dict[AtomicType|str, float], site:Site|None=None, update_atoms=False): self._site = site self.set(dct, update_atoms)
[docs] def copy(self, site:Site|None=None) -> Occupation: """ Create a copy of the object, associated with a given site. """ return Occupation({ a.copy(): v for a,v in self._occupation.items() }, site)
[docs] def set(self, dct: Dict[AtomicType | str, float ], update_atoms=True) -> None: """ Set (replace) the occupation data. The method automatically updates the symbols, atomic numbers and the occupancy property (if exists) of the underlying Atoms object. """ if isinstance(dct, (int, str, AtomicType)): dct = {dct : 1.0} if hasattr(dct, 'items'): iterator = dct.items() #dict else: iterator = dct self._occupation = OrderedDict( (AtomicType.to_atomic_type(i), j) for i,j in iterator ) self._normalize() if update_atoms: self._update_atoms()
[docs] def items(self): """ dict.items() like enumeration """ return self._occupation.items()
def __repr__(self): return f"Occupation {self._occupation}" def __str__(self): return f"Occupation {self._occupation}" def __iter__(self): return iter(self._oc/cupation)
[docs] def _update_atoms(self): if self._site: self._site.update_atoms()
def __iter__(self): return iter(self._occupation)
[docs] def _find_key(self, name): if isinstance(name, int): return list(self._occupation.keys())[name] if isinstance(name, AtomicType): return name for i in self: if i.symbol == name: return i raise KeyError(f"No {name} in the occupation")
def atomic_types(self) -> Iterable[AtomicType]: """ Returns the atomic types that can be present on the site. """ return self._occupation.keys()
[docs] def atomic_type(self, name: str|int|AtomicType) -> AtomicType | None: """ Find the corresponding atomic type according to the provided argument. Parameters ---------- name The identification of the atomic type. If it is integer, returns the n-th atomic type (according to the orderd supplied when the occupation is set). If it is a string, the first atomic type of given chemical element is returned If it is AtomicType, it is returned "as is" """ return self._find_key(name)
def __getitem__(self, name): name = self._find_key(name) return self._occupation[name]
[docs] def replace_type(self, name:str|int|AtomicType, to:str|AtomicType): """ Replace the given atomic type (see :meth:`atomic_type<ase2sprkkr.sprkkr.occupations.Occupation.atomic_type>`, how it can be identified) by the new one (given either by AtomicType or by its chemical symbol) """ key = self._find_key(name) to = AtomicType.to_atomic_type(to) self._occupation = OrderedDict( (k if k is not key else to, v) for k,v in self._occupation.items() )
[docs] def clean(self): """ Remove all items with zero probability. """ self._occupation = OrderedDict( (k, v) for k,v in self._occupation.items() if v > 0 )
def __setitem__(self, name, value): try: name = self._find_key(name) self._normalize(1. - value, name) self._occupation[name] = value except KeyError: if isinstance(name, int): raise ValueError(f"Cannot add an atomic type using integer key. Please use a string (chemical symbol).") self.add(name, value) self._update_atoms() @property def primary_atomic_type(self): """ Return the atomic type. If there are more atoms on the site, return the one with the largest occupation """ m = 0. prim = None for at,occ in self._occupation.items(): if occ > m and at.atomic_number > 0: m = occ prim = at return prim @property def primary_atomic_number(self): """ Return the atomic number of the atom at the site. If there are more atoms on the site, return the "main one". """ primary = self.primary_atomic_type return primary.atomic_number if primary else 0 @property def primary_symbol(self): """ Return the chemical symbol of the atom at the site. If there are more atoms on the site, return the "main one". """ primary = self.primary_atomic_type return primary.symbol if primary else 'X' def __len__(self): return len(self._occupation)
[docs] def add(self, name, value=None): """ Add atom to the site """ if value is None: value = 1. / (len(self) + 1.) self._normalize(1. - value) self._occupation[AtomicType.to_atomic_type(name)] = value self._update_atoms()
def __delitem__(self, name): name = self._find_key(name) del self._occupation[name] self._normalize()
[docs] def _normalize(self, to=1., except_from=None): """ Normalizes occupation so the sum will be equal to the value 'to' (by default to 1.). If there are no None values, all the values are multiplied by the same number to make their sum equal to to. If there are None values, the remainder to the 'to' value is equally divided among them. Parameters ---------- value : float Desired value except_from: AtomicType Skip the given atom during normalizing """ suma = 0. none = [] for i in self._occupation: if i is except_from: continue v = self._occupation[i] if v is None: none.append(i) else: suma+=v if suma >= to and none: for i in none: self._occupation[i] = 0. none = [] if suma == to: return if none: for i in none: self._occupation[i] = (to - suma) / len(none) else: ratio = to / suma for i in self._occupation: if i != except_from: self._occupation[i] *= ratio
@property def as_dict(self): occ = {} for at in self: if at.atomic_number == 0: continue occ[at.symbol] = occ.get(at.symbol, 0) + self[at] return occ @as_dict.setter def as_dict(self, x): self.set(x) @property def total_occupation(self): return sum(self._occupation.values())
[docs] @staticmethod def to_occupation(occupation, site): """ Create an occupation object associated with the given sites object """ if not isinstance(occupation, Occupation): occupation = Occupation(occupation, site) elif site is not None: occupation = Occupation.copy(occupation, site) return occupation
[docs] def check(self): if not np.isclose(sum(self),1.0): raise ValueError("Total occupation of the site should be equal to one")
[docs] def to_tuple(self): return zip(self.keys(), self.values())
[docs] def atomic_types(self): return self._occupation.keys()