Skip to content

Commit

Permalink
Add the PseudoDojoFamily
Browse files Browse the repository at this point in the history
The Pseudo Dojo project provides multiple pseudopotential family
configurations for the functionals LDA, PBE and PBEsol. In addition, it
also provides families with various treatments for relativistic effects
and the pseudopotentials come in various formats, including PSP8, PSML,
UPF and JthXML. Just like the SSSP project, Pseudo Dojo provides
recommended cutoffs for plane waves and the charge density.

A new pseudopotential family class `PseudoDojoFamily` is added that
implements the logic to define valid configurations and how to retrieve
the pseudopotentials and recommended cutoffs that come with it.

Currently, certain configurations that are in principle valid, as in
they correspond to an existing family, are commented out. This is
because they are currently inconsistent. Either cutoffs are missing or
the md5 listed in the metadata does not match that of the included
pseudopotential files. Once these problems are addressed on the Pseudo
Dojo source, they can be uncommented such that they become available for
installation through this plugin.

Any family can be installed through `aiida-pseudo install pseudo-dojo`
which exposes options to select the desired configuration.
  • Loading branch information
zooks97 authored and sphuber committed Dec 9, 2020
1 parent 2f4f416 commit fd050a9
Show file tree
Hide file tree
Showing 11 changed files with 611 additions and 13 deletions.
2 changes: 1 addition & 1 deletion aiida_pseudo/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
click_completion.init()

from .root import cmd_root
from .install import cmd_install, cmd_install_family, cmd_install_sssp
from .install import cmd_install, cmd_install_family, cmd_install_sssp, cmd_install_pseudo_dojo
from .list import cmd_list
from .show import cmd_show
90 changes: 90 additions & 0 deletions aiida_pseudo/cli/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,93 @@ def cmd_install_sssp(version, functional, protocol, traceback):
family.set_cutoffs({'normal': cutoffs})

echo.echo_success(f'installed `{label}` containing {family.count()} pseudo potentials')


@cmd_install.command('pseudo-dojo')
@options.VERSION(type=click.Choice(['0.3', '0.4', '1.0']), default='0.4')
@options.FUNCTIONAL(type=click.Choice(['PBE', 'PBEsol', 'pw']), default='PBE')
@options.RELATIVISTIC(type=click.Choice(['SR', 'SR3plus', 'FR']), default='SR')
@options.PROTOCOL(type=click.Choice(['standard', 'stringent']), default='standard')
@options.PSEUDO_FORMAT(type=click.Choice(['psp8', 'upf', 'psml', 'jthxml']), default='psp8')
@options.DEFAULT_STRINGENCY(type=click.Choice(['low', 'normal', 'high']), default='normal')
@options.TRACEBACK()
@decorators.with_dbenv()
def cmd_install_pseudo_dojo(version, functional, relativistic, protocol, pseudo_format, default_stringency, traceback):
"""Install a PseudoDojo configuration.
The PseudoDojo configuration will be automatically downloaded from pseudo-dojo.org to create a new
`PseudoDojoFamily` subclass instance based on the specified pseudopotential format.
"""
# pylint: disable=too-many-locals,too-many-arguments,too-many-statements
import requests

from aiida.common.files import md5_file
from aiida.orm import Group, QueryBuilder
from aiida_pseudo import __version__
from aiida_pseudo.data.pseudo import JthXmlData, Psp8Data, PsmlData, UpfData
from aiida_pseudo.groups.family import PseudoDojoConfiguration, PseudoDojoFamily

from .utils import attempt, create_family_from_archive

pseudo_type_mapping = {
'jthxml': JthXmlData,
'psp8': Psp8Data,
'psml': PsmlData,
'upf': UpfData,
}

try:
pseudo_type = pseudo_type_mapping[pseudo_format]
except KeyError:
echo.echo_critical(f'{pseudo_format} is not a valid PseudoDojo pseudopotential format')

configuration = PseudoDojoConfiguration(version, functional, relativistic, protocol, pseudo_format)
label = PseudoDojoFamily.format_configuration_label(configuration)
description = 'PseudoDojo v{} {} {} {} {} installed with aiida-pseudo v{}'.format(*configuration, __version__)

if configuration not in PseudoDojoFamily.valid_configurations:
echo.echo_critical('{} is not a valid PseudoDojo configuration'.format(*configuration))

if QueryBuilder().append(PseudoDojoFamily, filters={'label': label}).first():
echo.echo_critical(f'{PseudoDojoFamily.__name__}<{label}> is already installed')

with tempfile.TemporaryDirectory() as dirpath:

url_archive = PseudoDojoFamily.get_url_archive(label)
url_metadata = PseudoDojoFamily.get_url_metadata(label)

filepath_archive = os.path.join(dirpath, 'archive.tgz')
filepath_metadata = os.path.join(dirpath, 'metadata.tgz')

with attempt('downloading selected pseudo potentials archive... ', include_traceback=traceback):
response = requests.get(url_archive)
response.raise_for_status()
with open(filepath_archive, 'wb') as handle:
handle.write(response.content)
handle.flush()
description += f'\nArchive pseudos md5: {md5_file(filepath_archive)}'

with attempt('unpacking archive and parsing pseudos... ', include_traceback=traceback):
family = create_family_from_archive(PseudoDojoFamily, label, filepath_archive, pseudo_type=pseudo_type)

with attempt('downloading selected pseudo potentials metadata archive... ', include_traceback=traceback):
response = requests.get(url_metadata)
response.raise_for_status()
with open(filepath_metadata, 'wb') as handle:
handle.write(response.content)
handle.flush()
description += f'\nPseudo metadata archive md5: {md5_file(filepath_metadata)}'

with attempt('unpacking metadata archive and parsing metadata...', include_traceback=traceback):
md5s, cutoffs = PseudoDojoFamily.parse_djrepos_from_archive(filepath_metadata, pseudo_type=pseudo_type)

for element, md5 in md5s.items():
if family.get_pseudo(element).md5 != md5:
Group.objects.delete(family.pk)
msg = f'md5 of pseudo for element {element} does not match that of the metadata {md5}'
echo.echo_critical(msg)

family.description = description
family.set_cutoffs(cutoffs, default_stringency=default_stringency)

echo.echo_success(f'installed `{label}` containing {family.count()} pseudo potentials')
40 changes: 36 additions & 4 deletions aiida_pseudo/cli/params/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,50 @@
from aiida.cmdline.params.options import OverridableOption
from .types import PseudoPotentialFamilyTypeParam

__all__ = ('VERSION', 'FUNCTIONAL', 'PROTOCOL', 'TRACEBACK', 'FAMILY_TYPE', 'ARCHIVE_FORMAT')
__all__ = (
'VERSION', 'FUNCTIONAL', 'RELATIVISTIC', 'PROTOCOL', 'PSEUDO_FORMAT', 'DEFAULT_STRINGENCY', 'TRACEBACK',
'FAMILY_TYPE', 'ARCHIVE_FORMAT'
)

VERSION = OverridableOption(
'-v', '--version', type=click.STRING, required=False, help='Select the version of the SSSP configuration.'
'-v', '--version', type=click.STRING, required=False, help='Select the version of the installed configuration.'
)

FUNCTIONAL = OverridableOption(
'-f', '--functional', type=click.STRING, required=False, help='Select the functional of the SSSP configuration.'
'-x',
'--functional',
type=click.STRING,
required=False,
help='Select the functional of the installed configuration.'
)

RELATIVISTIC = OverridableOption(
'-r',
'--relativistic',
type=click.STRING,
required=False,
help='Select the type of relativistic effects included in the installed configuration.'
)

PROTOCOL = OverridableOption(
'-p', '--protocol', type=click.STRING, required=False, help='Select the protocol of the SSSP configuration.'
'-p', '--protocol', type=click.STRING, required=False, help='Select the protocol of the installed configuration.'
)

PSEUDO_FORMAT = OverridableOption(
'-f',
'--pseudo-format',
type=click.STRING,
required=True,
help='Select the pseudopotential file format of the installed configuration.'
)

DEFAULT_STRINGENCY = OverridableOption(
'-s',
'--default-stringency',
type=click.STRING,
required=False,
help='Select the default stringency level for the installed configuration. See the documentation for valid '
'options.'
)

TRACEBACK = OverridableOption(
Expand Down
12 changes: 7 additions & 5 deletions aiida_pseudo/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,18 @@ def attempt(message, exception_types=Exception, include_traceback=False):
echo.echo_highlight(' [OK]', color='success', bold=True)


def create_family_from_archive(cls, label, filepath_archive, fmt=None):
def create_family_from_archive(cls, label, filepath_archive, fmt=None, pseudo_type=None):
"""Construct a new pseudo family instance from a tar.gz archive.
.. warning:: the archive should not contain any subdirectories, but just the pseudo potential files.
:param cls: the class to use, e.g. ``SsspFamily``
:param cls: the pseudopotential family class to use, e.g. ``SsspFamily``
:param label: the label for the new family
:param filepath: absolute filepath to the .tar.gz archive containing the pseudo potentials.
:param filepath: optional absolute filepath to the .json file containing the pseudo potentials metadata.
:param filepath_archive: absolute filepath to the .tar.gz archive containing the pseudo potentials
:param fmt: the format of the archive, if not specified will attempt to guess based on extension of ``filepath``
:param pseudo_type: subclass of ``PseudoPotentialData`` to be used for the parsed pseudos. If not specified and
the family only defines a single supported pseudo type in ``_pseudo_types`` then that will be used otherwise
a ``ValueError`` is raised.
:return: newly created family
:raises OSError: if the archive could not be unpacked or pseudos in it could not be parsed into a family
"""
Expand All @@ -55,7 +57,7 @@ def create_family_from_archive(cls, label, filepath_archive, fmt=None):
raise OSError(f'failed to unpack the archive `{filepath_archive}`: {exception}') from exception

try:
family = cls.create_from_folder(dirpath, label)
family = cls.create_from_folder(dirpath, label, pseudo_type=pseudo_type)
except ValueError as exception:
raise OSError(f'failed to parse pseudos from `{dirpath}`: {exception}') from exception

Expand Down
Empty file removed aiida_pseudo/common/__init__.py
Empty file.
1 change: 1 addition & 0 deletions aiida_pseudo/common/units.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
"""Module with constants for unit conversions."""

RY_TO_EV = 13.6056917253 # Taken from `qe_tools.constants` v2.0
HA_TO_EV = RY_TO_EV * 2.0
3 changes: 2 additions & 1 deletion aiida_pseudo/groups/family/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# pylint: disable=undefined-variable
"""Module with group plugins to represent pseudo potential families."""
from .pseudo import *
from .pseudo_dojo import *
from .sssp import *

__all__ = (pseudo.__all__ + sssp.__all__)
__all__ = (pseudo.__all__ + pseudo_dojo.__all__ + sssp.__all__)
Loading

0 comments on commit fd050a9

Please sign in to comment.