Source code for aiida_quantumespresso.utils.hubbard

# -*- coding: utf-8 -*-
"""Utility class for handling the :class:`aiida_quantumespresso.data.hubbard_structure.HubbardStructureData`."""
# pylint: disable=no-name-in-module
from itertools import product
import os
from typing import Dict, List, Tuple, Union

from aiida.orm import StructureData
import numpy as np

from aiida_quantumespresso.common.hubbard import Hubbard
from aiida_quantumespresso.data.hubbard_structure import HubbardStructureData

__all__ = (
    'HubbardUtils',
    'initialize_hubbard_parameters',
    'get_supercell_atomic_index',
    'get_index_and_translation',
    'get_hubbard_indices',
    'is_intersite_hubbard',
    'max_number_of_neighbours',
)

QE_TRANSLATIONS = list(list(item) for item in product((-1, 0, 1), repeat=3))
first = QE_TRANSLATIONS.pop(13)
QE_TRANSLATIONS.insert(0, first)
QE_TRANSLATIONS = tuple(tuple(item) for item in QE_TRANSLATIONS)


[docs]class HubbardUtils: """Utility class for handling `HubbardStructureData` for QuantumESPRESSO.""" def __init__( self, hubbard_structure: HubbardStructureData, ): """Set a the `HubbardStructureData` to manipulate.""" if isinstance(hubbard_structure, HubbardStructureData): self._hubbard_structure = hubbard_structure else: raise ValueError('input is not of type `HubbardStructureData') @property
[docs] def hubbard_structure(self) -> HubbardStructureData: """Return the HubbardStructureData.""" return self._hubbard_structure
[docs] def get_hubbard_card(self) -> str: """Return QuantumESPRESSO `HUBBARD` input card for `pw.x`.""" hubbard = self.hubbard_structure.hubbard hubbard_parameters = hubbard.parameters sites = self.hubbard_structure.sites natoms = len(sites) lines = [f'HUBBARD\t{hubbard.projectors}\n'] for param in hubbard_parameters: atom_i = sites[param.atom_index].kind_name atom_j = sites[param.neighbour_index].kind_name index_i = param.atom_index + 1 # QE indices start from 1 index_j = get_supercell_atomic_index(param.neighbour_index, natoms, param.translation) + 1 man_i = param.atom_manifold man_j = param.neighbour_manifold value = param.value if hubbard.formulation not in ['dudarev', 'liechtenstein']: raise ValueError(f'Hubbard formulation {hubbard.formulation} is not implemented.') if hubbard.formulation == 'liechtenstein': line = f'{pre}\t{atom_i}-{man_i} \t{value}' # This variable is to meet QE implementation. If intersite interactions # (+V) are present, onsite parameters might not be relabelled by the ``hp.x`` # code, causing a subsequent ``pw.x`` calculation to crash. That is, # we need to avoid writing "U Co-3d 5.0", but instead "V Co-3d Co-3d 1 1 5.0". is_intersite = is_intersite_hubbard(hubbard=hubbard) if hubbard.formulation == 'dudarev': if param.hubbard_type == 'J': pre = 'J' elif not is_intersite and atom_i == atom_j and param.atom_manifold == param.neighbour_manifold: pre = 'U' else: pre = 'V' if pre in ['U', 'J']: line = f'{pre}\t{atom_i}-{man_i}\t{value}' else: line = f'{pre}\t{atom_i}-{man_i}\t{atom_j}-{man_j}\t{index_i}\t{index_j}\t{value}' line += '\n' if line not in lines: lines.append(line) return ' '.join(lines)
[docs] def parse_hubbard_dat(self, filepath: Union[str, os.PathLike]): """Parse the `HUBBARD.dat` of QuantumESPRESSO file associated to the current structure. This function is needed for parsing the HUBBARD.dat file generated in a `hp.x` calculation. .. note:: overrides current Hubbard information. :param filepath: the filepath of the *HUBBARD.dat* to parse """ self.hubbard_structure.clear_hubbard_parameters() natoms = len(self.hubbard_structure.sites) hubbard_data = [] with open(filepath, encoding='utf-8') as handle: lines = handle.readlines() for line in lines: if line.strip().split()[0] != '#': hubbard_data.append(list(line.strip().split())) projectors = hubbard_data.pop(0)[1] if projectors.startswith(('(', '[', '{')): projectors = projectors[1:-1] # Samples of parsed array are: # ['U', 'Co-3d', '6.0'] # ['V', 'Co-3d', 'O-2p', '1', '4', '6.0'] for data in hubbard_data: if data[0] == 'U': manifold = data[1].split('-') index = int(self.hubbard_structure._get_one_kind_index(manifold.pop(0))[0]) # pylint: disable=protected-access manifold = '-'.join(manifold) args = (index, manifold, index, manifold, float(data[2]), (0, 0, 0), 'U') else: manifolds = [] for i in [1, 2]: manifold = data[i].split('-') manifold.pop(0) # removing atom name manifolds.append('-'.join(manifold)) # -1 because QE index starts from 1 index_i, _ = get_index_and_translation(int(data[3]) - 1, natoms) index_j, tra = get_index_and_translation(int(data[4]) - 1, natoms) args = (index_i, manifolds[0], index_j, manifolds[1], float(data[5]), tuple(tra), data[0]) self.hubbard_structure.append_hubbard_parameter(*args) hubbard = self.hubbard_structure.hubbard hubbard.projectors = projectors self.hubbard_structure.hubbard = hubbard
[docs] def get_hubbard_file(self) -> str: """Return QuantumESPRESSO ``parameters.in`` data for ``pw.x```.""" hubbard = self.hubbard_structure.hubbard hubbard_parameters = hubbard.parameters sites = self.hubbard_structure.sites natoms = len(sites) if not hubbard.formulation == 'dudarev': raise ValueError('only `dudarev` formulation is implemented') card = '#\tAtom 1\tAtom 2\tHubbard V (eV)\n' for param in hubbard_parameters: index_i = param.atom_index + 1 # QE indices start from 1 index_j = get_supercell_atomic_index(param.neighbour_index, natoms, param.translation) + 1 value = param.value line = f'\t{index_i}\t{index_j}\t{value}' line += '\n' card += line return card
[docs] def reorder_atoms(self): """Reorder the atoms with with the kinds in the right order necessary for an ``hp.x`` calculation. An ``HpCalculation`` which restarts from a completed ``PwCalculation``, requires that the all Hubbard atoms appear first in the atomic positions card of the ``PwCalculation`` input file. This order is based on the order of the kinds in the structure. So a suitable structure has all Hubbard kinds in the begining of kinds list. .. note:: overrides current ``HubbardStructureData`` """ from copy import deepcopy structure = self.hubbard_structure # current reordered = structure.clone() # to be set at the end reordered.clear_kinds() hubbard = structure.hubbard.model_copy() parameters = hubbard.to_list() sites = structure.sites indices = get_hubbard_indices(hubbard=hubbard) hubbard_kinds = list(set(sites[index].kind_name for index in indices)) hubbard_kinds.sort(reverse=False) ordered_sites = [] # We define a map from ``index`` to ``site specifications``. We need the complete # specification, as we will loose track of the index ordering with the following shuffle. # The ``index`` are needed for re-indexing later the hubbard parameters. index_map = {index: site.get_raw() for index, site in enumerate(sites) if site.kind_name in hubbard_kinds} while hubbard_kinds: hubbard_kind = hubbard_kinds.pop() hubbard_sites = [s for s in sites if s.kind_name == hubbard_kind] remaining_sites = [s for s in sites if not s.kind_name == hubbard_kind] ordered_sites.extend(hubbard_sites) sites = remaining_sites # Extend the current site list with the remaining non-hubbard sites ordered_sites.extend(sites) for site in ordered_sites: if site.kind_name not in reordered.get_kind_names(): kind = structure.get_kind(site.kind_name) reordered.append_kind(kind) reordered.append_site(site) reordered_parameters = [] # Reordered site map, to match with raw sites of ``index_map`` site_map = [site.get_raw() for site in reordered.sites] for parameter in parameters: new_parameter = list(deepcopy(parameter)) new_parameter[0] = site_map.index(index_map[parameter[0]]) # atom index new_parameter[2] = site_map.index(index_map[parameter[2]]) # neighbour index reordered_parameters.append(new_parameter) # making sure we keep track of the other info as well args = (reordered_parameters, hubbard.projectors, hubbard.formulation) hubbard = hubbard.from_list(*args) reordered.hubbard = hubbard self._hubbard_structure = reordered
[docs] def is_to_reorder(self) -> bool: """Return whether the atoms should be reordered for an ``hp.x`` calculation.""" indices = get_hubbard_indices(self.hubbard_structure.hubbard) indices.sort() return indices != list(range(len(indices)))
[docs] def get_hubbard_for_supercell(self, supercell: StructureData, thr: float = 1e-3) -> HubbardStructureData: """Return the ``HubbbardStructureData`` for a supercell. .. note:: the two structure need to be commensurate (no rigid rotations) .. warning:: **always check** that the energy calculation of a pristine supercell structure obtained through this method is the same as the unitcell (within numerical noise) :returns: a new ``HubbbardStructureData`` with all the mapped Hubbard parameters """ uc_pymat = self.hubbard_structure.get_pymatgen_structure() sc_pymat = supercell.get_pymatgen_structure() uc_positions = uc_pymat.cart_coords # positions in Cartesian coordinates sc_positions = sc_pymat.cart_coords uc_cell = uc_pymat.lattice.matrix uc_cell_inv = np.linalg.inv(uc_cell) hubbard = self.hubbard_structure.hubbard sc_hubbard_parameters = [] # Dumb, but fairly safe, way to map all the hubbard parameters. # The idea is to map for each interaction in unitcell the # correspective one in supercell matching all the positions. for param in hubbard.parameters: # i -> atom_index | j -> neighbour_index uc_i_position = uc_positions[param.atom_index] uc_j_position = uc_positions[param.neighbour_index] sc_i_indices, sc_j_index, sc_i_translations = [], [], [] # Each atom in supercell is matched if a unitcell # translation vector is found. for i, position in enumerate(sc_positions): translation = np.dot(position - uc_i_position, uc_cell_inv) translation_int = np.rint(translation) if np.all(np.isclose(translation, translation_int, thr)): sc_i_translations.append(translation_int.tolist()) sc_i_indices.append(i) translation = np.dot(position - uc_j_position, uc_cell_inv) translation_int = np.rint(translation) if np.all(np.isclose(translation, translation_int, thr)): uc_j_translation = np.array(translation_int) sc_j_index = i # The position of the neighbour must be still translated; # This might happen in the supercell itself, or outside, thus # we neeed to recompute its position and its translation vector in supercell. sc_j_position = sc_positions[sc_j_index] for sc_i_index, sc_i_translation in zip(sc_i_indices, sc_i_translations): j_position = sc_j_position + np.dot(sc_i_translation - uc_j_translation + param.translation, uc_cell) local_site = sc_pymat.get_sites_in_sphere(pt=j_position, r=thr)[0] # pymatgen PeriodicSite sc_hubbard_parameter = [ int(sc_i_index), param.atom_manifold, int(local_site.index), # otherwise the class validator complains param.neighbour_manifold, param.value, np.array(local_site.image, dtype=np.int64).tolist(), # otherwise the class validator complains param.hubbard_type, ] sc_hubbard_parameters.append(sc_hubbard_parameter) args = (sc_hubbard_parameters, hubbard.projectors, hubbard.formulation) new_hubbard = Hubbard.from_list(*args) return HubbardStructureData.from_structure(structure=supercell, hubbard=new_hubbard)
[docs] def get_interacting_pairs(self) -> Dict[str, List[str]]: """Return tuple of kind name interaction pairs. :returns: dictionary of onsite kinds with a list of V kinds """ pairs_dict = dict() sites = self.hubbard_structure.sites parameters = self.hubbard_structure.hubbard.parameters onsite_indices = [p.atom_index for p in parameters if p.atom_index == p.neighbour_index] for index in onsite_indices: name = sites[index].kind_name if name not in pairs_dict: pairs_dict[name] = [] for parameter in parameters: onsite_name = sites[parameter.atom_index].kind_name neigh_name = sites[parameter.neighbour_index].kind_name if onsite_name in pairs_dict and onsite_name != neigh_name: if not neigh_name in pairs_dict[onsite_name]: pairs_dict[onsite_name].append(neigh_name) return pairs_dict
[docs] def get_pairs_radius( self, onsite_index: int, neighbours_names: List[str], number_of_neighbours: int, radius_max: float = 7.0, thr: float = 1.0e-2, ) -> Tuple[float, float]: """Return the minimum and maximum radius of the first neighbours of the onsite site. :param onsite_index: index in the structure of the onsite Hubbard atom :param neighbours_names: kind names of the neighbours :param number_of_neighbours: number of neighbours coming to select :param radius_max: maximum radius (in Angstrom) to use for looking for neighbours :param thr: threshold (in Angstrom) for defining the shells :return: (radius min +thr, radius max -thr) defining the shells containing only the first neighbours """ rmin = 0 pymat = self.hubbard_structure.get_pymatgen_structure() _, neigh_indices, _, distances = pymat.get_neighbor_list(sites=[pymat[onsite_index]], r=radius_max) sort = np.argsort(distances) neigh_indices = neigh_indices[sort] distances = distances[sort] count = 0 for i in range(len(neigh_indices)): # pylint: disable=consider-using-enumerate index = i if self.hubbard_structure.sites[neigh_indices[i]].kind_name in neighbours_names: rmin = max(rmin, distances[i]) count += 1 if count == number_of_neighbours: break rmax = distances[index + 1] if np.abs(rmax - rmin) < thr: rmax = rmin + 2 * rmin return rmin + thr, rmax - thr
[docs] def get_intersites_radius( self, nn_finder: str = 'crystal', nn_inputs: Union[Dict, None] = None, radius_max: float = 7.0, thr: float = 1.0e-2, **_, ) -> float: """Return the radius (in Angstrom) for intersites from nearest neighbour finders. It peforms a nearest neighbour analysis (via pymatgen modules) to find the first inersite neighbours for all the onsite atoms. A radius is returned which can be used to run an ``hp.x`` calculation. Such radius defines a shell including only the first neighbours of each onsite Hubbard atom. :param nn_finder: string defining the nearest neighbour finder; options are: * `crystal`: use :class:`pymatgen.analysis.local_env.CrystalNN` * `voronoi`: use :class:`pymatgen.analysis.local_env.VoronoiNN` :param nn_inputs: inputs for the nearest neighbours finder; when None, standard inputs are used to find geometric first neighbours (recommended) :param radius_max: max radius where to look for neighbouring atoms, in Angstrom :param thr: threshold (in Angstrom) for defining the shells :return: radius defining the shell containing only the first neighbours """ import warnings from pymatgen.analysis.local_env import CrystalNN, VoronoiNN rmin, rmax = 0.0, radius_max if nn_finder not in ['crystal', 'voronoi']: raise ValueError('`nn_finder` must be either `cyrstal` or `voronoi`') if nn_inputs is None: if nn_finder == 'crystal': nn_inputs = {'distance_cutoffs': None, 'x_diff_weight': 0, 'porous_adjustment': False} if nn_finder == 'voronoi': nn_inputs = {'tol': 0.1, 'cutoff': radius_max} voronoi = CrystalNN(**nn_inputs) if nn_finder == 'crystal' else VoronoiNN(**nn_inputs) # pylint: disable=unexpected-keyword-arg sites = self.hubbard_structure.sites name_to_specie = {kind.name: kind.symbol for kind in self.hubbard_structure.kinds} pymat = self.hubbard_structure.get_pymatgen_structure() pairs = self.get_interacting_pairs() for i, site in enumerate(sites): if site.kind_name in pairs: neigh_species = voronoi.get_cn_dict(pymat, i) # e.g. {'O': 4, 'S': 2, ...} number_of_neighs = 0 for neigh_name in pairs[site.kind_name]: specie = name_to_specie[neigh_name] if specie in neigh_species: number_of_neighs += neigh_species[specie] neigh_species.pop(specie) # avoid 'duplicating' same specie but different (kind) name rmin_, rmax_ = self.get_pairs_radius(i, pairs[site.kind_name], number_of_neighs, radius_max, thr) rmin = max(rmin_, rmin) # we want the largest to include them all rmax = min(rmax_, rmax) # we want the smallest to check whether such radius exist if rmin > rmax: warnings.warn('A common radius seems to not exist! Try lowering `thr`.') return min(rmin, rmax)
[docs] def get_intersites_list( self, nn_finder: str = 'crystal', nn_inputs: Union[Dict, None] = None, radius_max: float = 7.0, **_, ) -> List[Tuple[int, int, Tuple[int, int, int]]]: """Return the list of intersites from nearest neighbour finders. It peforms a nearest neighbour analysis (via pymatgen modules) to find the first inersite neighbours for all the onsite atoms. A list is returned with all the nearest neighbours providing all the information about the couples indices and the associated trasnaltion vector. Also on-site information is included. :param nn_finder: string defining the nearest neighbour finder; options are: * `crystal`: use :class:`pymatgen.analysis.local_env.CrystalNN` * `voronoi`: use :class:`pymatgen.analysis.local_env.VoronoiNN` :param nn_inputs: inputs for the nearest neighbours finder; when None, standard inputs are used to find geometric first neighbours (recommended) :param radius_max: max radius where to look for neighbouring atoms, in Angstrom :param thr: threshold (in Angstrom) for defining the shells :return: list of lists, each having (atom index, neighbouring index, translation vector) """ from pymatgen.analysis.local_env import CrystalNN, VoronoiNN if nn_finder not in ['crystal', 'voronoi']: raise ValueError('`nn_finder` must be either `cyrstal` or `voronoi`') if nn_inputs is None: if nn_finder == 'crystal': nn_inputs = {'distance_cutoffs': None, 'x_diff_weight': 0, 'porous_adjustment': False} if nn_finder == 'voronoi': nn_inputs = {'tol': 0.1, 'cutoff': radius_max} voronoi = CrystalNN(**nn_inputs) if nn_finder == 'crystal' else VoronoiNN(**nn_inputs) # pylint: disable=unexpected-keyword-arg sites = self.hubbard_structure.sites name_to_specie = {kind.name: kind.symbol for kind in self.hubbard_structure.kinds} pymat = self.hubbard_structure.get_pymatgen_structure() pairs = self.get_interacting_pairs() neigh_list = [] for i, site in enumerate(sites): # pylint: disable=too-many-nested-blocks if site.kind_name in pairs: neigh_species = voronoi.get_cn_dict(pymat, i) # e.g. {'O': 4, 'S': 2, ...} neigh_list.append([i, i, (0, 0, 0)]) for neigh_name in pairs[site.kind_name]: try: # we want an API that allows for specifying neighbours that are not present specie = name_to_specie[neigh_name] count = 0 if specie in neigh_species: _, neigh_indices, images, distances = pymat.get_neighbor_list( sites=[pymat[i]], r=radius_max ) sort = np.argsort(distances) neigh_indices = neigh_indices[sort] images = images[sort] for index, image in zip(neigh_indices, images): if pymat[index].specie.name == specie: neigh_list.append([ i, int(index), tuple(np.array(image, dtype=np.int64).tolist()), ]) count += 1 if count >= neigh_species[specie]: break except KeyError: pass return neigh_list
[docs] def get_max_number_of_neighbours( self, nn_finder: str = 'crystal', nn_inputs: Union[Dict, None] = None, radius_max: float = 7.0, **_, ) -> list: """Return the maximum number of nearest neighbours, aslo counting the non-interacting ones. It peforms a nearest neighbour analysis (via pymatgen modules) to find the first inersite neighbours for all the onsite atoms. A list is returned with all the nearest neighbours providing all the information about the couples indices and the associated trasnaltion vector. Also on-site information is included. :param nn_finder: string defining the nearest neighbour finder; options are: * `crystal`: use :class:`pymatgen.analysis.local_env.CrystalNN` * `voronoi`: use :class:`pymatgen.analysis.local_env.VoronoiNN` :param nn_inputs: inputs for the nearest neighbours finder; when None, standard inputs are used to find geometric first neighbours (recommended) :param radius_max: max radius where to look for neighbouring atoms, in Angstrom :param thr: threshold (in Angstrom) for defining the shells :return: list of lists, each having (atom index, neighbouring index, translation vector) """ from pymatgen.analysis.local_env import CrystalNN, VoronoiNN if nn_finder not in ['crystal', 'voronoi']: raise ValueError('`nn_finder` must be either `cyrstal` or `voronoi`') if nn_inputs is None: if nn_finder == 'crystal': nn_inputs = {'distance_cutoffs': None, 'x_diff_weight': 0, 'porous_adjustment': False} if nn_finder == 'voronoi': nn_inputs = {'tol': 0.1, 'cutoff': radius_max} voronoi = CrystalNN(**nn_inputs) if nn_finder == 'crystal' else VoronoiNN(**nn_inputs) # pylint: disable=unexpected-keyword-arg sites = self.hubbard_structure.sites pymat = self.hubbard_structure.get_pymatgen_structure() pairs = self.get_interacting_pairs() max_num_of_neighs = 0 for i, site in enumerate(sites): # pylint: disable=too-many-nested-blocks if site.kind_name in pairs: neigh_species = voronoi.get_cn_dict(pymat, i) # e.g. {'O': 4, 'S': 2, ...} max_num_of_neighs = max(max_num_of_neighs, np.sum(list(neigh_species.values()))) return max_num_of_neighs
[docs]def initialize_hubbard_parameters( structure: StructureData, pairs: Dict[str, Tuple[str, float, float, Dict[str, str]]], nn_finder: str = 'crystal', nn_inputs: Union[Dict, None] = None, fold: bool = True, standardize: bool = False, radius_max: float = 7.0, thr: float = 1e-5, use_kinds: bool = True, **_, ) -> HubbardStructureData: """Initialize the on-site and intersite parameters using nearest neighbour finders. It peforms a nearest neighbour analysis (via pymatgen modules) to find the first inersite neighbours for all the onsite atoms. Only the atoms in the `pair`, and that are nearest neighbours from the analysis, are initialized. :param structure: a StructureData instance :param pairs: dictionary of the kind {onsite name: [onsite manifold, onsite value, intersites value, {neighbour name: neighbour manifold}], ...} For example: {'Fe': ['3d', 5.0, 1.0, {'O':'2p', 'O1':'1s', 'Se':'4p'}]} :param nn_finder: string defining the nearest neighbour finder; options are: * `crystal`: use :class:`pymatgen.analysis.local_env.CrystalNN` * `voronoi`: use :class:`pymatgen.analysis.local_env.VoronoiNN` :param nn_inputs: inputs for the nearest neighbours finder; when None, standard inputs are used to find geometric first neighbours (recommended) :param fold: whether to fold in within the cell the atoms :param standardize: whether to standardize the atoms and the cell via spglib (symmetry analysis) :param radius_max: max radius where to look for neighbouring atoms, in Angstrom :param thr: threshold to refold the atoms with crystal coordinates close to 1.0 :param use_kinds: whether to use kinds for initializing the parameters; when False, it initializes all the ``Kinds`` matching the given specie :return: HubbardStructureData with initialized Hubbard parameters """ from aiida.tools.data import spglib_tuple_to_structure, structure_to_spglib_tuple from pymatgen.analysis.local_env import CrystalNN, VoronoiNN from spglib import standardize_cell if nn_finder not in ['crystal', 'voronoi']: raise ValueError('`nn_finder` must be either `cyrstal` or `voronoi`') if nn_inputs is None: if nn_finder == 'crystal': nn_inputs = {'distance_cutoffs': None, 'x_diff_weight': 0, 'porous_adjustment': False} if nn_finder == 'voronoi': nn_inputs = {'tol': 0.1, 'cutoff': radius_max} voronoi = CrystalNN(**nn_inputs) if nn_finder == 'crystal' else VoronoiNN(**nn_inputs) # pylint: disable=unexpected-keyword-arg if not standardize and not fold: hubbard_structure = HubbardStructureData.from_structure(structure=structure) if fold: cell, kind_info, kinds = structure_to_spglib_tuple(structure) cell = list(cell) cell[1] = cell[1] % 1.0 cell[1] = np.where(np.abs(cell[1] - 1.0) < thr, 0.0, cell[1]) folded_struccture = spglib_tuple_to_structure(cell, kind_info, kinds) hubbard_structure = HubbardStructureData.from_structure(structure=folded_struccture) if standardize: cell, kind_info, kinds = structure_to_spglib_tuple(structure) new_cell = standardize_cell(cell) new_structure = spglib_tuple_to_structure(new_cell, kind_info, kinds) hubbard_structure = HubbardStructureData.from_structure(structure=new_structure) sites = hubbard_structure.sites name_to_specie = {kind.name: kind.symbol for kind in hubbard_structure.kinds} pymat = hubbard_structure.get_pymatgen_structure() for i, site in enumerate(sites): # pylint: disable=too-many-nested-blocks site_name = site.kind_name if use_kinds else name_to_specie[site.kind_name] if site_name in pairs: neigh_species = voronoi.get_cn_dict(pymat, i) # e.g. {'O': 4, 'S': 2, ...} onsite = pairs[site.kind_name] hubbard_structure.append_hubbard_parameter(i, onsite[0], i, onsite[0], onsite[1]) for neigh_name in onsite[3]: try: # we want an API that allows for specifying neighbours that are not present specie = name_to_specie[neigh_name] count = 0 if specie in neigh_species: _, neigh_indices, images, distances = pymat.get_neighbor_list(sites=[pymat[i]], r=radius_max) sort = np.argsort(distances) neigh_indices = neigh_indices[sort] images = images[sort] for index, image in zip(neigh_indices, images): if pymat[index].specie.name == specie: hubbard_structure.append_hubbard_parameter( atom_index=i, atom_manifold=onsite[0], neighbour_index=int(index), # otherwise the validator complains neighbour_manifold=onsite[3][neigh_name], value=onsite[2], translation=np.array(image, dtype=np.int64).tolist(), hubbard_type='V', ) count += 1 if count >= neigh_species[specie]: break except KeyError: pass return hubbard_structure
[docs]def get_supercell_atomic_index(index: int, num_sites: int, translation: List[Tuple[int, int, int]]) -> int: """Return the atomic index in 3x3x3 supercell. :param index: atomic index in unit cell :param num_sites: number of sites in structure :param translation: (3,) shape list of int referring to the translated atom in the 3x3x3 supercell :returns: atomic index in supercell standardized with the QuantumESPRESSO loop """ return index + QE_TRANSLATIONS.index(translation) * num_sites
[docs]def get_index_and_translation(index: int, num_sites: int) -> Tuple[int, List[Tuple[int, int, int]]]: """Return the atomic index in unitcell and the associated translation from a 3x3x3 QuantumESPRESSO supercell index. :param index: atomic index :param num_sites: number of sites in structure :returns: tuple (index, (3,) shape list of ints) """ from math import floor number = floor(index / num_sites) # associated supercell number return (index - num_sites * number, QE_TRANSLATIONS[number])
[docs]def get_hubbard_indices(hubbard: Hubbard) -> List[int]: """Return the set list of Hubbard indices.""" atom_indices = {parameters.atom_index for parameters in hubbard.parameters} neigh_indices = {parameters.neighbour_index for parameters in hubbard.parameters} atom_indices.update(neigh_indices) return list(atom_indices)
[docs]def is_intersite_hubbard(hubbard: Hubbard) -> bool: """Return whether `Hubbard` contains intersite interactions (+V).""" couples = [(param.atom_index != param.neighbour_index) for param in hubbard.parameters] return any(couples)
[docs]def max_number_of_neighbours(intersites_list: List[Tuple[int, int]]) -> int: """Return the maximum number of neighbours found. .. note:: it assumes only one onsite parameter is defined per atom index, that means `intra` Hubbard interactions are not defined, such as `V Fe 3d Fe 2p 1 1 1.0` :param intersites_list: list of lists of shape (atom index, neigbours index) """ from collections import Counter first_indices = np.array(intersites_list, dtype='object')[:, 0] counts = Counter(first_indices) return max(counts.values()) - 1 # assuming there is always 1 and only 1 onsite parameter