Coverage for larch/xafs/feffrunner.py: 11%

236 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2024-10-16 21:04 +0000

1import sys 

2import os 

3 

4import glob 

5from shutil import copy, move 

6import subprocess 

7import time 

8import re 

9from pathlib import Path 

10from optparse import OptionParser 

11from subprocess import Popen, PIPE 

12 

13from larch import Group, isNamedClass 

14from larch.utils import isotime, bytes2str, uname, bindir, get_cwd 

15 

16def find_exe(exename): 

17 if uname == 'win' and not exename.endswith('.exe'): 

18 exename = f"{exename}.exe" 

19 exefile = Path(bindir, exename) 

20 if exefile.exists() and os.access(exefile, os.X_OK): 

21 return exefile 

22 

23class FeffRunner(Group): 

24 """ 

25 A Larch plugin for managing calls to the feff85exafs stand-alone executables. 

26 This plugin does not manage the output of Feff. See feffpath() and other tools. 

27 

28 Methods: 

29 run -- run one or more parts of feff 

30 

31 feff = feffrunner(feffinp='path/to/feff.inp') 

32 feff.run() to run feff monolithically 

33 feff.run('rdinp') 

34 feff.run('xsph') 

35 and so on to run individual parts of feff 

36 ('rdinp', 'pot', 'opconsat', 'xsph', 'pathfinder', 'genfmt', 'ff2x') 

37 

38 If the symbol _xafs._feff_executable is set to a Feff executable, 

39 it can be run by doing 

40 

41 feff = feffrunner(feffinp='path/to/feff6.inp') 

42 feff.run(None) 

43 

44 run returns None if feff ran successfully, otherwise it 

45 returns an Exception with a useful message 

46 

47 Other versions of feff in the execution path can also be 

48 handled, with the caveat that the executable begins with 

49 'feff', i.e. 'feff6', 'feff7', etc. 

50 

51 feff = feffrunner(feffinp='path/to/feff6.inp') 

52 feff.run('feff6') 

53 

54 If the value of the feffinp attribute is a file with a 

55 basename other than 'feff.inp', that file will be renamed to 

56 'feff.inp' and care will be taken to preserve an existing 

57 file by that name. 

58 

59 Attributes: 

60 folder -- the folder to run in, containing feff.inp file 

61 feffinp -- the feff.inp file, absolute or relative to `folder` 

62 resolved -- the fully resolved path to the most recently run executable 

63 verbose -- write screen messages if True 

64 mpse -- run opconsat after pot if True 

65 

66 """ 

67 

68 Feff8l_modules = ('rdinp', 'pot', 'xsph', 'pathfinder', 'genfmt', 'ff2x') 

69 

70 def __init__(self, feffinp='feff.inp', folder='.', verbose=True, _larch=None, 

71 message_writer=None, **kws): 

72 kwargs = dict(name='Feff runner') 

73 kwargs.update(kws) 

74 Group.__init__(self, **kwargs) 

75 self._larch = _larch 

76 

77 if folder is None: 

78 folder = '.' 

79 self.folder = folder 

80 self.feffinp = feffinp 

81 self.verbose = verbose 

82 self.message_writer = message_writer 

83 self.mpse = False 

84 self.resolved = None 

85 self.threshold = [] 

86 self.chargetransfer = [] 

87 

88 def __repr__(self): 

89 ffile = Path(self.folder, self.feffinp) 

90 return f'<External Feff Group: {ffile}>' 

91 

92 def run(self, feffinp=None, folder=None, exe='feff8l'): 

93 """ 

94 Make system call to run one or more of the stand-alone executables, 

95 writing a log file to the folder containing the input file. 

96 

97 """ 

98 if folder is not None: 

99 self.folder = folder 

100 

101 if feffinp is not None: 

102 self.feffinp = feffinp 

103 

104 if self.feffinp is None: 

105 raise Exception("no feff.inp file was specified") 

106 

107 savefile = '.save_.inp' 

108 here = Path.cwd().absolute() 

109 os.chdir(Path(self.folder).absolute()) 

110 

111 pfeff = Path(self.feffinp) 

112 feffinp_dir, feffinp_file = pfeff.parent, pfeff.name 

113 if feffinp_dir.exists(): 

114 os.chdir(feffinp_dir) 

115 

116 if not Path(feffinp_file).is_file(): 

117 raise Exception(f"feff.inp file '{feffinp_file}' could not be found") 

118 

119 if exe in (None, 'feff8l'): 

120 for module in self.Feff8l_modules: 

121 os.chdir(here) 

122 self.run(exe=module) 

123 return 

124 

125 # 

126 # exe is set, find the corresponding executable file 

127 ## find program to run: 

128 program = None 

129 if exe in self.Feff8l_modules: 

130 exe = f"feff8l_{exe}" 

131 

132 resolved_exe = find_exe(exe) 

133 if resolved_exe is not None: 

134 program = resolved_exe 

135 

136 else: 

137 getsym = self._larch.symtable.get_symbol 

138 try: 

139 program = getsym('_xafs._feff8_executable') 

140 except (NameError, AttributeError) as exc: 

141 try: 

142 program = getsym('_xafs._feff_executable') 

143 except (NameError, AttributeError) as exc: 

144 program = None 

145 

146 if program is not None: 

147 if not os.access(program, os.X_OK): 

148 program = None 

149 

150 if program is None: # Give up! 

151 os.chdir(here) 

152 raise Exception(f"'{exe}' executable cannot be found") 

153 

154 ## preserve an existing feff.inp file if this is not called feff.inp 

155 if feffinp_file != 'feff.inp': 

156 if Path('feff.inp').is_file(): 

157 copy('feff.inp', savefile) 

158 copy(feffinp_file, 'feff.inp') 

159 

160 logname = Path(program).name 

161 if logname.endswith('.exe'): 

162 logname = logname[:4] 

163 

164 log = f'feffrun_{logname}.log' 

165 

166 if Path(log).is_file(): 

167 os.unlink(log) 

168 

169 f = open(log, 'a') 

170 header = f"\n======== running Feff module {exe} ========\n" 

171 

172 def write(msg): 

173 msg = bytes2str(msg) 

174 msg = " : {:s}\n".format(msg.strip().rstrip()) 

175 if self._larch is not None: 

176 self._larch.writer.write(msg) 

177 else: 

178 sys.stdout.write(msg) 

179 

180 if self.verbose: 

181 write(header) 

182 f.write(header) 

183 process=subprocess.Popen(program, shell=False, 

184 stdout=subprocess.PIPE, 

185 stderr=subprocess.STDOUT) 

186 flag = False 

187 thislist = [] 

188 while True: 

189 if process.returncode is None: 

190 process.poll() 

191 time.sleep(0.01) 

192 line = bytes2str(process.stdout.readline()) 

193 if not line: 

194 break 

195 if self.verbose: 

196 write(line) 

197 if callable(self.message_writer): 

198 self.message_writer(line) 

199 

200 ## snarf threshold energy 

201 pattern = re.compile(r'mu_(new|old)=\s+(-?\d\.\d+)') 

202 match = pattern.search(line) 

203 if match is not None: 

204 self.threshold.append(match.group(2)) 

205 ## snarf charge transfer 

206 if line.strip().startswith('Charge transfer'): 

207 thislist = [] 

208 flag = True 

209 elif line.strip().startswith('SCF ITERATION'): 

210 self.chargetransfer.append(list(thislist)) 

211 flag = False 

212 elif line.strip().startswith('Done with module 1'): 

213 self.chargetransfer.append(list(thislist)) 

214 flag = False 

215 elif flag: 

216 this = line.split() 

217 thislist.append(this[1]) 

218 f.write(line) 

219 f.close 

220 

221 if Path(savefile).is_file(): 

222 move(savefile, 'feff.inp') 

223 os.chdir(here) 

224 return None 

225 

226###################################################################### 

227def feffrunner(folder=None, feffinp=None, verbose=True, _larch=None, **kws): 

228 """ 

229 Make a FeffRunner group given a folder containing a baseline calculation 

230 """ 

231 return FeffRunner(folder=folder, feffinp=feffinp, verbose=verbose, 

232 _larch=_larch, **kws) 

233 

234def feff6l(feffinp='feff.inp', folder='.', verbose=True, _larch=None, **kws): 

235 """ 

236 run a Feff6l calculation for a feff.inp file in a folder 

237 

238 Arguments: 

239 ---------- 

240 feffinp (str): name of feff.inp file to use ['feff.inp'] 

241 folder (str): folder for calculation, containing 'feff.inp' file ['.'] 

242 verbose (bool): whether to print out extra messages [False] 

243 

244 Returns: 

245 -------- 

246 instance of FeffRunner 

247 

248 Notes: 

249 ------ 

250 many results data files are generated in the Feff working folder 

251 """ 

252 feffrunner = FeffRunner(folder=folder, feffinp=feffinp, verbose=verbose, 

253 _larch=_larch, **kws) 

254 exe = find_exe('feff6l') 

255 feffrunner.run(exe=exe) 

256 return feffrunner 

257 

258def feff6l_cli(): 

259 """run feff6l as command line program 

260 """ 

261 

262 usage = """usage: %prog [options] folder(s) 

263 

264run feff6l on one or more folders containing feff.inp files 

265or on an input file in the current folder 

266 

267Examples: 

268 feff6l Structure1 Structure2 

269 

270 feff6l feff_Cu2O.inp 

271 

272""" 

273 

274 parser = OptionParser(usage=usage, prog="feff6l", 

275 version="Feff6L version 6L.02") 

276 

277 FEFFINP = 'feff.inp' 

278 (options, args) = parser.parse_args() 

279 if len(args) == 0: 

280 args = ['.'] 

281 

282 curdir = Path(get_cwd()).absolute() 

283 for arg in args: 

284 parg = Path(arg).absolute() 

285 if parg.is_file(): 

286 feff6l(feffinp=parg.as_posix()) 

287 elif parg.is_dir(): 

288 feffinp = Path(parg, 'feff.inp').absolute() 

289 if feffinp.exists(): 

290 os.chdir(parg) 

291 feff6l(folder=parg.as_posix()) 

292 else: 

293 cdir = Path.cwd().absolute() 

294 msg = "Could not find feff.inp file in folder '{cdir}'" 

295 sys.stdout.write(msg) 

296 os.chdir(curdir) 

297 

298 

299def feff8l(feffinp='feff.inp', folder='.', module=None, verbose=True, _larch=None, **kws): 

300 """ 

301 run a Feff8l calculation for a feff.inp file in a folder 

302 

303 Arguments: 

304 ---------- 

305 feffinp (str): name of feff.inp file to use ['feff.inp'] 

306 folder (str): folder for calculation, containing 'feff.inp' file ['.'] 

307 module (None or str): module of Feff8l to run [None -- run all] 

308 verbose (bool): whether to print out extra messages [False] 

309 

310 Returns: 

311 -------- 

312 instance of FeffRunner 

313 

314 Notes: 

315 ------ 

316 many results data files are generated in the Feff working folder 

317 """ 

318 feffrunner = FeffRunner(folder=folder, feffinp=feffinp, verbose=verbose, 

319 _larch=_larch, **kws) 

320 feffrunner.run(exe='feff8l') 

321 return feffrunner 

322 

323 

324def feff8l_cli(): 

325 """run feff8l as a command line program to run all or some of 

326 feff8l_rdinp 

327 feff8l_pot 

328 feff8l_xsph 

329 feff8l_pathfinder 

330 feff8l_genfmt 

331 feff8l_ff2x 

332 """ 

333 

334 usage = """usage: %prog [options] folder(s) 

335 

336run feff8l (or selected modules) on one 

337or more folders containing feff.inp files. 

338 

339Example: 

340 feff8l --no-ff2chi Structure1 Structure2 

341""" 

342 

343 parser = OptionParser(usage=usage, prog="feff8l", 

344 version="Feff85L for EXAFS version 8.5L, build 001") 

345 parser.add_option("-q", "--quiet", dest="quiet", action="store_true", 

346 default=False, help="set quiet mode, default=False") 

347 parser.add_option("--no-pot", dest="no_pot", action="store_true", 

348 default=False, help="do not run POT module") 

349 parser.add_option("--no-phases", dest="no_phases", action="store_true", 

350 default=False, help="do not run XSPH module") 

351 parser.add_option("--no-paths", dest="no_paths", action="store_true", 

352 default=False, help="do not run PATHFINDER module") 

353 parser.add_option("--no-genfmt", dest="no_genfmt", action="store_true", 

354 default=False, help="do not run GENFMT module") 

355 parser.add_option("--no-ff2chi", dest="no_ff2chi", action="store_true", 

356 default=False, help="do not run FF2CHI module") 

357 

358 

359 FEFFINP = 'feff.inp' 

360 (options, args) = parser.parse_args() 

361 

362 verbose = not options.quiet 

363 modules = ['rdinp', 'pot', 'xsph', 'pathfinder', 'genfmt', 'ff2x'] 

364 if options.no_pot: 

365 modules.remove('pot') 

366 if options.no_phases: 

367 modules.remove('xsph') 

368 if options.no_paths: 

369 modules.remove('pathfinder') 

370 if options.no_genfmt: 

371 modules.remove('genfmt') 

372 if options.no_ff2chi: 

373 modules.remove('ff2x') 

374 

375 if len(args) == 0: 

376 args = ['.'] 

377 

378 

379 def run_feff8l(modules): 

380 """ run selected modules of Feff85L """ 

381 try: 

382 logfile = open('feff8l.log', 'w+') 

383 except: 

384 logfile = tempfile.NamedTemporaryFile(prefix='feff8l') 

385 

386 def write(msg): 

387 msg = bytes2str(msg) 

388 sys.stdout.write(msg) 

389 logfile.write(msg) 

390 

391 write(f"#= Feff85l {isotime()}\n") 

392 for mod in modules: 

393 write(f"#= Feff85l {mod} module\n") 

394 exe = find_exe(f'feff8l_{mod}') 

395 proc = Popen(exe, stdout=PIPE, stderr=PIPE) 

396 while True: 

397 msg = bytes2str(proc.stdout.read()) 

398 if msg == '': 

399 break 

400 write(msg) 

401 while True: 

402 msg = bytes2str(proc.stderr.read()) 

403 if msg == '': 

404 break 

405 write(f"#ERROR {msg}") 

406 logfile.flush() 

407 for fname in glob.glob('log*.dat'): 

408 try: 

409 os.unlink(fname) 

410 except IOError: 

411 pass 

412 write(f"#= Feff85l done {isotime()}\n") 

413 

414 for dname in args: 

415 pdir = Path(dname).absolute() 

416 if pdir.exists() and pdir.is_dir(): 

417 thisdir = Path.cwd().absolute() 

418 os.chdir(pdir) 

419 pfeff = Path(FEFFINP) 

420 if pfeff.exists() and pfeff.is_file(): 

421 run_feff8l(modules) 

422 else: 

423 msg = f"Could not find feff.inp file in folder '{pdir}'" 

424 sys.stdout.write(msg) 

425 os.chdir(thisdir) 

426 else: 

427 print(f"Could not find folder '{pdir}'")