Coverage for /builds/ase/ase/ase/calculators/espresso.py: 96.72%

61 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-08-02 00:12 +0000

1# fmt: off 

2 

3"""Quantum ESPRESSO Calculator 

4 

5Run pw.x jobs. 

6""" 

7 

8 

9import os 

10import warnings 

11 

12from ase.calculators.genericfileio import ( 

13 BaseProfile, 

14 CalculatorTemplate, 

15 GenericFileIOCalculator, 

16 read_stdout, 

17) 

18from ase.io import read, write 

19from ase.io.espresso import Namelist 

20 

21compatibility_msg = ( 

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

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

24 'to customize command-line arguments.' 

25) 

26 

27 

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

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

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

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

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

33 

34 

35class EspressoProfile(BaseProfile): 

36 configvars = {'pseudo_dir'} 

37 

38 def __init__(self, command, pseudo_dir, **kwargs): 

39 super().__init__(command, **kwargs) 

40 # not Path object to avoid problems in remote calculations from Windows 

41 self.pseudo_dir = str(pseudo_dir) 

42 

43 @staticmethod 

44 def parse_version(stdout): 

45 import re 

46 

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

48 assert match is not None 

49 return match.group(1) 

50 

51 def version(self): 

52 stdout = read_stdout(self._split_command) 

53 return self.parse_version(stdout) 

54 

55 def get_calculator_command(self, inputfile): 

56 return ['-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 **kwargs, 

123 ): 

124 """ 

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

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

127 already in a dict. 

128 

129 input_data: dict 

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

131 pseudopotentials: dict 

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

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

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

135 kspacing: float 

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

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

138 will be used instead. 

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

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

141 as the dimensions of a Monkhorst-Pack grid. 

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

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

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

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

146 are typically reduced by half. 

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

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

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

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

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

152 koffset: (int, int, int) 

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

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

155 

156 """ 

157 

158 if command is not self._deprecated: 

159 raise RuntimeError(compatibility_msg) 

160 

161 if label is not self._deprecated: 

162 warnings.warn( 

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

164 ) 

165 

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

167 warnings.warn(compatibility_msg, FutureWarning) 

168 

169 template = EspressoTemplate() 

170 super().__init__( 

171 profile=profile, 

172 template=template, 

173 directory=directory, 

174 parameters=kwargs, 

175 )