Source code for wulfric.crystal._atoms

# ================================== LICENSE ===================================
# Wulfric - Cell, Atoms, K-path, visualization.
# Copyright (C) 2023 Andrey Rybakov
#
# e-mail: anry@uv.es, web: adrybakov.com
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
# ================================ END LICENSE =================================
from wulfric._exceptions import FailedToDeduceAtomSpecies
from wulfric.constants._atoms import ATOM_SPECIES
from wulfric.crystal._crystal_validation import validate_atoms

__all__ = ["get_atom_species", "get_atoms_species", "get_unique_names"]


[docs] def get_atom_species(name: str, raise_on_fail=False) -> str: r""" Attempts to identify atom's species based on its name (i.e. Cr1 -> Cr, ...). If no species is identified, then return "X". Parameters ---------- name : str Name of the atom. raise_on_fail : bool, default False Whether to raise an exception if automatic species deduction fails. Returns ------- species : str Species of the atom. Raises ------ FailedToDeduceAtomSpecies If ``raise_on_fail = True`` and automatic species deduction fails. Warnings -------- If ``raise_on_fail = True`` and automatic species deduction fails, then ``RuntimeWarning`` is issued, and atom species is set to "X". See Also -------- get_atoms_species Notes ----- If ``name`` contains several possible atom species of length 2 as substrings, then the species is equal to the first one found. Examples -------- .. doctest:: >>> from wulfric.crystal import get_atom_species >>> get_atom_species("@%^#$") 'X' >>> get_atom_species("Cr") 'Cr' >>> get_atom_species("Cr1") 'Cr' >>> get_atom_species("_3341Cr") 'Cr' >>> get_atom_species("cr") 'Cr' >>> get_atom_species("S") 'S' >>> get_atom_species("Se") 'Se' >>> get_atom_species("Sp") 'S' >>> get_atom_species("123a") 'X' >>> get_atom_species("CrSBr") 'Cr' """ atom_species = "X" for trial_species in ATOM_SPECIES: if trial_species.lower() in name.lower(): atom_species = trial_species # Maximum amount of characters in the atom species # Some 1-character species are parts of some 2-character species (i.e. "Se" and "S") # If species of two characters is found then it is unique, # If species of one character is found, then the search must continue if len(atom_species) == 2: break if atom_species == "X": if raise_on_fail: raise FailedToDeduceAtomSpecies(name=name) else: import warnings warnings.warn( f"Atom species deduction failed for '{name}'. Set species to 'X'", RuntimeWarning, ) return atom_species
[docs] def get_atoms_species(atoms, raise_on_fail=False) -> str: r""" Attempts to identify atoms species based on their names (i.e. Cr1 -> Cr, ...). If no species is identified, then return "X". Parameters ---------- atoms : dict Dictionary with N atoms. Expected keys: * "names" : (N, ) list of str raise_on_fail : bool, default False Whether to raise an exception if automatic species deduction fails. Returns ------- species : str Species of the atom. Raises ------ FailedToDeduceAtomSpecies If ``raise_on_fail = True`` and automatic species deduction fails. Warnings -------- If ``raise_on_fail = True`` and automatic species deduction fails, then ``RuntimeWarning`` is issued, and atom species is set to "X". See Also -------- get_atom_species Notes ----- If ``atoms["names"][i]`` contains several possible atom species of length 2 as substrings, then the species is equal to the first one found. Examples -------- .. doctest:: >>> import wulfric >>> atoms = dict(names=["Fe1", "Fe2", "Cr", "Br3"]) >>> wulfric.crystal.get_atoms_species(atoms) ['Fe', 'Fe', 'Cr', 'Br'] >>> atoms = {"names": ["Cr1", "cr2", "Br3", "S4", "fe5", "Fe6"]} >>> wulfric.crystal.get_atoms_species(atoms) ['Cr', 'Cr', 'Br', 'S', 'Fe', 'Fe'] """ validate_atoms(atoms=atoms, required_keys=["names"], raise_errors=True) return [ get_atom_species(name=name, raise_on_fail=raise_on_fail) for name in atoms["names"] ]
[docs] def get_unique_names(atoms, strategy: str = "all") -> list: r""" Ensures that atoms have unique ``"names"``. If atom names are already unique, then returns ``atoms["names"]``. Parameters ---------- atoms : dict Dictionary with N atoms. Expected keys: * "names" : (N, ) list of str strategy : str, default "all" Strategy for the modification of atom names. Supported strategies are * "all" Add an index to the end of every atom, starting from 1. * "repeated-only" Add an index only to the repeated names, index starts with 1, independently for each repeated group. (See examples) Case-insensitive. Returns ------- unique_names : list of str Unique names of atoms. Raises ------ ValueError If ``strategy`` is not supported. Examples -------- .. doctest:: >>> import wulfric >>> atoms = {"names": ["Cr1", "Cr2", "Br", "Br", "S", "S"]} >>> # Default strategy is "all" >>> wulfric.crystal.get_unique_names(atoms) ['Cr11', 'Cr22', 'Br3', 'Br4', 'S5', 'S6'] >>> atoms = {"names": ["Cr1", "Cr2", "Br", "Br", "S", "S"]} >>> wulfric.crystal.get_unique_names(atoms, strategy="repeated-only") ['Cr1', 'Cr2', 'Br1', 'Br2', 'S1', 'S2'] >>> # Nothing happens if atom names are already unique >>> wulfric.crystal.get_unique_names(atoms) ['Cr1', 'Cr2', 'Br1', 'Br2', 'S1', 'S2'] >>> wulfric.crystal.get_unique_names(atoms, strategy="repeated-only") ['Cr1', 'Cr2', 'Br1', 'Br2', 'S1', 'S2'] """ validate_atoms(atoms=atoms, required_keys=["names"], raise_errors=True) SUPPORTED_STRATEGIES = ["all", "repeated-only"] strategy = strategy.lower() if strategy not in SUPPORTED_STRATEGIES: raise ValueError( f"{strategy} strategy is not supported. Supported are:\n" + ("\n").join([f" * {i}" for i in SUPPORTED_STRATEGIES]) ) unique_names = atoms["names"] names_are_not_unique = not len(unique_names) == len(set(unique_names)) if names_are_not_unique and strategy == "all": unique_names = [f"{name}{i + 1}" for i, name in enumerate(atoms["names"])] if names_are_not_unique and strategy == "repeated-only": counter = {} for name in unique_names: if name not in counter: counter[name] = [1, 1] else: counter[name][1] += 1 for i in range(len(unique_names)): name = unique_names[i] total = counter[name][1] if total > 1: unique_names[i] += str(counter[name][0]) counter[name][0] += 1 return unique_names