Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for PseudoDojo v0.5 standard/stringent #34

Merged
merged 3 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
python-version: "3.11"

- name: Install dependencies
run: pip install -e .[pre-commit,tests,analysis]
run: pip install -e .[pre-commit,tests,analysis,dev]

- name: Run pre-commit
run: pre-commit run --all-files || ( git status --short; git diff; exit 1 )
Expand Down
75 changes: 75 additions & 0 deletions dev/pseudos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/usr/bin/env python
"""Generate the semicore JSON for a given pseudo family."""
from collections import OrderedDict
import hashlib
from importlib.resources import path
import json

from aiida_pseudo.data.pseudo.upf import UpfData
from rich import print # pylint: disable=redefined-builtin
from rich.progress import track
import typer
from upf_tools import UPFDict

from aiida import load_profile, orm

from aiida_wannier90_workflows.utils.pseudo.data import semicore

load_profile()


def generate_semicore_dict(pseudo: UpfData, semicore_threshold=-1.8) -> dict:
"""Generate the semicore dict for a given pseudo potential."""
upf = UPFDict.from_str(pseudo.get_content())
filename = pseudo.filename
md5_hash = hashlib.md5(pseudo.get_content().encode()).hexdigest()

label_energy_dict = {
wfc["label"]: wfc["pseudo_energy"] for wfc in upf["pswfc"]["chi"]
}

threshold_modifier = {
"2S": -1.0,
"3P": 0.5,
"3D": 0.4,
"4P": 0.7,
"4D": 0.5,
"5P": 0.9,
"5D": 0.8,
}
return {
upf["header"]["element"].rstrip(): {
"filename": filename,
"md5": md5_hash,
"pswfcs": list(label_energy_dict.keys()),
"semicores": [
label
for label, energy in label_energy_dict.items()
if energy < semicore_threshold + threshold_modifier.get(label, 0)
],
}
}


def cli(pseudo_family: str, semicore_threshold: float = -1.8):
"""Generate the semicore JSON for a given pseudo family."""
pseudo_group = orm.load_group(pseudo_family)

semicore_dict = {}

for pseudo in track(
pseudo_group.pseudos.values(),
description=f"Generating semicore dict for {pseudo_family}",
):
semicore_dict.update(generate_semicore_dict(pseudo, semicore_threshold))

with path(semicore, f"{pseudo_family.replace('/', '_')}.json") as semicore_path:
with semicore_path.open("w", encoding="utf8") as handle:
json.dump(OrderedDict(sorted(semicore_dict.items())), handle, indent=4)

print("[bold green]Success:[/] the semicore JSON has been generated at:\n")
print(f" {semicore_path.absolute()}\n")


if __name__ == "__main__":
typer.run(cli)
23 changes: 23 additions & 0 deletions dev/test_pseudos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Test that the data provided for the pseudopotentials is complete and correct."""

from aiida import load_profile, orm

from aiida_wannier90_workflows.utils.pseudo import get_pseudo_orbitals

load_profile()


for family in (
"PseudoDojo/0.4/LDA/SR/standard/upf",
"PseudoDojo/0.4/LDA/SR/stringent/upf",
"PseudoDojo/0.4/PBE/SR/standard/upf",
"PseudoDojo/0.4/PBE/SR/stringent/upf",
"PseudoDojo/0.5/PBE/SR/standard/upf",
"PseudoDojo/0.5/PBE/SR/stringent/upf",
):
print(f"Testing family {family}")
for el, pseudo in orm.load_group(family).pseudos.items():
try:
get_pseudo_orbitals({el: pseudo})
except ValueError as exc:
print(exc)
10 changes: 10 additions & 0 deletions docs/source/developer_guide/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

Developer guide
^^^^^^^^^^^^^^^

Below you can find several guides can come in handy for developers/maintainers of the package.

.. toctree::
:maxdepth: 2

pseudos
78 changes: 78 additions & 0 deletions docs/source/developer_guide/pseudos.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@

Pseudopotentials
^^^^^^^^^^^^^^^^

Running the Wannier90 workflows requires specifying which orbitals you will consider as semicore.
To do this automatically, the pseudo wave functions and which ones are considered semicore are stored in a JSON format for each pseudopotential family in the following directory:


.. code-block::

src/aiida_wannier90_workflows/utils/pseudo/data/semicore

Adding support for more pseudopotentials means adding a new JSON file there where the following is specified for each element in the pseudopotential family:

1. The filename of the pseudopotential.
2. The ``md5`` hash of the file contents.
3. The pseudo wave functions (``pswfcs```) and chosen semicore states (``semicores``).

Here's an example:

.. code-block::

"Ba": {
"filename": "Ba.upf",
"md5": "ecceda5fc736cf81896add41b7758c6c",
"pswfcs": [
"5S",
"5P",
"6S"
],
"semicores": [
"5S",
"5P"
]
},

The ``pswfcs`` are all the labels of the wave functions stored in the pseudo.
These can be found by looking for blocks like this one in the UPF file:

.. code-block::

<PP_CHI.1
type="real"
size="1950"
columns="4"
index="1"
occupation=" 2.000"
pseudo_energy=" -0.2475035028E+01"
label="5S"
l="0" >

.. note::

The above example is only valid for the PseudoDojo pseudopotentials.
Other pseudopotentials may have different formats, and you may need to look for the labels and energies in other parts of the file.

Which of the ``pswfcs`` should be treated as semicore is determined somewhat heuristically.
Simply look at their energy level (``pseudo_energy``), in case it is much lower (an order of magnitude) than other wave functions, they can be considered semicore.

However, which orbitals should be considered semicore is in principle structure-dependent, and can fail in some cases.
It can only be really tested by using the choice and comparing the wannier-interpolated band structure with one directly calculated using Quantum ESPRESSO.

For the PseudoDojo, you can find the pseudopotentials at:

http://www.pseudo-dojo.org/pseudos/

In the `dev` directory at the root of this repository, you can find a script that can help you generate the JSON files for the PseudoDojo pseudopotentials, called `pseudos.py`.
Simply run it with the label of the pseudopotential family you want to generate the JSON file for, e.g.:

.. code-block::

python pseudos.py PseudoDojo/0.4/PBE/SR/standard/upf

This will generate a JSON file in the `src/aiida_wannier90_workflows/utils/pseudo/data/semicore` directory.

.. important::

The script currently only supports the PseudoDojo pseudopotentials.
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Contents
user_guide/index
module_guide/index
cli/index
developer_guide/index

Indices and tables
==================
Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ docs = [
"furo",
"markupsafe"
]
dev = [
"typer[all]",
"upf_tools~=0.1"
]
analysis = ["pandas", "tables", "scikit-learn"]

[project.scripts]
Expand Down
26 changes: 22 additions & 4 deletions src/aiida_wannier90_workflows/utils/pseudo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,17 @@ def get_pseudo_and_cutoff(


def get_pseudo_orbitals(pseudos: ty.Mapping[str, PseudoPotentialData]) -> dict:
"""Get the pseudo wavefunctions contained in the pseudopotential.
"""Get the pseudo wave functions contained in the pseudo potential.

Currently only support the following pseudopotentials installed by `aiida-pseudo`:
1. SSSP/1.1/PBE/efficiency
2. SSSP/1.1/PBEsol/efficiency
* SSSP/1.1/PBE/efficiency
* SSSP/1.1/PBEsol/efficiency
* PseudoDojo/0.4/LDA/SR/standard/upf
* PseudoDojo/0.4/LDA/SR/stringent/upf
* PseudoDojo/0.4/PBE/SR/standard/upf
* PseudoDojo/0.4/PBE/SR/stringent/upf
* PseudoDojo/0.5/PBE/SR/standard/upf
* PseudoDojo/0.5/PBE/SR/stringent/upf
"""
from .data import load_pseudo_metadata

Expand All @@ -63,14 +69,26 @@ def get_pseudo_orbitals(pseudos: ty.Mapping[str, PseudoPotentialData]) -> dict:
pseudo_data.append(
load_pseudo_metadata("semicore/PseudoDojo_0.4_PBE_SR_standard_upf.json")
)
pseudo_data.append(
load_pseudo_metadata("semicore/PseudoDojo_0.4_PBE_SR_stringent_upf.json")
)
pseudo_data.append(
load_pseudo_metadata("semicore/PseudoDojo_0.5_PBE_SR_standard_upf.json")
)
pseudo_data.append(
load_pseudo_metadata("semicore/PseudoDojo_0.5_PBE_SR_stringent_upf.json")
)
pseudo_data.append(
load_pseudo_metadata("semicore/PseudoDojo_0.4_LDA_SR_standard_upf.json")
)
pseudo_data.append(
load_pseudo_metadata("semicore/PseudoDojo_0.4_LDA_SR_stringent_upf.json")
)

pseudo_orbitals = {}
for element in pseudos:
for data in pseudo_data:
if data[element]["md5"] == pseudos[element].md5:
if data.get(element, {}).get("md5", "") == pseudos[element].md5:
pseudo_orbitals[element] = data[element]
break
else:
Expand Down
Loading