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

1"""Quantum ESPRESSO Calculator 

2 

3Run pw.x jobs. 

4""" 

5 

6 

7import os 

8import warnings 

9from pathlib import Path 

10 

11from ase.calculators.genericfileio import (BaseProfile, CalculatorTemplate, 

12 GenericFileIOCalculator, 

13 read_stdout) 

14from ase.io import read, write 

15from ase.io.espresso import Namelist 

16 

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) 

22 

23 

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.' 

29 

30 

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) 

36 

37 @staticmethod 

38 def parse_version(stdout): 

39 import re 

40 

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) 

44 

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 

54 

55 def get_calculator_command(self, inputfile): 

56 return [self.binary, '-in', inputfile] 

57 

58 

59class EspressoTemplate(CalculatorTemplate): 

60 _label = 'espresso' 

61 

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" 

70 

71 def write_input(self, profile, directory, atoms, parameters, properties): 

72 dst = directory / self.inputname 

73 

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)) 

77 

78 parameters["input_data"] = input_data 

79 

80 write( 

81 dst, 

82 atoms, 

83 format='espresso-in', 

84 properties=properties, 

85 **parameters, 

86 ) 

87 

88 def execute(self, directory, profile): 

89 profile.run(directory, self.inputname, self.outputname, 

90 errorfile=self.errorname) 

91 

92 def read_results(self, directory): 

93 path = directory / self.outputname 

94 atoms = read(path, format='espresso-out') 

95 return dict(atoms.calc.properties()) 

96 

97 def load_profile(self, cfg, **kwargs): 

98 return EspressoProfile.from_config(cfg, self.name, **kwargs) 

99 

100 def socketio_parameters(self, unixsocket, port): 

101 return {} 

102 

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 ] 

112 

113 

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. 

130 

131 Accepts all the options for pw.x as given in the QE docs, plus some 

132 additional options: 

133 

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). 

160 

161 

162 .. note:: 

163 Set ``tprnfor=True`` and ``tstress=True`` to calculate forces and 

164 stresses. 

165 

166 .. note:: 

167 Band structure plots can be made as follows: 

168 

169 

170 1. Perform a regular self-consistent calculation, 

171 saving the wave functions at the end, as well as 

172 getting the Fermi energy: 

173 

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() 

179 

180 2. Perform a non-self-consistent 'band structure' run 

181 after updating your input_data and kpts keywords: 

182 

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) 

189 

190 3. Make the plot using the BandStructure functionality, 

191 after setting the Fermi level to that of the prior 

192 self-consistent calculation: 

193 

194 >>> bs = calc.band_structure() 

195 >>> bs.reference = fermi_energy 

196 >>> bs.plot() 

197 

198 """ 

199 

200 if command is not self._deprecated: 

201 raise RuntimeError(compatibility_msg) 

202 

203 if label is not self._deprecated: 

204 import warnings 

205 

206 warnings.warn( 

207 'Ignoring label, please use directory instead', FutureWarning 

208 ) 

209 

210 if 'ASE_ESPRESSO_COMMAND' in os.environ and profile is None: 

211 import warnings 

212 

213 warnings.warn(compatibility_msg, FutureWarning) 

214 

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 )