Coverage for /builds/kinetik161/ase/ase/calculators/acemolecule.py: 73.47%

98 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-12-10 11:04 +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 i 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 i 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 i = 0 

90 for section_param in kwargs[section]: 

91 new_parameters[section][i] = update_parameter( 

92 new_parameters[section][i], section_param) 

93 i += 1 

94 self.parameters = new_parameters 

95 return changed_parameters 

96 

97 def read(self, label): 

98 FileIOCalculator.read(self, label) 

99 filename = self.label + ".log" 

100 

101 with open(filename) as fd: 

102 lines = fd.readlines() 

103 if 'WARNING' in lines: 

104 raise ReadError( 

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

106 if '! total energy' not in lines: 

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

108 

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

110 raise ReadError( 

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

112 

113 self.read_results() 

114 

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

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

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

118 

119 Parameters 

120 ========== 

121 atoms: ASE atoms object. 

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

123 of self.implemented_properties. 

124 system_chages: Ignored. 

125 

126 ''' 

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

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

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

130 atoms.write(xyz_name) 

131 

132 run_parameters = self.prepare_input(xyz_name, properties) 

133 self.write_acemolecule_input(inputfile, run_parameters) 

134 

135 def prepare_input(self, geometry_filename, properties): 

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

137 calculated properties. 

138 

139 Parameters 

140 ========== 

141 geometry_filename: Geometry (XYZ format) file path. 

142 properties: Properties to be calculated. 

143 

144 Returns 

145 ======= 

146 Updated version of self.parameters; geometry file and 

147 optionally Force section are updated. 

148 

149 ''' 

150 copied_parameters = deepcopy(self.parameters) 

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

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

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

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

155 f"{self.label}.xyz" 

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

157 return copied_parameters 

158 

159 def read_results(self): 

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

161 the log file. 

162 

163 quantities 

164 ======= 

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

166 forces : obtaing force of each atom form log file 

167 excitation-energy : it able to calculate TDDFT. 

168 Return value is None. Result is not used. 

169 atoms : ASE atoms object 

170 

171 ''' 

172 filename = self.label + '.log' 

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

174 

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

176 '''Write parameters in each section of input 

177 

178 Parameters 

179 ========== 

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

181 section: Dictionary of a parameter section. 

182 depth: Nested input depth. 

183 ''' 

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

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

186 fpt.write( 

187 ' ' * 

188 depth + 

189 str(section) + 

190 " " + 

191 str(section_param) + 

192 "\n") 

193 else: 

194 if isinstance(section_param, dict): 

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

196 self.write_acemolecule_section( 

197 fpt, section_param, depth + 1) 

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

199 if isinstance(section_param, list): 

200 for val in section_param: 

201 fpt.write( 

202 ' ' * 

203 depth + 

204 str(section) + 

205 " " + 

206 str(val) + 

207 "\n") 

208 

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

210 '''Write ACE-Molecule input 

211 

212 ACE-Molecule input examples (not minimal) 

213 %% BasicInformation 

214 Type Scaling 

215 Scaling 0.4 

216 Basis Sinc 

217 Cell 10.0 

218 Grid Sphere 

219 GeometryFormat xyz 

220 SpinMultiplicity 3.0 

221 Polarize 1 

222 Centered 0 

223 %% Pseudopotential 

224 Pseudopotential 1 

225 UsingDoubleGrid 0 

226 FilterType Sinc 

227 Format upf 

228 PSFilePath /PATH/TO/UPF 

229 PSFileSuffix .pbe-theos.UPF 

230 %% End 

231 GeometryFilename xyz/C.xyz 

232 %% End 

233 %% Guess 

234 InitialGuess 3 

235 InitialFilenames 001.cube 

236 InitialFilenames 002.cube 

237 %% End 

238 %% Scf 

239 IterateMaxCycle 150 

240 ConvergenceType Energy 

241 ConvergenceTolerance 0.00001 

242 EnergyDecomposition 1 

243 ComputeInitialEnergy 1 

244 %% Diagonalize 

245 Tolerance 0.000001 

246 %% End 

247 %% ExchangeCorrelation 

248 XFunctional GGA_X_PBE 

249 CFunctional GGA_C_PBE 

250 %% End 

251 %% Mixing 

252 MixingMethod 1 

253 MixingType Density 

254 MixingParameter 0.5 

255 PulayMixingParameter 0.1 

256 %% End 

257 %% End 

258 

259 Parameters 

260 ========== 

261 fpt: File object, should be write mode. 

262 param: Dictionary of parameters. Also should contain 

263 special 'order' section_name for parameter section ordering. 

264 depth: Nested input depth. 

265 

266 Notes 

267 ===== 

268 - Order of parameter section 

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

270 is important, because it determines calculation order. 

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

272 calculation will not run because Scf will tries to run 

273 without initial Hamiltonian. 

274 - Order of each parameter section-section_name pair is 

275 not important unless their keys are the same. 

276 - Indentation unimportant and capital letters are important. 

277 ''' 

278 prefix = " " * depth 

279 

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

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

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

283 if len(section_list) > 0: 

284 section = section_list.pop(0) 

285 self.write_acemolecule_section(fpt, section, 1) 

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

287 return 

288 

289 

290def update_parameter(oldpar, newpar): 

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

292 If section of newpar exist in oldpar, 

293 - Replace the section_name with newpar's section_name if 

294 oldvar section_name type is not dict. 

295 - Append the section_name with newpar's section_name 

296 if oldvar section_name type is list. 

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

298 So call update_parameter again. 

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

300 

301 Parameters 

302 ========== 

303 oldpar: dictionary of original parameters to be updated. 

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

305 

306 Return 

307 ====== 

308 Updated parameter dictionary. 

309 ''' 

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

311 if section in oldpar: 

312 if isinstance(section_param, dict): 

313 oldpar[section] = update_parameter( 

314 oldpar[section], section_param) 

315 else: 

316 oldpar[section] = section_param 

317 else: 

318 oldpar[section] = section_param 

319 return oldpar