Source code for ase2sprkkr.bindings.spglib

"""
Wrapper for spglib for computing symmetry of a primitive cell
"""
from __future__ import annotations

from typing import Optional, List, Dict, Tuple
import spglib
from ase import Atoms
from ase.spacegroup import Spacegroup
from ..common.unique_values import UniqueValuesMapping
from ..sprkkr import sprkkr_atoms
import numpy as np


[docs] class SpacegroupInfo: """ Class, that carry information about spacegroup and symmetry of a structure """
[docs] def __init__(self, atoms: sprkkr_atoms.SPRKKRAtoms, spacegroup: Optional[Spacegroup], dataset: Optional[Dict]=None, equivalent_sites: Optional[UniqueValuesMapping]=None): """ Parameters ---------- atoms ASE atoms object desribed by the SpacegroupInfo spacegroup ASE spacegroup object. If None, there is no symmetry in the crystal. dataset SPGLib dataset - a dictionary, containing many informations about the symmetry and spacegroup of the structure. If it is not provided, and ``spacegroup`` is not ``None`` the spacegroup is recomputed to obtain the dataset, if the dataset is requested. equivalent_sites Object, that provides equivalence classes for the atoms. If it is not provided, and ``spacegroup`` is not ``None``, the spacegroup is recomputed to obtain the equivalence equivalent_sites, if the equivalent_sites is requested. """ atoms = sprkkr_atoms.SPRKKRAtoms.promote_ase_atoms(atoms) self.atoms = atoms self.spacegroup = spacegroup self._dataset = dataset self._equivalent_sites = equivalent_sites
def __repr__(self): return str(self.spacegroup) or 'No spacegroup' def __str__(self): return self.__repr__()
[docs] def number(self) -> Optional[int]: """ Returns ------- spacegroup Spacegroup number or None, if there is no spacegroup. """ return self.spacegroup.no if self.spacegroup else None
@property def dataset(self)->Optional[Dict]: if self._dataset is None and self.spacegroup: self.recompute(allow_change=False) return self._dataset @property def equivalent_sites(self)->UniqueValuesMapping: """ Object, that provide equivalence classes for the atoms. If it is not provided, and ``spacegroup`` is not ``None``, the spacegroup is recomputed to obtain the equivalent_sites, if the equivalent_sites is requested. """ if self._equivalent_sites is None: if self._dataset: return UniqueValuesMapping(self._dataset['equivalent_atoms']) elif self.spacegroup: self.recompute(allow_change=False) else: self._equivalent_sites = UniqueValuesMapping(np.arange(len(self.atoms))) return self._equivalent_sites
[docs] def recompute(self, allow_change:bool=True): """ Recompute spacegroup, dataset and equivalent_sites. allow_change If False, the resulting spacegroup have to be the same as the old one. Used, if the spacegroup info have been constructed using a spacegroup without providing spglib dataset. """ if not self.atoms.symmetry: self.spacegroup = None self._dataset = None self._equivalent_sites = UniqueValuesMapping(np.arange(len(self.atoms))) sg, dataset, equivalent_sites = self.compute_spacegroup_info(self.atoms) if not allow_change and ( bool(sg) != bool(self.spacegroup) or (sg and self.spacegroup.no != sg.no)): raise "The stored spacegroup do not correspond to the actual one" self.spacegroup = sg self._dataset = dataset self._equivalent_sites = equivalent_sites
[docs] @staticmethod def compute_spacegroup_info(atoms:Atoms, atomic_numbers : List=None, consider_old : bool=False, precision : float=1e-5, angular_precision:float=1.0, ) -> Tuple[Spacegroup, Dict, UniqueValuesMapping]: """ Return the values needed to create (or update) :class:`Spacegroup` that suits to the atoms' cell structure. For the parameters description, see the ':meth:`from_atoms` method. Returns ------- spacegroup ASE Spacegroup object, describing the spacegroup dataset Dataset, describing symmetry- and spacegroup-related information about the structure, see the `Spglib documentation <https://spglib.github.io/spglib/dataset.html#spglib-dataset>` for the documentation of the members of the `dataset and the `documentation of the C-to-python Spglib binding <https://spglib.github.io/spglib/python-spglib.html>` for the names of the members (which slightly differ from the names in the C structure) equivalent_sites The map of equivalent atoms. """ sg_dataset = None sprkkr_atoms.SPRKKRAtoms.promote_ase_atoms(atoms) if atoms.symmetry: equivalent_sites = possibly_equivalent_sites(atoms, atomic_numbers, consider_old) spositions = atoms.get_scaled_positions() sg_dataset = spglib.get_symmetry_dataset((atoms.get_cell(), spositions, equivalent_sites.mapping), symprec = precision, angle_tolerance = angular_precision) if sg_dataset is None: return None, None, UniqueValuesMapping(np.arange(len(atoms))) spacegroup = Spacegroup(sg_dataset['number']) try: tags = spacegroup.tag_sites(spositions) except AssertionError: tags = np.arange(len(spositions)) equivalent_sites = equivalent_sites.merge(tags) equivalent_sites.normalize(start_from=0) return spacegroup, sg_dataset, equivalent_sites
[docs] @staticmethod def from_atoms(atoms:Atoms, atomic_numbers : List=None, consider_old : bool=False, precision : float=1e-5, angular_precision:float=1.0, ) -> SpacegroupInfo: """ Create :class:`SpacegroupInfo` for given ASE :class:`Atoms` object. Parameters ---------- atoms ASE atoms structure atomic_numbers The atomic numbers can be overriden, e.g. to force (partialy) break the symmetry. The atomic numbers have not to be the real ones, they can be (and are treated) just the labels of equivalence classes. consider_old precision The tolerated unprecision angular_precision The tolerated unprecision in angular coordinate Returns ------- """ out = SpacegroupInfo.compute_spacegroup_info(**locals()) return SpacegroupInfo(atoms, *out)
[docs] def possibly_equivalent_sites(atoms: Atoms, atomic_numbers : List=None, consider_old : bool=False) -> UniqueValuesMapping: """ Return the object that describe equivalence classes of the atoms from the given Atoms object (i.e. in one equivalence class will be the atoms with the same atomic number, occupation, etc.) """ if atomic_numbers is not None: equivalent_sites = UniqueValuesMapping(atomic_numbers) else: equivalent_sites = UniqueValuesMapping(atoms.get_atomic_numbers()) if consider_old: try: equivalent_sites = equivalent_sites.merge(atoms.get_array(atoms.sites_array_name)) except KeyError: pass else: occupation = atoms.info.get('occupancy', {}) if occupation: def gen_occ(): for i in range(len(equivalent_sites)): val = occupation.get(i, None) or \ occupation.get(str(i), None) if val is None: yield val else: yield tuple((k, val[k]) for k in val) equivalent_sites = equivalent_sites.merge(gen_occ()) equivalent_sites.normalize(start_from=0) return equivalent_sites