Coverage for larch/wxlib/structure2feff_browser.py: 13%
359 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
1#!/usr/bin/env python
2"""
3Read Structure input file, Make Feff input file, and run Feff
4"""
6import os
7import sys
8from pathlib import Path
9import numpy as np
10np.seterr(all='ignore')
12import wx
13import wx.lib.scrolledpanel as scrolled
14import wx.lib.agw.flatnotebook as fnb
16from xraydb.chemparser import chemparse
17from xraydb import atomic_number
19import larch
20from larch.xafs import feff8l, feff6l
21from larch.utils import unixpath, mkdir, read_textfile
22from larch.utils.strutils import fix_filename, unique_name, strict_ascii
23from larch.site_config import user_larchdir
25from larch.wxlib import (LarchFrame, FloatSpin, EditableListBox,
26 FloatCtrl, SetTip, get_icon, SimpleText, pack,
27 Button, Popup, HLine, FileSave, FileOpen, Choice,
28 Check, MenuItem, CEN, LEFT, FRAMESTYLE,
29 Font, FONTSIZE, flatnotebook, LarchUpdaterDialog,
30 PeriodicTablePanel, FeffResultsPanel, LarchWxApp,
31 ExceptionPopup, set_color)
34from larch.xrd import structure2feff
36LEFT = wx.ALIGN_LEFT
37CEN |= wx.ALL
38FNB_STYLE = fnb.FNB_NO_X_BUTTON|fnb.FNB_SMART_TABS
39FNB_STYLE |= fnb.FNB_NO_NAV_BUTTONS|fnb.FNB_NODRAG
41MAINSIZE = (850, 750)
43class Structure2FeffFrame(wx.Frame):
44 _about = """Larch structure browser for generating and running Feff.
46 Ryuichi Shimogawa <ryuichi.shimogawa@stonybrook.edu>
47 """
48 def __init__(self, parent=None, _larch=None, path_importer=None, filename=None, **kws):
49 wx.Frame.__init__(self, parent, -1, size=MAINSIZE, style=FRAMESTYLE)
51 title = "Larch FEFF Input Generator and FEFF Runner"
53 self.larch = _larch
54 if _larch is None:
55 self.larch = larch.Interpreter()
56 self.larch.eval("# started Structure browser\n")
57 self.larch.eval("if not hasattr('_sys', '_feffruns'): _sys._feffruns = {}")
58 self.path_importer = path_importer
59 self.subframes = {}
60 self.current_structure = None
61 self.SetTitle(title)
62 self.SetSize(MAINSIZE)
63 self.SetFont(Font(FONTSIZE))
64 self.createMainPanel()
65 self.createMenus()
67 self.feff_folder = Path(user_larchdir, 'feff').as_posix()
68 mkdir(self.feff_folder)
70 self.runs_list = []
71 for fname in os.listdir(self.feff_folder):
72 full = Path(self.feff_folder, fname).absolute()
73 if full.is_dir():
74 self.runs_list.append(full.name)
76 self.statusbar = self.CreateStatusBar(2, style=wx.STB_DEFAULT_STYLE)
77 self.statusbar.SetStatusWidths([-3, -1])
78 statusbar_fields = [" ", ""]
79 for i in range(len(statusbar_fields)):
80 self.statusbar.SetStatusText(statusbar_fields[i], i)
81 self.Show()
83 def createMainPanel(self):
84 display0 = wx.Display(0)
85 client_area = display0.ClientArea
86 xmin, ymin, xmax, ymax = client_area
87 xpos = int((xmax-xmin)*0.07) + xmin
88 ypos = int((ymax-ymin)*0.09) + ymin
89 self.SetPosition((xpos, ypos))
91 # main panel with scrolled panel
92 scrolledpanel = scrolled.ScrolledPanel(self)
93 panel = wx.Panel(scrolledpanel)
94 sizer = wx.GridBagSizer(2,2)
96 wids = self.wids = {}
98 folderlab = SimpleText(panel, ' Feff Folder: ')
99 wids['run_folder'] = wx.TextCtrl(panel, value='calc1', size=(250, -1))
101 wids['run_feff'] = Button(panel, ' Run Feff ',
102 action=self.onRunFeff)
103 wids['run_feff'].Disable()
104 wids['without_h'] = Check(panel, default=True, label='Remove H atoms',
105 action=self.onGetFeff)
108 wids['central_atom'] = Choice(panel, choices=['<empty>'], size=(80, -1),
109 action=self.onCentralAtom)
110 wids['edge'] = Choice(panel, choices=['K', 'L3', 'L2', 'L1',
111 'M5', 'M4'],
112 size=(80, -1),
113 action=self.onGetFeff)
115 wids['feffvers'] = Choice(panel, choices=['6', '8'], default=1,
116 size=(80, -1),
117 action=self.onGetFeff)
118 wids['site'] = Choice(panel, choices=['1', '2', '3', '4'],
119 size=(80, -1),
120 action=self.onGetFeff)
121 wids['cluster_size'] = FloatSpin(panel, value=7.0, digits=2,
122 increment=0.1, max_val=10,
123 action=self.onGetFeff)
124 wids['central_atom'].Disable()
125 wids['edge'].Disable()
126 wids['cluster_size'].Disable()
127 catomlab = SimpleText(panel, ' Absorbing Atom: ')
128 sitelab = SimpleText(panel, ' Crystal Site: ')
129 edgelab = SimpleText(panel, ' Edge: ')
130 csizelab = SimpleText(panel, ' Cluster Size (\u212B): ')
131 fverslab = SimpleText(panel, ' Feff Version:')
133 ir = 1
135 sizer.Add(catomlab, (ir, 0), (1, 1), LEFT, 3)
136 sizer.Add(wids['central_atom'], (ir, 1), (1, 1), LEFT, 3)
137 sizer.Add(sitelab, (ir, 2), (1, 1), LEFT, 3)
138 sizer.Add(wids['site'], (ir, 3), (1, 1), LEFT, 3)
139 sizer.Add(edgelab, (ir, 4), (1, 1), LEFT, 3)
140 sizer.Add(wids['edge'], (ir, 5), (1, 1), LEFT, 3)
142 ir += 1
143 sizer.Add(csizelab, (ir, 0), (1, 1), LEFT, 3)
144 sizer.Add(wids['cluster_size'], (ir, 1), (1, 1), LEFT, 3)
145 sizer.Add(fverslab, (ir, 2), (1, 1), LEFT, 3)
146 sizer.Add(wids['feffvers'], (ir, 3), (1, 1), LEFT, 3)
147 sizer.Add(wids['without_h'], (ir, 4), (1, 2), LEFT, 3)
149 ir += 1
150 sizer.Add(folderlab, (ir, 0), (1, 1), LEFT, 3)
151 sizer.Add(wids['run_folder'], (ir, 1), (1, 4), LEFT, 3)
152 sizer.Add(wids['run_feff'], (ir, 5), (1, 1), LEFT, 3)
154 pack(panel, sizer)
156 self.nb = flatnotebook(scrolledpanel, {}, on_change=self.onNBChanged)
158 self.feffresults = FeffResultsPanel(scrolledpanel,
159 path_importer=self.path_importer,
160 _larch=self.larch)
162 structure_panel = wx.Panel(scrolledpanel)
163 wids['structure_text'] = wx.TextCtrl(structure_panel, value='<STRUCTURE TEXT>',
164 style=wx.TE_MULTILINE|wx.TE_READONLY,
165 size=(300, 350))
166 wids['structure_text'].SetFont(Font(FONTSIZE+1))
167 structure_sizer = wx.BoxSizer(wx.VERTICAL)
168 structure_sizer.Add(wids['structure_text'], 1, LEFT|wx.GROW, 1)
169 pack(structure_panel, structure_sizer)
171 feff_panel = wx.Panel(scrolledpanel)
172 wids['feff_text'] = wx.TextCtrl(feff_panel,
173 value='<Feff Input Text>',
174 style=wx.TE_MULTILINE,
175 size=(300, 350))
176 wids['feff_text'].CanCopy()
178 feff_panel.onPanelExposed = self.onGetFeff
179 wids['feff_text'].SetFont(Font(FONTSIZE+1))
180 feff_sizer = wx.BoxSizer(wx.VERTICAL)
181 feff_sizer.Add(wids['feff_text'], 1, LEFT|wx.GROW, 1)
182 pack(feff_panel, feff_sizer)
184 feffout_panel = wx.Panel(scrolledpanel)
185 wids['feffout_text'] = wx.TextCtrl(feffout_panel,
186 value='<Feff Output>',
187 style=wx.TE_MULTILINE,
188 size=(300, 350))
189 wids['feffout_text'].CanCopy()
190 wids['feffout_text'].SetFont(Font(FONTSIZE+1))
191 feffout_sizer = wx.BoxSizer(wx.VERTICAL)
192 feffout_sizer.Add(wids['feffout_text'], 1, LEFT|wx.GROW, 1)
193 pack(feffout_panel, feffout_sizer)
195 self.nbpages = []
196 for label, page in (('Structure Text', structure_panel),
197 ('Feff Input Text', feff_panel),
198 ('Feff Output Text', feffout_panel),
199 ('Feff Results', self.feffresults),
200 ):
201 self.nb.AddPage(page, label, True)
202 self.nbpages.append((label, page))
203 self.nb.SetSelection(0)
205 r_sizer = wx.BoxSizer(wx.VERTICAL)
206 r_sizer.Add(panel, 0, LEFT|wx.GROW|wx.ALL)
207 r_sizer.Add(self.nb, 1, LEFT|wx.GROW, 2)
208 pack(scrolledpanel, r_sizer)
209 scrolledpanel.SetupScrolling()
211 def get_nbpage(self, name):
212 "get nb page by name"
213 name = name.lower()
214 for i, dat in enumerate(self.nbpages):
215 label, page = dat
216 if name in label.lower():
217 return i, page
218 return (0, self.npbages[0][1])
220 def onCentralAtom(self, event=None):
221 structure = self.current_structure
222 if structure is None:
223 return
224 catom = event.GetString()
225 try:
226 sites = structure2feff.structure_sites(structure['structure_text'], absorber=catom, fmt=structure['fmt'])
227 sites = ['%d' % (i+1) for i in range(len(sites))]
228 self.wids['site'].Clear()
229 self.wids['site'].AppendItems(sites)
230 self.wids['site'].Select(0)
231 except:
232 self.write_message(f"could not get sites for central atom '{catom}'")
233 title = f"Could not get sites for central atom '{catom}'"
234 message = []
235 ExceptionPopup(self, title, message)
237 edge_val = 'K' if atomic_number(catom) < 60 else 'L3'
238 self.wids['edge'].SetStringSelection(edge_val)
239 self.onGetFeff()
241 def onGetFeff(self, event=None):
242 structure = self.current_structure
243 if structure is None:
244 return
245 edge = self.wids['edge'].GetStringSelection()
246 version8 = '8' == self.wids['feffvers'].GetStringSelection()
247 catom = self.wids['central_atom'].GetStringSelection()
248 asite = int(self.wids['site'].GetStringSelection())
249 csize = self.wids['cluster_size'].GetValue()
250 with_h = not self.wids['without_h'].IsChecked()
251 folder = f'{catom:s}{asite:d}_{edge:s}'
252 folder = unique_name(fix_filename(folder), self.runs_list)
254 fefftext = structure2feff.structure2feffinp(structure['structure_text'], catom, edge=edge,
255 cluster_size=csize,
256 absorber_site=asite,
257 version8=version8,
258 with_h=with_h,
259 fmt=structure['fmt'])
261 self.wids['run_folder'].SetValue(folder)
262 self.wids['feff_text'].SetValue(fefftext)
263 self.wids['run_feff'].Enable()
264 i, p = self.get_nbpage('Feff Input')
265 self.nb.SetSelection(i)
267 def onRunFeff(self, event=None):
268 fefftext = self.wids['feff_text'].GetValue()
269 if len(fefftext) < 100 or 'ATOMS' not in fefftext:
270 return
272 structure_text = self.wids['structure_text'].GetValue()
273 structure = self.current_structure
274 structure_fname = None
276 if structure is not None:
277 structure_fname = structure['fname']
279 version8 = '8' == self.wids['feffvers'].GetStringSelection()
281 fname = self.wids['run_folder'].GetValue()
282 fname = unique_name(fix_filename(fname), self.runs_list)
283 self.runs_list.append(fname)
284 folder = Path(self.feff_folder, fname).absolute()
285 mkdir(folder)
287 ix, p = self.get_nbpage('Feff Output')
288 self.nb.SetSelection(ix)
290 self.folder = folder.as_posix()
291 out = self.wids['feffout_text']
292 out.Clear()
293 out.SetInsertionPoint(0)
294 out.WriteText(f'########\n###\n# Run Feff in folder: {folder:s}\n')
295 out.SetInsertionPoint(out.GetLastPosition())
296 out.WriteText('###\n########\n')
297 out.SetInsertionPoint(out.GetLastPosition())
299 fname = Path(folder, 'feff.inp').absolute()
300 with open(fname, 'w', encoding=sys.getdefaultencoding()) as fh:
301 fh.write(strict_ascii(fefftext))
303 if structure_fname is not None:
304 cname = Path(folder, structure_fname).absolute()
305 with open(cname, 'w', encoding=sys.getdefaultencoding()) as fh:
306 fh.write(strict_ascii(structure_text))
308 wx.CallAfter(self.run_feff, self.folder, version8=version8)
310 def run_feff(self, folder, version8=True):
311 folder = Path(folder).absolute()
312 dname = folder.name
313 prog, cmd = feff8l, 'feff8l'
314 if not version8:
315 prog, cmd = feff6l, 'feff6l'
316 command = f"{cmd:s}(folder='{folder}')"
317 self.larch.eval(f"## running Feff as:\n# {command:s}\n##\n")
319 prog(folder=folder.as_posix(), message_writer=self.feff_output)
320 self.larch.eval("## gathering results:\n")
321 self.larch.eval(f"_sys._feffruns['{dname}'] = get_feff_pathinfo('{folder}')")
322 this_feffrun = self.larch.symtable._sys._feffruns[f'{dname}']
323 self.feffresults.set_feffresult(this_feffrun)
324 ix, p = self.get_nbpage('Feff Results')
325 self.nb.SetSelection(ix)
327 # clean up unused, intermediate Feff files
328 for fname in os.listdir(folder):
329 if (fname.endswith('.json') or fname.endswith('.pad') or
330 fname.endswith('.bin') or fname.startswith('log') or
331 fname in ('chi.dat', 'xmu.dat', 'misc.dat')):
332 os.unlink(Path(folder, fname).absolute())
334 def feff_output(self, text):
335 out = self.wids['feffout_text']
336 ix, p = self.get_nbpage('Feff Output')
337 self.nb.SetSelection(ix)
338 pos0 = out.GetLastPosition()
339 if not text.endswith('\n'):
340 text = '%s\n' % text
341 out.WriteText(text)
342 out.SetInsertionPoint(out.GetLastPosition())
343 out.Update()
344 out.Refresh()
346 def onExportFeff(self, event=None):
347 if self.current_structure is None:
348 return
349 fefftext = self.wids['feff_text'].GetValue()
350 if len(fefftext) < 20:
351 return
352 cc = self.current_structure
353 fname = f'{cc["fname"]}_feff.inp'
354 wildcard = 'Feff Inut files (*.inp)|*.inp|All files (*.*)|*.*'
355 path = FileSave(self, message='Save Feff File',
356 wildcard=wildcard,
357 default_file=fname)
358 if path is not None:
359 with open(path, 'w', encoding=sys.getdefaultencoding()) as fh:
360 fh.write(fefftext)
361 self.write_message("Wrote Feff file %s" % path, 0)
363 def onExportStructure(self, event=None):
364 if self.current_structure is None:
365 return
367 cc = self.current_structure
368 fname = cc["fname"]
369 wildcard = f'Sturcture files (*.{cc["fmt"]})|*.{cc["fmt"]}|All files (*.*)|*.*'
370 path = FileSave(self, message='Save Structure File',
371 wildcard=wildcard,
372 default_file=fname)
374 if path is not None:
375 with open(path, 'w', encoding=sys.getdefaultencoding()) as fh:
376 fh.write(cc['structure_text'])
377 self.write_message("Wrote structure file %s" % path, 0)
379 def onImportStructure(self, event=None):
380 wildcard = 'Strucuture files (*.cif/*.postcar/*.contcar/*.chgcar/*locpot/*.cssr)|*.cif;*.postcar;*.contcar;*.chgcar;*locpot;*.cssr|Molecule files (*.xyz/*.gjf/*.g03/*.g09/*.com/*.inp)|*.xyz;*.gjf;*.g03;*.g09;*.com;*.inp|All other files readable with Openbabel (*.*)|*.*'
381 path = FileOpen(self, message='Open Structure File',
382 wildcard=wildcard, default_file='My.cif')
384 if path is not None:
385 fmt = path.split('.')[-1]
386 fname = Path(path).name
387 with open(path, 'r', encoding=sys.getdefaultencoding()) as f:
388 structure_text = f.read()
390 self.current_structure = structure2feff.parse_structure(structure_text=structure_text, fmt=fmt, fname=fname)
392 self.wids['structure_text'].SetValue(self.current_structure['structure_text'])
394 # use pytmatgen to get formula
395 elems = chemparse(self.current_structure['formula'].replace(' ', ''))
397 self.wids['central_atom'].Enable()
398 self.wids['edge'].Enable()
399 self.wids['cluster_size'].Enable()
401 self.wids['central_atom'].Clear()
402 self.wids['central_atom'].AppendItems(list(elems.keys()))
403 self.wids['central_atom'].Select(0)
407 el0 = list(elems.keys())[0]
408 edge_val = 'K' if atomic_number(el0) < 60 else 'L3'
409 self.wids['edge'].SetStringSelection(edge_val)
411 # sites
412 sites = structure2feff.structure_sites(self.current_structure['structure_text'], fmt=self.current_structure["fmt"], absorber=el0)
413 try:
414 sites = ['%d' % (i+1) for i in range(len(sites))]
415 except:
416 title = "Could not make sense of atomic sites"
417 message = [f"Elements: {list(elems.keys())}",
418 f"Sites: {sites}"]
419 ExceptionPopup(self, title, message)
422 self.wids['site'].Clear()
423 self.wids['site'].AppendItems(sites)
424 self.wids['site'].Select(0)
425 i, p = self.get_nbpage('Structure Text')
426 self.nb.SetSelection(i)
428 def onImportFeff(self, event=None):
429 wildcard = 'Feff input files (*.inp)|*.inp|All files (*.*)|*.*'
430 path = FileOpen(self, message='Open Feff Input File',
431 wildcard=wildcard, default_file='feff.inp')
432 if path is not None:
433 fefftext = None
434 fname = Path(path).name.replace('.inp', '_run')
435 fname = unique_name(fix_filename(fname), self.runs_list)
436 fefftext = read_textfile(path)
437 if fefftext is not None:
438 self.wids['feff_text'].SetValue(fefftext)
439 self.wids['run_folder'].SetValue(fname)
440 self.wids['run_feff'].Enable()
441 i, p = self.get_nbpage('Feff Input')
442 self.nb.SetSelection(i)
444 def onFeffFolder(self, eventa=None):
445 "prompt for Feff Folder"
446 dlg = wx.DirDialog(self, 'Select Main Folder for Feff Calculations',
447 style=wx.DD_DEFAULT_STYLE|wx.DD_CHANGE_DIR)
449 dlg.SetPath(self.feff_folder)
450 if dlg.ShowModal() == wx.ID_CANCEL:
451 return None
452 self.feff_folder = Path(dlg.GetPath()).absolute().as_posix()
453 mkdir(self.feff_folder)
455 def onNBChanged(self, event=None):
456 callback = getattr(self.nb.GetCurrentPage(), 'onPanelExposed', None)
457 if callable(callback):
458 callback()
460 def onSelAll(self, event=None):
461 self.controller.filelist.select_all()
463 def onSelNone(self, event=None):
464 self.controller.filelist.select_none()
466 def write_message(self, msg, panel=0):
467 """write a message to the Status Bar"""
468 self.statusbar.SetStatusText(msg, panel)
470 def createMenus(self):
471 self.menubar = wx.MenuBar()
472 fmenu = wx.Menu()
473 group_menu = wx.Menu()
474 data_menu = wx.Menu()
475 ppeak_menu = wx.Menu()
476 m = {}
478 MenuItem(self, fmenu, "&Open Structure File\tCtrl+O",
479 "Open Structure File", self.onImportStructure)
481 MenuItem(self, fmenu, "&Save Structure File\tCtrl+S",
482 "Save Structure File", self.onExportStructure)
484 MenuItem(self, fmenu, "Open Feff Input File",
485 "Open Feff input File", self.onImportFeff)
487 MenuItem(self, fmenu, "Save &Feff Inp File\tCtrl+F",
488 "Save Feff6 File", self.onExportFeff)
490 fmenu.AppendSeparator()
491 MenuItem(self, fmenu, "Select Main Feff Folder",
492 "Select Main Folder for running Feff",
493 self.onFeffFolder)
494 fmenu.AppendSeparator()
495 MenuItem(self, fmenu, "Quit", "Exit", self.onClose)
497 self.menubar.Append(fmenu, "&File")
499 self.SetMenuBar(self.menubar)
500 self.Bind(wx.EVT_CLOSE, self.onClose)
502 def onClose(self, event=None):
503 self.Destroy()
506class Structure2FeffViewer(LarchWxApp):
507 def __init__(self, filename=None, version_info=None, **kws):
508 self.filename = filename
509 LarchWxApp.__init__(self, version_info=version_info, **kws)
511 def createApp(self):
512 frame = Structure2FeffFrame(filename=self.filename,
513 version_info=self.version_info)
514 self.SetTopWindow(frame)
515 return True
517def structure_viewer(**kws):
518 Structure2FeffViewer(**kws)
520if __name__ == '__main__':
521 Structure2FeffViewer().MainLoop()