Skip to content

Commit

Permalink
Add the HubbardStructureData data plugin (#849)
Browse files Browse the repository at this point in the history
Quantum ESPRESSO implements a new syntax for Hubbard parameters starting
from `v7.1` onwards, see the following URL for details:

    https://gitlab.com/QEF/q-e/-/commit/39a77cecc02e6a43ca33e

The `pw.x` now needs a new `CARD` in the input file.
The following new modules are added:

- `common.hubbard`: Contains a code, structure agnostic data class
    describing Hubbard information, such as parameters, projectors and
    formulation.
- `data.hubbard_structure`: Contains a new code agnostic data plugin,
    the `HubbardStructureData`, for storing Hubbard information along
    with its associated `StructureData` in the provenance graph.
- `utils.hubbard`: A specific, dedicated QuantumESPRESSO utility module
    for handling and manipulating `HubbardStructureData` nodes.

The new data class and plugin implement Hubbard information in a code
agnostic manner, i.e. not specific to Quantum ESPRESSO, in the hope that
they can be shared with plugins for other codes that also support
Hubbard corrections, and they have no constraints for our particular
plugin implementation. 

The conversion of the abstract Hubbard parameters to Quantum ESPRRESSO
input is handled through the `utils.hubbard` module.

Co-authored-by: Marnik Bercx <mbercx@gmail.com>
Co-authored-by: Sebastiaan Huber <mail@sphuber.net>
  • Loading branch information
3 people committed Apr 6, 2023
1 parent 56fdb57 commit 355020c
Show file tree
Hide file tree
Showing 15 changed files with 1,315 additions and 4 deletions.
4 changes: 3 additions & 1 deletion pyproject.toml
Expand Up @@ -28,6 +28,7 @@ dependencies = [
'importlib_resources',
'jsonschema',
'numpy',
'pydantic',
'packaging',
'qe-tools~=2.0',
'xmlschema~=1.2,>=1.2.5'
Expand Down Expand Up @@ -86,6 +87,7 @@ aiida-quantumespresso = 'aiida_quantumespresso.cli:cmd_root'

[project.entry-points.'aiida.data']
'quantumespresso.force_constants' = 'aiida_quantumespresso.data.force_constants:ForceConstantsData'
'quantumespresso.hubbard_structure' = 'aiida_quantumespresso.data.hubbard_structure:HubbardStructureData'

[project.entry-points.'aiida.parsers']
'quantumespresso.cp' = 'aiida_quantumespresso.parsers.cp:CpParser'
Expand Down Expand Up @@ -150,7 +152,7 @@ ignore = [
]

[tool.pylint.master]
load-plugins = ['pylint_aiida']
load-plugins = ['pylint_aiida','pylint.extensions.no_self_use']

[tool.pylint.format]
max-line-length = 120
Expand Down
8 changes: 8 additions & 0 deletions src/aiida_quantumespresso/calculations/__init__.py
Expand Up @@ -15,7 +15,9 @@
from aiida.plugins import DataFactory
from qe_tools.converters import get_parameters_from_cell

from aiida_quantumespresso.data.hubbard_structure import HubbardStructureData
from aiida_quantumespresso.utils.convert import convert_input_to_namelist_entry
from aiida_quantumespresso.utils.hubbard import HubbardUtils

from .base import CalcJob
from .helpers import QEInputValidationError
Expand Down Expand Up @@ -685,6 +687,10 @@ def _generate_PWCPinputdata(cls, parameters, settings, pseudos, structure, kpoin
kpoints_card = ''.join(kpoints_card_list)
del kpoints_card_list

# HUBBARD CARD
hubbard_card = HubbardUtils(structure).get_hubbard_card() if isinstance(structure, HubbardStructureData) \
else None

# =================== NAMELISTS AND CARDS ========================
try:
namelists_toprint = settings.pop('NAMELISTS')
Expand Down Expand Up @@ -728,6 +734,8 @@ def _generate_PWCPinputdata(cls, parameters, settings, pseudos, structure, kpoin
if cls._use_kpoints:
inputfile += kpoints_card
inputfile += cell_parameters_card
if hubbard_card is not None:
inputfile += hubbard_card

# Generate additional cards bases on input parameters and settings that are subclass specific
tail = cls._generate_PWCP_input_tail(input_params=input_params, settings=settings)
Expand Down
2 changes: 2 additions & 0 deletions src/aiida_quantumespresso/common/__init__.py
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
"""Common modules to be shared with other aiida plugins."""
161 changes: 161 additions & 0 deletions src/aiida_quantumespresso/common/hubbard.py
@@ -0,0 +1,161 @@
# -*- coding: utf-8 -*-
"""Utility class and functions for HubbardStructureData."""
# pylint: disable=no-name-in-module, invalid-name
from typing import List, Literal, Tuple

from pydantic import BaseModel, conint, constr, validator

__all__ = ('HubbardParameters', 'Hubbard')


class HubbardParameters(BaseModel):
"""Class for describing onsite and intersite Hubbard interaction parameters.
.. note: allowed manifold formats are:
* {N}{L} (2 characters)
* {N1}{L1}-{N2}{L2} (5 characters)
N = quantum number (1,2,3,...); L = orbital letter (s,p,d,f,g,h)
"""

atom_index: conint(strict=True, ge=0)
"""Atom index in the abstract structure."""

atom_manifold: constr(strip_whitespace=True, to_lower=True, min_length=2, max_length=5)
"""Atom manifold (syntax is `3d`, `3d-2p`)."""

neighbour_index: conint(strict=True, ge=0)
"""Neighbour index in the abstract structure."""

neighbour_manifold: constr(strip_whitespace=True, to_lower=True, min_length=2, max_length=5)
"""Atom manifold (syntax is `3d`, `3d-2p`)."""

translation: Tuple[conint(strict=True), conint(strict=True), conint(strict=True)]
"""Translation vector referring to the neighbour atom, (3,) shape list of ints."""

value: float
"""Value of the Hubbard parameter, expessed in eV."""

hubbard_type: Literal['Ueff', 'U', 'V', 'J', 'B', 'E2', 'E3']
"""Type of the Hubbard parameters used (`Ueff`, `U`, `V`, `J`, `B`, `E2`, `E3`)."""

@validator('atom_manifold', 'neighbour_manifold') # cls is mandatory to use
def check_manifolds(cls, value): # pylint: disable=no-self-argument, no-self-use
"""Check the validity of the manifold input.
Allowed formats are:
* {N}{L} (2 characters)
* {N1}{L1}-{N2}{L2} (5 characters)
N = quantum number (1,2,3,...); L = orbital letter (s,p,d,f,g,h)
"""
length = len(value)
if length not in [2, 5]:
raise ValueError(f'invalid length ``{length}``. Only 2 or 5.')
if length == 2:
if not value[0] in [str(_ + 1) for _ in range(6)]:
raise ValueError(f'invalid quantum number {value[0]}')
if not value[1] in ['s', 'p', 'd', 'f', 'h']:
raise ValueError(f'invalid manifold symbol {value[1]}')
if length == 5:
if not value[2] == '-':
raise ValueError(f'the separator {value[0]} is not allowed. Only `-`')
if not value[3] in [str(_ + 1) for _ in range(6)]:
raise ValueError(f'the quantum number {value[0]} is not correct')
if not value[4] in ['s', 'p', 'd', 'f', 'h']:
raise ValueError(f'the manifold number {value[1]} is not correct')
return value

def to_tuple(self) -> Tuple[int, str, int, str, float, Tuple[int, int, int], str]:
"""Return the parameters as a tuple.
The parameters have the following order:
* atom_index
* atom_manifold
* neighbour_index
* neighbour_manifold
* value
* translationr
* hubbard_type
"""
return (
self.atom_index, self.atom_manifold, self.neighbour_index, self.neighbour_manifold, self.value,
self.translation, self.hubbard_type
)

@staticmethod
def from_tuple(hubbard_parameters: Tuple[int, str, int, str, float, Tuple[int, int, int], str]):
"""Return a :meth:`~aiida_quantumespresso.common.hubbard.HubbardParameters` instance from a list.
The parameters within the list must have the following order:
* atom_index
* atom_manifold
* neighbour_index
* neighbour_manifold
* value
* translation
* hubbard_type
"""
keys = [
'atom_index',
'atom_manifold',
'neighbour_index',
'neighbour_manifold',
'value',
'translation',
'hubbard_type',
]
return HubbardParameters(**dict(zip(keys, hubbard_parameters)))


class Hubbard(BaseModel):
"""Class for complete description of Hubbard interactions."""

parameters: List[HubbardParameters]
"""List of :meth:`~aiida_quantumespress.common.hubbard.HubbardParameters`."""

projectors: Literal['atomic',
'ortho-atomic',
'norm-atomic',
'wannier-functions',
'pseudo-potentials',
] = 'ortho-atomic'
"""Name of the projectors used. Allowed values are:
'atomic', 'ortho-atomic', 'norm-atomic', 'wannier-functions', 'pseudo-potentials'."""

formulation: Literal['dudarev', 'liechtenstein'] = 'dudarev'
"""Hubbard formulation used. Allowed values are: 'dudarev', `liechtenstein`."""

def to_list(self) -> List[Tuple[int, str, int, str, float, Tuple[int, int, int], str]]:
"""Return the Hubbard `parameters` as a list of lists.
The parameters have the following order within each list:
* atom_index
* atom_manifold
* neighbour_index
* neighbour_manifold
* value
* translation
* hubbard_type
"""
return [params.to_tuple() for params in self.parameters]

@staticmethod
def from_list(
parameters: List[Tuple[int, str, int, str, float, Tuple[int, int, int], str]],
projectors: str = 'ortho-atomic',
formulation: str = 'dudarev',
):
"""Return a :meth:`~aiida_quantumespresso.common.hubbard.Hubbard` instance from a list of tuples.
Each list must contain the hubbard parameters in the following order:
* atom_index
* atom_manifold
* neighbour_index
* neighbour_manifold
* value
* translation
* hubbard_type
"""
parameters = [HubbardParameters.from_tuple(value) for value in parameters]
return Hubbard(parameters=parameters, projectors=projectors, formulation=formulation)

0 comments on commit 355020c

Please sign in to comment.