Coverage for /builds/hweiske/ase/ase/calculators/espresso.py: 91.18%
68 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-22 11:22 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-22 11:22 +0000
1"""Quantum ESPRESSO Calculator
3Run pw.x jobs.
4"""
7import os
8import warnings
9from pathlib import Path
11from ase.calculators.genericfileio import (BaseProfile, CalculatorTemplate,
12 GenericFileIOCalculator,
13 read_stdout)
14from ase.io import read, write
15from ase.io.espresso import Namelist
17compatibility_msg = (
18 'Espresso calculator is being restructured. Please use e.g. '
19 "Espresso(profile=EspressoProfile(argv=['mpiexec', 'pw.x'])) "
20 'to customize command-line arguments.'
21)
24# XXX We should find a way to display this warning.
25# warn_template = 'Property "%s" is None. Typically, this is because the ' \
26# 'required information has not been printed by Quantum ' \
27# 'Espresso at a "low" verbosity level (the default). ' \
28# 'Please try running Quantum Espresso with "high" verbosity.'
31class EspressoProfile(BaseProfile):
32 def __init__(self, binary, pseudo_dir, **kwargs):
33 super().__init__(**kwargs)
34 self.binary = binary
35 self.pseudo_dir = Path(pseudo_dir)
37 @staticmethod
38 def parse_version(stdout):
39 import re
41 match = re.match(r'\s*Program PWSCF\s*v\.(\S+)', stdout, re.M)
42 assert match is not None
43 return match.group(1)
45 def version(self):
46 try:
47 stdout = read_stdout(self.binary)
48 return self.parse_version(stdout)
49 except FileNotFoundError:
50 warnings.warn(
51 f'The executable {self.binary} is not found on the path'
52 )
53 return None
55 def get_calculator_command(self, inputfile):
56 return [self.binary, '-in', inputfile]
59class EspressoTemplate(CalculatorTemplate):
60 _label = 'espresso'
62 def __init__(self):
63 super().__init__(
64 'espresso',
65 ['energy', 'free_energy', 'forces', 'stress', 'magmoms', 'dipole'],
66 )
67 self.inputname = f'{self._label}.pwi'
68 self.outputname = f'{self._label}.pwo'
69 self.errorname = f"{self._label}.err"
71 def write_input(self, profile, directory, atoms, parameters, properties):
72 dst = directory / self.inputname
74 input_data = Namelist(parameters.pop("input_data", None))
75 input_data.to_nested("pw")
76 input_data["control"].setdefault("pseudo_dir", str(profile.pseudo_dir))
78 parameters["input_data"] = input_data
80 write(
81 dst,
82 atoms,
83 format='espresso-in',
84 properties=properties,
85 **parameters,
86 )
88 def execute(self, directory, profile):
89 profile.run(directory, self.inputname, self.outputname,
90 errorfile=self.errorname)
92 def read_results(self, directory):
93 path = directory / self.outputname
94 atoms = read(path, format='espresso-out')
95 return dict(atoms.calc.properties())
97 def load_profile(self, cfg, **kwargs):
98 return EspressoProfile.from_config(cfg, self.name, **kwargs)
100 def socketio_parameters(self, unixsocket, port):
101 return {}
103 def socketio_argv(self, profile, unixsocket, port):
104 if unixsocket:
105 ipi_arg = f'{unixsocket}:UNIX'
106 else:
107 ipi_arg = f'localhost:{port:d}' # XXX should take host, too
108 return profile.get_calculator_command(self.inputname) + [
109 '--ipi',
110 ipi_arg,
111 ]
114class Espresso(GenericFileIOCalculator):
115 def __init__(
116 self,
117 *,
118 profile=None,
119 command=GenericFileIOCalculator._deprecated,
120 label=GenericFileIOCalculator._deprecated,
121 directory='.',
122 parallel_info=None,
123 parallel=True,
124 **kwargs,
125 ):
126 """
127 All options for pw.x are copied verbatim to the input file, and put
128 into the correct section. Use ``input_data`` for parameters that are
129 already in a dict, all other ``kwargs`` are passed as parameters.
131 Accepts all the options for pw.x as given in the QE docs, plus some
132 additional options:
134 input_data: dict
135 A flat or nested dictionary with input parameters for pw.x
136 pseudopotentials: dict
137 A filename for each atomic species, e.g.
138 ``{'O': 'O.pbe-rrkjus.UPF', 'H': 'H.pbe-rrkjus.UPF'}``.
139 A dummy name will be used if none are given.
140 kspacing: float
141 Generate a grid of k-points with this as the minimum distance,
142 in A^-1 between them in reciprocal space. If set to None, kpts
143 will be used instead.
144 kpts: (int, int, int), dict, or BandPath
145 If kpts is a tuple (or list) of 3 integers, it is interpreted
146 as the dimensions of a Monkhorst-Pack grid.
147 If ``kpts`` is set to ``None``, only the Γ-point will be included
148 and QE will use routines optimized for Γ-point-only calculations.
149 Compared to Γ-point-only calculations without this optimization
150 (i.e. with ``kpts=(1, 1, 1)``), the memory and CPU requirements
151 are typically reduced by half.
152 If kpts is a dict, it will either be interpreted as a path
153 in the Brillouin zone (*) if it contains the 'path' keyword,
154 otherwise it is converted to a Monkhorst-Pack grid (**).
155 (*) see ase.dft.kpoints.bandpath
156 (**) see ase.calculators.calculator.kpts2sizeandoffsets
157 koffset: (int, int, int)
158 Offset of kpoints in each direction. Must be 0 (no offset) or
159 1 (half grid offset). Setting to True is equivalent to (1, 1, 1).
162 .. note::
163 Set ``tprnfor=True`` and ``tstress=True`` to calculate forces and
164 stresses.
166 .. note::
167 Band structure plots can be made as follows:
170 1. Perform a regular self-consistent calculation,
171 saving the wave functions at the end, as well as
172 getting the Fermi energy:
174 >>> input_data = {<your input data>}
175 >>> calc = Espresso(input_data=input_data, ...)
176 >>> atoms.calc = calc
177 >>> atoms.get_potential_energy()
178 >>> fermi_level = calc.get_fermi_level()
180 2. Perform a non-self-consistent 'band structure' run
181 after updating your input_data and kpts keywords:
183 >>> input_data['control'].update({'calculation':'bands',
184 >>> 'restart_mode':'restart',
185 >>> 'verbosity':'high'})
186 >>> calc.set(kpts={<your Brillouin zone path>},
187 >>> input_data=input_data)
188 >>> calc.calculate(atoms)
190 3. Make the plot using the BandStructure functionality,
191 after setting the Fermi level to that of the prior
192 self-consistent calculation:
194 >>> bs = calc.band_structure()
195 >>> bs.reference = fermi_energy
196 >>> bs.plot()
198 """
200 if command is not self._deprecated:
201 raise RuntimeError(compatibility_msg)
203 if label is not self._deprecated:
204 import warnings
206 warnings.warn(
207 'Ignoring label, please use directory instead', FutureWarning
208 )
210 if 'ASE_ESPRESSO_COMMAND' in os.environ and profile is None:
211 import warnings
213 warnings.warn(compatibility_msg, FutureWarning)
215 template = EspressoTemplate()
216 super().__init__(
217 profile=profile,
218 template=template,
219 directory=directory,
220 parallel_info=parallel_info,
221 parallel=parallel,
222 parameters=kwargs,
223 )