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
« prev ^ index » next coverage.py v7.6.0, created at 2024-10-16 21:04 +0000
1import sys
2import os
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
13from larch import Group, isNamedClass
14from larch.utils import isotime, bytes2str, uname, bindir, get_cwd
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
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.
28 Methods:
29 run -- run one or more parts of feff
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')
38 If the symbol _xafs._feff_executable is set to a Feff executable,
39 it can be run by doing
41 feff = feffrunner(feffinp='path/to/feff6.inp')
42 feff.run(None)
44 run returns None if feff ran successfully, otherwise it
45 returns an Exception with a useful message
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.
51 feff = feffrunner(feffinp='path/to/feff6.inp')
52 feff.run('feff6')
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.
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
66 """
68 Feff8l_modules = ('rdinp', 'pot', 'xsph', 'pathfinder', 'genfmt', 'ff2x')
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
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 = []
88 def __repr__(self):
89 ffile = Path(self.folder, self.feffinp)
90 return f'<External Feff Group: {ffile}>'
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.
97 """
98 if folder is not None:
99 self.folder = folder
101 if feffinp is not None:
102 self.feffinp = feffinp
104 if self.feffinp is None:
105 raise Exception("no feff.inp file was specified")
107 savefile = '.save_.inp'
108 here = Path.cwd().absolute()
109 os.chdir(Path(self.folder).absolute())
111 pfeff = Path(self.feffinp)
112 feffinp_dir, feffinp_file = pfeff.parent, pfeff.name
113 if feffinp_dir.exists():
114 os.chdir(feffinp_dir)
116 if not Path(feffinp_file).is_file():
117 raise Exception(f"feff.inp file '{feffinp_file}' could not be found")
119 if exe in (None, 'feff8l'):
120 for module in self.Feff8l_modules:
121 os.chdir(here)
122 self.run(exe=module)
123 return
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}"
132 resolved_exe = find_exe(exe)
133 if resolved_exe is not None:
134 program = resolved_exe
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
146 if program is not None:
147 if not os.access(program, os.X_OK):
148 program = None
150 if program is None: # Give up!
151 os.chdir(here)
152 raise Exception(f"'{exe}' executable cannot be found")
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')
160 logname = Path(program).name
161 if logname.endswith('.exe'):
162 logname = logname[:4]
164 log = f'feffrun_{logname}.log'
166 if Path(log).is_file():
167 os.unlink(log)
169 f = open(log, 'a')
170 header = f"\n======== running Feff module {exe} ========\n"
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)
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)
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
221 if Path(savefile).is_file():
222 move(savefile, 'feff.inp')
223 os.chdir(here)
224 return None
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)
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
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]
244 Returns:
245 --------
246 instance of FeffRunner
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
258def feff6l_cli():
259 """run feff6l as command line program
260 """
262 usage = """usage: %prog [options] folder(s)
264run feff6l on one or more folders containing feff.inp files
265or on an input file in the current folder
267Examples:
268 feff6l Structure1 Structure2
270 feff6l feff_Cu2O.inp
272"""
274 parser = OptionParser(usage=usage, prog="feff6l",
275 version="Feff6L version 6L.02")
277 FEFFINP = 'feff.inp'
278 (options, args) = parser.parse_args()
279 if len(args) == 0:
280 args = ['.']
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)
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
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]
310 Returns:
311 --------
312 instance of FeffRunner
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
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 """
334 usage = """usage: %prog [options] folder(s)
336run feff8l (or selected modules) on one
337or more folders containing feff.inp files.
339Example:
340 feff8l --no-ff2chi Structure1 Structure2
341"""
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")
359 FEFFINP = 'feff.inp'
360 (options, args) = parser.parse_args()
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')
375 if len(args) == 0:
376 args = ['.']
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')
386 def write(msg):
387 msg = bytes2str(msg)
388 sys.stdout.write(msg)
389 logfile.write(msg)
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")
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}'")