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 List, Tuple, Union

from aiida.orm import StructureData

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

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

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 """ import numpy as np 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_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)