Coverage for /builds/debichem-team/python-ase/ase/calculators/acemolecule.py: 72.92%

96 statements  

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

1import os 

2from copy import deepcopy 

3 

4from ase.calculators.calculator import FileIOCalculator, ReadError 

5from ase.io import read 

6 

7 

8class ACE(FileIOCalculator): 

9 ''' 

10 ACE-Molecule logfile reader 

11 It has default parameters of each input section 

12 And parameters' type = list of dictionaries 

13 ''' 

14 name = 'ace' 

15 implemented_properties = ['energy', 'forces', 'excitation-energy'] 

16 basic_list = [{ 

17 'Type': 'Scaling', 'Scaling': '0.35', 'Basis': 'Sinc', 

18 'Grid': 'Sphere', 

19 'KineticMatrix': 'Finite_Difference', 'DerivativesOrder': '7', 

20 'GeometryFilename': None, 'NumElectrons': None} 

21 ] 

22 scf_list = [{ 

23 'ExchangeCorrelation': {'XFunctional': 'GGA_X_PBE', 

24 'CFunctional': 'GGA_C_PBE'}, 

25 'NumberOfEigenvalues': None, 

26 }] 

27 

28 force_list = [{'ForceDerivative': 'Potential'}] 

29 tddft_list = [{ 

30 'SortOrbital': 'Order', 'MaximumOrder': '10', 

31 'ExchangeCorrelation': {'XFunctional': 'GGA_X_PBE', 

32 'CFunctional': 'GGA_C_PBE'}, 

33 }] 

34 

35 order_list = ['BasicInformation', 'Guess', 'Scf'] 

36 guess_list = [{}] # type: ignore[var-annotated] 

37 default_parameters = {'BasicInformation': basic_list, 'Guess': guess_list, 

38 'Scf': scf_list, 'Force': force_list, 

39 'TDDFT': tddft_list, 'order': order_list} 

40 

41 def __init__( 

42 self, restart=None, 

43 ignore_bad_restart_file=FileIOCalculator._deprecated, 

44 label='ace', atoms=None, command=None, 

45 basisfile=None, **kwargs): 

46 FileIOCalculator.__init__(self, restart, ignore_bad_restart_file, 

47 label, atoms, command=command, **kwargs) 

48 

49 def set(self, **kwargs): 

50 '''Update parameters self.parameter member variable. 

51 1. Add default values for repeated parameter sections with 

52 self.default_parameters using order. 

53 2. Also add empty dictionary as an indicator for section existence 

54 if no relevant default_parameters exist. 

55 3. Update parameters from arguments. 

56 

57 Returns 

58 ======= 

59 Updated parameter 

60 ''' 

61 new_parameters = deepcopy(self.parameters) 

62 

63 changed_parameters = FileIOCalculator.set(self, **kwargs) 

64 

65 # Add default values for repeated parameter sections with 

66 # self.default_parameters using order. Also add empty 

67 # dictionary as an indicator for section existence if no 

68 # relevant default_parameters exist. 

69 if 'order' in kwargs: 

70 new_parameters['order'] = kwargs['order'] 

71 section_sets = set(kwargs['order']) 

72 for section_name in section_sets: 

73 repeat = kwargs['order'].count(section_name) 

74 if section_name in self.default_parameters.keys(): 

75 for _ in range(repeat - 1): 

76 new_parameters[section_name] += deepcopy( 

77 self.default_parameters[section_name]) 

78 else: 

79 new_parameters[section_name] = [] 

80 for _ in range(repeat): 

81 new_parameters[section_name].append({}) 

82 

83 # Update parameters 

84 for section in new_parameters['order']: 

85 if section in kwargs: 

86 if isinstance(kwargs[section], dict): 

87 kwargs[section] = [kwargs[section]] 

88 

89 for i, section_param in enumerate(kwargs[section]): 

90 new_parameters[section][i] = update_parameter( 

91 new_parameters[section][i], section_param) 

92 self.parameters = new_parameters 

93 return changed_parameters 

94 

95 def read(self, label): 

96 FileIOCalculator.read(self, label) 

97 filename = self.label + ".log" 

98 

99 with open(filename) as fd: 

100 lines = fd.readlines() 

101 if 'WARNING' in lines: 

102 raise ReadError( 

103 f"Not convergy energy in log file {filename}.") 

104 if '! total energy' not in lines: 

105 raise ReadError(f"Wrong ACE-Molecule log file {filename}.") 

106 

107 if not os.path.isfile(filename): 

108 raise ReadError( 

109 f"Wrong ACE-Molecule input file {filename}.") 

110 

111 self.read_results() 

112 

113 def write_input(self, atoms, properties=None, system_changes=None): 

114 '''Initializes input parameters and xyz files. If force calculation is 

115 requested, add Force section to parameters if not exists. 

116 

117 Parameters 

118 ========== 

119 atoms: ASE atoms object. 

120 properties: List of properties to be calculated. Should be element 

121 of self.implemented_properties. 

122 system_chages: Ignored. 

123 

124 ''' 

125 FileIOCalculator.write_input(self, atoms, properties, system_changes) 

126 with open(self.label + '.inp', 'w') as inputfile: 

127 xyz_name = f"{self.label}.xyz" 

128 atoms.write(xyz_name) 

129 

130 run_parameters = self.prepare_input(xyz_name, properties) 

131 self.write_acemolecule_input(inputfile, run_parameters) 

132 

133 def prepare_input(self, geometry_filename, properties): 

134 '''Initialize parameters dictionary based on geometry filename and 

135 calculated properties. 

136 

137 Parameters 

138 ========== 

139 geometry_filename: Geometry (XYZ format) file path. 

140 properties: Properties to be calculated. 

141 

142 Returns 

143 ======= 

144 Updated version of self.parameters; geometry file and 

145 optionally Force section are updated. 

146 

147 ''' 

148 copied_parameters = deepcopy(self.parameters) 

149 if (properties is not None and "forces" in properties 

150 and 'Force' not in copied_parameters['order']): 

151 copied_parameters['order'].append('Force') 

152 copied_parameters["BasicInformation"][0]["GeometryFilename"] = \ 

153 f"{self.label}.xyz" 

154 copied_parameters["BasicInformation"][0]["GeometryFormat"] = "xyz" 

155 return copied_parameters 

156 

157 def read_results(self): 

158 '''Read calculation results, speficied by 'quantities' variable, from 

159 the log file. 

160 

161 quantities 

162 ======= 

163 energy : obtaing single point energy(eV) from log file 

164 forces : obtaing force of each atom form log file 

165 excitation-energy : it able to calculate TDDFT. 

166 Return value is None. Result is not used. 

167 atoms : ASE atoms object 

168 

169 ''' 

170 filename = self.label + '.log' 

171 self.results = read(filename, format='acemolecule-out') 

172 

173 def write_acemolecule_section(self, fpt, section, depth=0): 

174 '''Write parameters in each section of input 

175 

176 Parameters 

177 ========== 

178 fpt: ACE-Moleucle input file object. Should be write mode. 

179 section: Dictionary of a parameter section. 

180 depth: Nested input depth. 

181 ''' 

182 for section, section_param in section.items(): 

183 if isinstance(section_param, (str, int, float)): 

184 fpt.write( 

185 ' ' * 

186 depth + 

187 str(section) + 

188 " " + 

189 str(section_param) + 

190 "\n") 

191 else: 

192 if isinstance(section_param, dict): 

193 fpt.write(' ' * depth + "%% " + str(section) + "\n") 

194 self.write_acemolecule_section( 

195 fpt, section_param, depth + 1) 

196 fpt.write(' ' * depth + "%% End\n") 

197 if isinstance(section_param, list): 

198 for val in section_param: 

199 fpt.write( 

200 ' ' * 

201 depth + 

202 str(section) + 

203 " " + 

204 str(val) + 

205 "\n") 

206 

207 def write_acemolecule_input(self, fpt, param, depth=0): 

208 '''Write ACE-Molecule input 

209 

210 ACE-Molecule input examples (not minimal) 

211 %% BasicInformation 

212 Type Scaling 

213 Scaling 0.4 

214 Basis Sinc 

215 Cell 10.0 

216 Grid Sphere 

217 GeometryFormat xyz 

218 SpinMultiplicity 3.0 

219 Polarize 1 

220 Centered 0 

221 %% Pseudopotential 

222 Pseudopotential 1 

223 UsingDoubleGrid 0 

224 FilterType Sinc 

225 Format upf 

226 PSFilePath /PATH/TO/UPF 

227 PSFileSuffix .pbe-theos.UPF 

228 %% End 

229 GeometryFilename xyz/C.xyz 

230 %% End 

231 %% Guess 

232 InitialGuess 3 

233 InitialFilenames 001.cube 

234 InitialFilenames 002.cube 

235 %% End 

236 %% Scf 

237 IterateMaxCycle 150 

238 ConvergenceType Energy 

239 ConvergenceTolerance 0.00001 

240 EnergyDecomposition 1 

241 ComputeInitialEnergy 1 

242 %% Diagonalize 

243 Tolerance 0.000001 

244 %% End 

245 %% ExchangeCorrelation 

246 XFunctional GGA_X_PBE 

247 CFunctional GGA_C_PBE 

248 %% End 

249 %% Mixing 

250 MixingMethod 1 

251 MixingType Density 

252 MixingParameter 0.5 

253 PulayMixingParameter 0.1 

254 %% End 

255 %% End 

256 

257 Parameters 

258 ========== 

259 fpt: File object, should be write mode. 

260 param: Dictionary of parameters. Also should contain 

261 special 'order' section_name for parameter section ordering. 

262 depth: Nested input depth. 

263 

264 Notes 

265 ===== 

266 - Order of parameter section 

267 (denoted using %% -- %% BasicInformation, %% Guess, etc.) 

268 is important, because it determines calculation order. 

269 For example, if Guess section comes after Scf section, 

270 calculation will not run because Scf will tries to run 

271 without initial Hamiltonian. 

272 - Order of each parameter section-section_name pair is 

273 not important unless their keys are the same. 

274 - Indentation unimportant and capital letters are important. 

275 ''' 

276 prefix = " " * depth 

277 

278 for i in range(len(param['order'])): 

279 fpt.write(prefix + "%% " + param['order'][i] + "\n") 

280 section_list = param[param['order'][i]] 

281 if len(section_list) > 0: 

282 section = section_list.pop(0) 

283 self.write_acemolecule_section(fpt, section, 1) 

284 fpt.write("%% End\n") 

285 return 

286 

287 

288def update_parameter(oldpar, newpar): 

289 '''Update each section of parameter (oldpar) using newpar keys and values. 

290 If section of newpar exist in oldpar, 

291 - Replace the section_name with newpar's section_name if 

292 oldvar section_name type is not dict. 

293 - Append the section_name with newpar's section_name 

294 if oldvar section_name type is list. 

295 - If oldpar section_name type is dict, it is subsection. 

296 So call update_parameter again. 

297 otherwise, add the parameter section and section_name from newpar. 

298 

299 Parameters 

300 ========== 

301 oldpar: dictionary of original parameters to be updated. 

302 newpar: dictionary containing parameter section and values to update. 

303 

304 Return 

305 ====== 

306 Updated parameter dictionary. 

307 ''' 

308 for section, section_param in newpar.items(): 

309 if section in oldpar: 

310 if isinstance(section_param, dict): 

311 oldpar[section] = update_parameter( 

312 oldpar[section], section_param) 

313 else: 

314 oldpar[section] = section_param 

315 else: 

316 oldpar[section] = section_param 

317 return oldpar