Source code for ase.calculators.espresso

"""Quantum ESPRESSO Calculator

Run pw.x jobs.
"""


import os
import warnings

from ase.calculators.genericfileio import (
    BaseProfile,
    CalculatorTemplate,
    GenericFileIOCalculator,
    read_stdout,
)
from ase.io import read, write
from ase.io.espresso import Namelist

compatibility_msg = (
    'Espresso calculator is being restructured.  Please use e.g. '
    "Espresso(profile=EspressoProfile(argv=['mpiexec', 'pw.x'])) "
    'to customize command-line arguments.'
)


# XXX We should find a way to display this warning.
# warn_template = 'Property "%s" is None. Typically, this is because the ' \
#                 'required information has not been printed by Quantum ' \
#                 'Espresso at a "low" verbosity level (the default). ' \
#                 'Please try running Quantum Espresso with "high" verbosity.'


class EspressoProfile(BaseProfile):
    configvars = {'pseudo_dir'}

    def __init__(self, command, pseudo_dir, **kwargs):
        super().__init__(command, **kwargs)
        # not Path object to avoid problems in remote calculations from Windows
        self.pseudo_dir = str(pseudo_dir)

    @staticmethod
    def parse_version(stdout):
        import re

        match = re.match(r'\s*Program PWSCF\s*v\.(\S+)', stdout, re.M)
        assert match is not None
        return match.group(1)

    def version(self):
        stdout = read_stdout(self._split_command)
        return self.parse_version(stdout)

    def get_calculator_command(self, inputfile):
        return ['-in', inputfile]


class EspressoTemplate(CalculatorTemplate):
    _label = 'espresso'

    def __init__(self):
        super().__init__(
            'espresso',
            ['energy', 'free_energy', 'forces', 'stress', 'magmoms', 'dipole'],
        )
        self.inputname = f'{self._label}.pwi'
        self.outputname = f'{self._label}.pwo'
        self.errorname = f"{self._label}.err"

    def write_input(self, profile, directory, atoms, parameters, properties):
        dst = directory / self.inputname

        input_data = Namelist(parameters.pop("input_data", None))
        input_data.to_nested("pw")
        input_data["control"].setdefault("pseudo_dir", str(profile.pseudo_dir))

        parameters["input_data"] = input_data

        write(
            dst,
            atoms,
            format='espresso-in',
            properties=properties,
            **parameters,
        )

    def execute(self, directory, profile):
        profile.run(directory, self.inputname, self.outputname,
                    errorfile=self.errorname)

    def read_results(self, directory):
        path = directory / self.outputname
        atoms = read(path, format='espresso-out')
        return dict(atoms.calc.properties())

    def load_profile(self, cfg, **kwargs):
        return EspressoProfile.from_config(cfg, self.name, **kwargs)

    def socketio_parameters(self, unixsocket, port):
        return {}

    def socketio_argv(self, profile, unixsocket, port):
        if unixsocket:
            ipi_arg = f'{unixsocket}:UNIX'
        else:
            ipi_arg = f'localhost:{port:d}'  # XXX should take host, too
        return profile.get_calculator_command(self.inputname) + [
            '--ipi',
            ipi_arg,
        ]


[docs] class Espresso(GenericFileIOCalculator): def __init__( self, *, profile=None, command=GenericFileIOCalculator._deprecated, label=GenericFileIOCalculator._deprecated, directory='.', **kwargs, ): """ All options for pw.x are copied verbatim to the input file, and put into the correct section. Use ``input_data`` for parameters that are already in a dict. input_data: dict A flat or nested dictionary with input parameters for pw.x pseudopotentials: dict A filename for each atomic species, e.g. ``{'O': 'O.pbe-rrkjus.UPF', 'H': 'H.pbe-rrkjus.UPF'}``. A dummy name will be used if none are given. kspacing: float Generate a grid of k-points with this as the minimum distance, in A^-1 between them in reciprocal space. If set to None, kpts will be used instead. kpts: (int, int, int), dict, or BandPath If kpts is a tuple (or list) of 3 integers, it is interpreted as the dimensions of a Monkhorst-Pack grid. If ``kpts`` is set to ``None``, only the Γ-point will be included and QE will use routines optimized for Γ-point-only calculations. Compared to Γ-point-only calculations without this optimization (i.e. with ``kpts=(1, 1, 1)``), the memory and CPU requirements are typically reduced by half. If kpts is a dict, it will either be interpreted as a path in the Brillouin zone (*) if it contains the 'path' keyword, otherwise it is converted to a Monkhorst-Pack grid (**). (*) see ase.dft.kpoints.bandpath (**) see ase.calculators.calculator.kpts2sizeandoffsets koffset: (int, int, int) Offset of kpoints in each direction. Must be 0 (no offset) or 1 (half grid offset). Setting to True is equivalent to (1, 1, 1). """ if command is not self._deprecated: raise RuntimeError(compatibility_msg) if label is not self._deprecated: warnings.warn( 'Ignoring label, please use directory instead', FutureWarning ) if 'ASE_ESPRESSO_COMMAND' in os.environ and profile is None: warnings.warn(compatibility_msg, FutureWarning) template = EspressoTemplate() super().__init__( profile=profile, template=template, directory=directory, parameters=kwargs, )