Coverage for /builds/kinetik161/ase/ase/calculators/espresso.py: 90.00%

60 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-12-10 11:04 +0000

1"""Quantum ESPRESSO Calculator 

2 

3Run pw.x jobs. 

4""" 

5 

6 

7import os 

8import warnings 

9 

10from ase.calculators.genericfileio import ( 

11 CalculatorTemplate, 

12 GenericFileIOCalculator, 

13 read_stdout, 

14 BaseProfile, 

15) 

16from ase.io import read, write 

17 

18compatibility_msg = ( 

19 'Espresso calculator is being restructured. Please use e.g. ' 

20 "Espresso(profile=EspressoProfile(argv=['mpiexec', 'pw.x'])) " 

21 'to customize command-line arguments.' 

22) 

23 

24 

25# XXX We should find a way to display this warning. 

26# warn_template = 'Property "%s" is None. Typically, this is because the ' \ 

27# 'required information has not been printed by Quantum ' \ 

28# 'Espresso at a "low" verbosity level (the default). ' \ 

29# 'Please try running Quantum Espresso with "high" verbosity.' 

30 

31 

32class EspressoProfile(BaseProfile): 

33 def __init__(self, binary, pseudo_path, **kwargs): 

34 super().__init__(**kwargs) 

35 self.binary = binary 

36 self.pseudo_path = pseudo_path 

37 

38 @staticmethod 

39 def parse_version(stdout): 

40 import re 

41 

42 match = re.match(r'\s*Program PWSCF\s*v\.(\S+)', stdout, re.M) 

43 assert match is not None 

44 return match.group(1) 

45 

46 def version(self): 

47 try: 

48 stdout = read_stdout(self.binary) 

49 return self.parse_version(stdout) 

50 except FileNotFoundError: 

51 warnings.warn( 

52 f'The executable {self.binary} is not found on the path' 

53 ) 

54 return None 

55 

56 def get_calculator_command(self, inputfile): 

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

58 

59 

60class EspressoTemplate(CalculatorTemplate): 

61 def __init__(self): 

62 super().__init__( 

63 'espresso', 

64 ['energy', 'free_energy', 'forces', 'stress', 'magmoms', 'dipole'], 

65 ) 

66 self.inputname = 'espresso.pwi' 

67 self.outputname = 'espresso.pwo' 

68 

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

70 dst = directory / self.inputname 

71 write( 

72 dst, 

73 atoms, 

74 format='espresso-in', 

75 properties=properties, 

76 pseudo_dir=str(profile.pseudo_path), 

77 **parameters, 

78 ) 

79 

80 def execute(self, directory, profile): 

81 profile.run(directory, self.inputname, directory / self.outputname) 

82 

83 def read_results(self, directory): 

84 path = directory / self.outputname 

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

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

87 

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

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

90 

91 def socketio_parameters(self, unixsocket, port): 

92 return {} 

93 

94 def socketio_argv(self, profile, unixsocket, port): 

95 if unixsocket: 

96 ipi_arg = f'{unixsocket}:UNIX' 

97 else: 

98 ipi_arg = f'localhost:{port:d}' # XXX should take host, too 

99 return profile.get_calculator_command(self.inputname) + [ 

100 '--ipi', 

101 ipi_arg, 

102 ] 

103 

104 

105class Espresso(GenericFileIOCalculator): 

106 def __init__( 

107 self, 

108 *, 

109 profile=None, 

110 command=GenericFileIOCalculator._deprecated, 

111 label=GenericFileIOCalculator._deprecated, 

112 directory='.', 

113 parallel_info=None, 

114 parallel=True, 

115 **kwargs, 

116 ): 

117 """ 

118 All options for pw.x are copied verbatim to the input file, and put 

119 into the correct section. Use ``input_data`` for parameters that are 

120 already in a dict, all other ``kwargs`` are passed as parameters. 

121 

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

123 additional options: 

124 

125 input_data: dict 

126 A flat or nested dictionary with input parameters for pw.x 

127 pseudopotentials: dict 

128 A filename for each atomic species, e.g. 

129 ``{'O': 'O.pbe-rrkjus.UPF', 'H': 'H.pbe-rrkjus.UPF'}``. 

130 A dummy name will be used if none are given. 

131 kspacing: float 

132 Generate a grid of k-points with this as the minimum distance, 

133 in A^-1 between them in reciprocal space. If set to None, kpts 

134 will be used instead. 

135 kpts: (int, int, int), dict, or BandPath 

136 If kpts is a tuple (or list) of 3 integers, it is interpreted 

137 as the dimensions of a Monkhorst-Pack grid. 

138 If ``kpts`` is set to ``None``, only the Γ-point will be included 

139 and QE will use routines optimized for Γ-point-only calculations. 

140 Compared to Γ-point-only calculations without this optimization 

141 (i.e. with ``kpts=(1, 1, 1)``), the memory and CPU requirements 

142 are typically reduced by half. 

143 If kpts is a dict, it will either be interpreted as a path 

144 in the Brillouin zone (*) if it contains the 'path' keyword, 

145 otherwise it is converted to a Monkhorst-Pack grid (**). 

146 (*) see ase.dft.kpoints.bandpath 

147 (**) see ase.calculators.calculator.kpts2sizeandoffsets 

148 koffset: (int, int, int) 

149 Offset of kpoints in each direction. Must be 0 (no offset) or 

150 1 (half grid offset). Setting to True is equivalent to (1, 1, 1). 

151 

152 

153 .. note:: 

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

155 stresses. 

156 

157 .. note:: 

158 Band structure plots can be made as follows: 

159 

160 

161 1. Perform a regular self-consistent calculation, 

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

163 getting the Fermi energy: 

164 

165 >>> input_data = {<your input data>} 

166 >>> calc = Espresso(input_data=input_data, ...) 

167 >>> atoms.calc = calc 

168 >>> atoms.get_potential_energy() 

169 >>> fermi_level = calc.get_fermi_level() 

170 

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

172 after updating your input_data and kpts keywords: 

173 

174 >>> input_data['control'].update({'calculation':'bands', 

175 >>> 'restart_mode':'restart', 

176 >>> 'verbosity':'high'}) 

177 >>> calc.set(kpts={<your Brillouin zone path>}, 

178 >>> input_data=input_data) 

179 >>> calc.calculate(atoms) 

180 

181 3. Make the plot using the BandStructure functionality, 

182 after setting the Fermi level to that of the prior 

183 self-consistent calculation: 

184 

185 >>> bs = calc.band_structure() 

186 >>> bs.reference = fermi_energy 

187 >>> bs.plot() 

188 

189 """ 

190 

191 if command is not self._deprecated: 

192 raise RuntimeError(compatibility_msg) 

193 

194 if label is not self._deprecated: 

195 import warnings 

196 

197 warnings.warn( 

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

199 ) 

200 

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

202 import warnings 

203 

204 warnings.warn(compatibility_msg, FutureWarning) 

205 

206 template = EspressoTemplate() 

207 super().__init__( 

208 profile=profile, 

209 template=template, 

210 directory=directory, 

211 parallel_info=parallel_info, 

212 parallel=parallel, 

213 parameters=kwargs, 

214 )