Coverage for /builds/debichem-team/python-ase/ase/calculators/espresso.py: 96.72%

61 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-03-06 04:00 +0000

1"""Quantum ESPRESSO Calculator 

2 

3Run pw.x jobs. 

4""" 

5 

6 

7import os 

8import warnings 

9 

10from ase.calculators.genericfileio import ( 

11 BaseProfile, 

12 CalculatorTemplate, 

13 GenericFileIOCalculator, 

14 read_stdout, 

15) 

16from ase.io import read, write 

17from ase.io.espresso import Namelist 

18 

19compatibility_msg = ( 

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

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

22 'to customize command-line arguments.' 

23) 

24 

25 

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

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

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

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

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

31 

32 

33class EspressoProfile(BaseProfile): 

34 configvars = {'pseudo_dir'} 

35 

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

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

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

39 self.pseudo_dir = str(pseudo_dir) 

40 

41 @staticmethod 

42 def parse_version(stdout): 

43 import re 

44 

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

46 assert match is not None 

47 return match.group(1) 

48 

49 def version(self): 

50 stdout = read_stdout(self._split_command) 

51 return self.parse_version(stdout) 

52 

53 def get_calculator_command(self, inputfile): 

54 return ['-in', inputfile] 

55 

56 

57class EspressoTemplate(CalculatorTemplate): 

58 _label = 'espresso' 

59 

60 def __init__(self): 

61 super().__init__( 

62 'espresso', 

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

64 ) 

65 self.inputname = f'{self._label}.pwi' 

66 self.outputname = f'{self._label}.pwo' 

67 self.errorname = f"{self._label}.err" 

68 

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

70 dst = directory / self.inputname 

71 

72 input_data = Namelist(parameters.pop("input_data", None)) 

73 input_data.to_nested("pw") 

74 input_data["control"].setdefault("pseudo_dir", str(profile.pseudo_dir)) 

75 

76 parameters["input_data"] = input_data 

77 

78 write( 

79 dst, 

80 atoms, 

81 format='espresso-in', 

82 properties=properties, 

83 **parameters, 

84 ) 

85 

86 def execute(self, directory, profile): 

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

88 errorfile=self.errorname) 

89 

90 def read_results(self, directory): 

91 path = directory / self.outputname 

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

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

94 

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

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

97 

98 def socketio_parameters(self, unixsocket, port): 

99 return {} 

100 

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

102 if unixsocket: 

103 ipi_arg = f'{unixsocket}:UNIX' 

104 else: 

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

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

107 '--ipi', 

108 ipi_arg, 

109 ] 

110 

111 

112class Espresso(GenericFileIOCalculator): 

113 def __init__( 

114 self, 

115 *, 

116 profile=None, 

117 command=GenericFileIOCalculator._deprecated, 

118 label=GenericFileIOCalculator._deprecated, 

119 directory='.', 

120 **kwargs, 

121 ): 

122 """ 

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

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

125 already in a dict. 

126 

127 input_data: dict 

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

129 pseudopotentials: dict 

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

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

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

133 kspacing: float 

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

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

136 will be used instead. 

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

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

139 as the dimensions of a Monkhorst-Pack grid. 

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

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

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

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

144 are typically reduced by half. 

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

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

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

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

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

150 koffset: (int, int, int) 

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

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

153 

154 """ 

155 

156 if command is not self._deprecated: 

157 raise RuntimeError(compatibility_msg) 

158 

159 if label is not self._deprecated: 

160 warnings.warn( 

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

162 ) 

163 

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

165 warnings.warn(compatibility_msg, FutureWarning) 

166 

167 template = EspressoTemplate() 

168 super().__init__( 

169 profile=profile, 

170 template=template, 

171 directory=directory, 

172 parameters=kwargs, 

173 )