Coverage for /Users/Newville/Codes/xraylarch/larch/xafs/feffrunner.py: 12%

232 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-11-09 10:08 -0600

1import sys 

2import os 

3from os.path import realpath, isdir, isfile, join, basename, dirname, abspath 

4import glob 

5from shutil import copy, move 

6import subprocess 

7import time 

8import re 

9from optparse import OptionParser 

10from subprocess import Popen, PIPE 

11 

12from larch import Group, isNamedClass 

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

14 

15def find_exe(exename): 

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

17 exename = "%s.exe" % exename 

18 exefile = join(bindir, exename) 

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

20 return exefile 

21 

22class FeffRunner(Group): 

23 """ 

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

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

26 

27 Methods: 

28 run -- run one or more parts of feff 

29 

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

31 feff.run() to run feff monolithically 

32 feff.run('rdinp') 

33 feff.run('xsph') 

34 and so on to run individual parts of feff 

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

36 

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

38 it can be run by doing 

39 

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

41 feff.run(None) 

42 

43 run returns None if feff ran successfully, otherwise it 

44 returns an Exception with a useful message 

45 

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

47 handled, with the caveat that the executable begins with 

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

49 

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

51 feff.run('feff6') 

52 

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

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

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

56 file by that name. 

57 

58 Attributes: 

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

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

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

62 verbose -- write screen messages if True 

63 mpse -- run opconsat after pot if True 

64 

65 """ 

66 

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

68 

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

70 message_writer=None, **kws): 

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

72 kwargs.update(kws) 

73 Group.__init__(self, **kwargs) 

74 self._larch = _larch 

75 

76 if folder is None: 

77 folder = '.' 

78 self.folder = folder 

79 self.feffinp = feffinp 

80 self.verbose = verbose 

81 self.message_writer = message_writer 

82 self.mpse = False 

83 self.resolved = None 

84 self.threshold = [] 

85 self.chargetransfer = [] 

86 

87 def __repr__(self): 

88 fullfile = os.path.join(self.folder, self.feffinp) 

89 return '<External Feff Group: %s>' % fullfile 

90 

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

92 """ 

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

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

95 

96 """ 

97 if folder is not None: 

98 self.folder = folder 

99 

100 if feffinp is not None: 

101 self.feffinp = feffinp 

102 

103 if self.feffinp is None: 

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

105 

106 savefile = '.save_.inp' 

107 here = abspath(get_cwd()) 

108 os.chdir(abspath(self.folder)) 

109 

110 feffinp_dir, feffinp_file = os.path.split(self.feffinp) 

111 feffinp_dir = dirname(self.feffinp) 

112 if len(feffinp_dir) > 0: 

113 os.chdir(feffinp_dir) 

114 

115 if not isfile(feffinp_file): 

116 raise Exception("feff.inp file '%s' could not be found" % feffinp_file) 

117 

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

119 for module in self.Feff8l_modules: 

120 os.chdir(here) 

121 self.run(exe=module) 

122 return 

123 

124 # 

125 # exe is set, find the corresponding executable file 

126 ## find program to run: 

127 program = None 

128 if exe in self.Feff8l_modules: 

129 exe = "feff8l_%s" % exe 

130 

131 resolved_exe = find_exe(exe) 

132 if resolved_exe is not None: 

133 program = resolved_exe 

134 

135 else: 

136 getsym = self._larch.symtable.get_symbol 

137 try: 

138 program = getsym('_xafs._feff8_executable') 

139 except (NameError, AttributeError) as exc: 

140 try: 

141 program = getsym('_xafs._feff_executable') 

142 except (NameError, AttributeError) as exc: 

143 program = None 

144 

145 if program is not None: 

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

147 program = None 

148 

149 if program is None: # Give up! 

150 os.chdir(here) 

151 raise Exception("'%s' executable cannot be found" % exe) 

152 

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

154 if feffinp_file != 'feff.inp': 

155 if isfile('feff.inp'): 

156 copy('feff.inp', savefile) 

157 copy(feffinp_file, 'feff.inp') 

158 

159 _, logname = os.path.split(program) 

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

161 logname = logname[:4] 

162 

163 log = 'feffrun_%s.log' % logname 

164 

165 if isfile(log): 

166 os.unlink(log) 

167 

168 f = open(log, 'a') 

169 header = "\n======== running Feff module %s ========\n" % exe 

170 

171 def write(msg): 

172 msg = bytes2str(msg) 

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

174 if self._larch is not None: 

175 self._larch.writer.write(msg) 

176 else: 

177 sys.stdout.write(msg) 

178 

179 if self.verbose: 

180 write(header) 

181 f.write(header) 

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

183 stdout=subprocess.PIPE, 

184 stderr=subprocess.STDOUT) 

185 flag = False 

186 thislist = [] 

187 while True: 

188 if process.returncode is None: 

189 process.poll() 

190 time.sleep(0.01) 

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

192 if not line: 

193 break 

194 if self.verbose: 

195 write(line) 

196 if callable(self.message_writer): 

197 self.message_writer(line) 

198 

199 ## snarf threshold energy 

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

201 match = pattern.search(line) 

202 if match is not None: 

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

204 ## snarf charge transfer 

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

206 thislist = [] 

207 flag = True 

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

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

210 flag = False 

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

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

213 flag = False 

214 elif flag: 

215 this = line.split() 

216 thislist.append(this[1]) 

217 f.write(line) 

218 f.close 

219 

220 if isfile(savefile): 

221 move(savefile, 'feff.inp') 

222 os.chdir(here) 

223 return None 

224 

225###################################################################### 

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

227 """ 

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

229 """ 

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

231 _larch=_larch, **kws) 

232 

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

234 """ 

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

236 

237 Arguments: 

238 ---------- 

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

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

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

242 

243 Returns: 

244 -------- 

245 instance of FeffRunner 

246 

247 Notes: 

248 ------ 

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

250 """ 

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

252 _larch=_larch, **kws) 

253 exe = find_exe('feff6l') 

254 feffrunner.run(exe=exe) 

255 return feffrunner 

256 

257def feff6l_cli(): 

258 """run feff6l as command line program 

259 """ 

260 

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

262 

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

264or on an input file in the current folder 

265 

266Examples: 

267 feff6l Structure1 Structure2 

268 

269 feff6l feff_Cu2O.inp 

270 

271""" 

272 

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

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

275 

276 FEFFINP = 'feff.inp' 

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

278 if len(args) == 0: 

279 args = ['.'] 

280 

281 curdir = abspath(get_cwd()) 

282 for arg in args: 

283 if os.path.isfile(arg): 

284 feff6l(feffinp=arg) 

285 elif os.path.isdir(arg): 

286 feffinp = os.path.join(arg, 'feff.inp') 

287 if os.path.exists(feffinp): 

288 os.chdir(abspath(arg)) 

289 feff6l(folder=arg) 

290 else: 

291 msg = "Could not find feff.inp file in folder '{:s}'" 

292 sys.stdout.write(msg.format(abspath(os.curdir))) 

293 os.chdir(curdir) 

294 

295 

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

297 """ 

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

299 

300 Arguments: 

301 ---------- 

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

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

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

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

306 

307 Returns: 

308 -------- 

309 instance of FeffRunner 

310 

311 Notes: 

312 ------ 

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

314 """ 

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

316 _larch=_larch, **kws) 

317 feffrunner.run(exe='feff8l') 

318 return feffrunner 

319 

320 

321def feff8l_cli(): 

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

323 feff8l_rdinp 

324 feff8l_pot 

325 feff8l_xsph 

326 feff8l_pathfinder 

327 feff8l_genfmt 

328 feff8l_ff2x 

329 """ 

330 

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

332 

333run feff8l (or selected modules) on one 

334or more folders containing feff.inp files. 

335 

336Example: 

337 feff8l --no-ff2chi Structure1 Structure2 

338""" 

339 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

354 

355 

356 FEFFINP = 'feff.inp' 

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

358 

359 verbose = not options.quiet 

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

361 if options.no_pot: 

362 modules.remove('pot') 

363 if options.no_phases: 

364 modules.remove('xsph') 

365 if options.no_paths: 

366 modules.remove('pathfinder') 

367 if options.no_genfmt: 

368 modules.remove('genfmt') 

369 if options.no_ff2chi: 

370 modules.remove('ff2x') 

371 

372 if len(args) == 0: 

373 args = ['.'] 

374 

375 

376 def run_feff8l(modules): 

377 """ run selected modules of Feff85L """ 

378 try: 

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

380 except: 

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

382 

383 def write(msg): 

384 msg = bytes2str(msg) 

385 sys.stdout.write(msg) 

386 logfile.write(msg) 

387 

388 write("#= Feff85l %s\n" % isotime()) 

389 for mod in modules: 

390 write("#= Feff85l %s module\n" % mod) 

391 exe = find_exe('feff8l_%s' % mod) 

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

393 while True: 

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

395 if msg == '': 

396 break 

397 write(msg) 

398 while True: 

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

400 if msg == '': 

401 break 

402 write("#ERROR %s" % msg) 

403 logfile.flush() 

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

405 try: 

406 os.unlink(fname) 

407 except IOError: 

408 pass 

409 write("#= Feff85l done %s\n" % isotime()) 

410 

411 for dirname in args: 

412 if os.path.exists(dirname) and os.path.isdir(dirname): 

413 thisdir = abspath(os.curdir) 

414 os.chdir(dirname) 

415 if os.path.exists(FEFFINP) and os.path.isfile(FEFFINP): 

416 run_feff8l(modules) 

417 else: 

418 msg = "Could not find feff.inp file in folder '{:s}'" 

419 sys.stdout.write(msg.format(abspath(os.curdir))) 

420 os.chdir(thisdir) 

421 else: 

422 print("Could not find folder '{:s}'".format(dirname))