Coverage for /Users/Newville/Codes/xraylarch/larch/wxlib/structure2feff_browser.py: 13%
359 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
1#!/usr/bin/env python
2"""
3Read Structure input file, Make Feff input file, and run Feff
4"""
6import os
7import sys
8import numpy as np
9np.seterr(all='ignore')
11import wx
12import wx.lib.scrolledpanel as scrolled
13import wx.lib.agw.flatnotebook as fnb
15from xraydb.chemparser import chemparse
16from xraydb import atomic_number
18import larch
19from larch.xafs import feff8l, feff6l
20from larch.utils import unixpath, mkdir, read_textfile
21from larch.utils.strutils import fix_filename, unique_name, strict_ascii
22from larch.site_config import user_larchdir
24from larch.wxlib import (LarchFrame, FloatSpin, EditableListBox,
25 FloatCtrl, SetTip, get_icon, SimpleText, pack,
26 Button, Popup, HLine, FileSave, FileOpen, Choice,
27 Check, MenuItem, CEN, LEFT, FRAMESTYLE,
28 Font, FONTSIZE, flatnotebook, LarchUpdaterDialog,
29 PeriodicTablePanel, FeffResultsPanel, LarchWxApp,
30 ExceptionPopup, set_color)
33from larch.xrd import structure2feff
35LEFT = wx.ALIGN_LEFT
36CEN |= wx.ALL
37FNB_STYLE = fnb.FNB_NO_X_BUTTON|fnb.FNB_SMART_TABS
38FNB_STYLE |= fnb.FNB_NO_NAV_BUTTONS|fnb.FNB_NODRAG
40MAINSIZE = (850, 750)
42class Structure2FeffFrame(wx.Frame):
43 _about = """Larch structure browser for generating and running Feff.
45 Ryuichi Shimogawa <ryuichi.shimogawa@stonybrook.edu>
46 """
47 def __init__(self, parent=None, _larch=None, path_importer=None, filename=None, **kws):
48 wx.Frame.__init__(self, parent, -1, size=MAINSIZE, style=FRAMESTYLE)
50 title = "Larch FEFF Input Generator and FEFF Runner"
52 self.larch = _larch
53 if _larch is None:
54 self.larch = larch.Interpreter()
55 self.larch.eval("# started Structure browser\n")
56 self.larch.eval("if not hasattr('_sys', '_feffruns'): _sys._feffruns = {}")
57 self.path_importer = path_importer
58 self.subframes = {}
59 self.current_structure = None
60 self.SetTitle(title)
61 self.SetSize(MAINSIZE)
62 self.SetFont(Font(FONTSIZE))
63 self.createMainPanel()
64 self.createMenus()
66 self.feff_folder = unixpath(os.path.join(user_larchdir, 'feff'))
67 mkdir(self.feff_folder)
69 self.runs_list = []
70 for fname in os.listdir(self.feff_folder):
71 full = os.path.join(self.feff_folder, fname)
72 if os.path.isdir(full):
73 self.runs_list.append(fname)
75 self.statusbar = self.CreateStatusBar(2, style=wx.STB_DEFAULT_STYLE)
76 self.statusbar.SetStatusWidths([-3, -1])
77 statusbar_fields = [" ", ""]
78 for i in range(len(statusbar_fields)):
79 self.statusbar.SetStatusText(statusbar_fields[i], i)
80 self.Show()
82 def createMainPanel(self):
83 display0 = wx.Display(0)
84 client_area = display0.ClientArea
85 xmin, ymin, xmax, ymax = client_area
86 xpos = int((xmax-xmin)*0.07) + xmin
87 ypos = int((ymax-ymin)*0.09) + ymin
88 self.SetPosition((xpos, ypos))
90 # main panel with scrolled panel
91 scrolledpanel = scrolled.ScrolledPanel(self)
92 panel = wx.Panel(scrolledpanel)
93 sizer = wx.GridBagSizer(2,2)
95 wids = self.wids = {}
97 folderlab = SimpleText(panel, ' Feff Folder: ')
98 wids['run_folder'] = wx.TextCtrl(panel, value='calc1', size=(250, -1))
100 wids['run_feff'] = Button(panel, ' Run Feff ',
101 action=self.onRunFeff)
102 wids['run_feff'].Disable()
103 wids['without_h'] = Check(panel, default=True, label='Remove H atoms',
104 action=self.onGetFeff)
107 wids['central_atom'] = Choice(panel, choices=['<empty>'], size=(80, -1),
108 action=self.onCentralAtom)
109 wids['edge'] = Choice(panel, choices=['K', 'L3', 'L2', 'L1',
110 'M5', 'M4'],
111 size=(80, -1),
112 action=self.onGetFeff)
114 wids['feffvers'] = Choice(panel, choices=['6', '8'], default=1,
115 size=(80, -1),
116 action=self.onGetFeff)
117 wids['site'] = Choice(panel, choices=['1', '2', '3', '4'],
118 size=(80, -1),
119 action=self.onGetFeff)
120 wids['cluster_size'] = FloatSpin(panel, value=7.0, digits=2,
121 increment=0.1, max_val=10,
122 action=self.onGetFeff)
123 wids['central_atom'].Disable()
124 wids['edge'].Disable()
125 wids['cluster_size'].Disable()
126 catomlab = SimpleText(panel, ' Absorbing Atom: ')
127 sitelab = SimpleText(panel, ' Crystal Site: ')
128 edgelab = SimpleText(panel, ' Edge: ')
129 csizelab = SimpleText(panel, ' Cluster Size (\u212B): ')
130 fverslab = SimpleText(panel, ' Feff Version:')
132 ir = 1
134 sizer.Add(catomlab, (ir, 0), (1, 1), LEFT, 3)
135 sizer.Add(wids['central_atom'], (ir, 1), (1, 1), LEFT, 3)
136 sizer.Add(sitelab, (ir, 2), (1, 1), LEFT, 3)
137 sizer.Add(wids['site'], (ir, 3), (1, 1), LEFT, 3)
138 sizer.Add(edgelab, (ir, 4), (1, 1), LEFT, 3)
139 sizer.Add(wids['edge'], (ir, 5), (1, 1), LEFT, 3)
141 ir += 1
142 sizer.Add(csizelab, (ir, 0), (1, 1), LEFT, 3)
143 sizer.Add(wids['cluster_size'], (ir, 1), (1, 1), LEFT, 3)
144 sizer.Add(fverslab, (ir, 2), (1, 1), LEFT, 3)
145 sizer.Add(wids['feffvers'], (ir, 3), (1, 1), LEFT, 3)
146 sizer.Add(wids['without_h'], (ir, 4), (1, 2), LEFT, 3)
148 ir += 1
149 sizer.Add(folderlab, (ir, 0), (1, 1), LEFT, 3)
150 sizer.Add(wids['run_folder'], (ir, 1), (1, 4), LEFT, 3)
151 sizer.Add(wids['run_feff'], (ir, 5), (1, 1), LEFT, 3)
153 pack(panel, sizer)
155 self.nb = flatnotebook(scrolledpanel, {}, on_change=self.onNBChanged)
157 self.feffresults = FeffResultsPanel(scrolledpanel,
158 path_importer=self.path_importer,
159 _larch=self.larch)
161 structure_panel = wx.Panel(scrolledpanel)
162 wids['structure_text'] = wx.TextCtrl(structure_panel, value='<STRUCTURE TEXT>',
163 style=wx.TE_MULTILINE|wx.TE_READONLY,
164 size=(300, 350))
165 wids['structure_text'].SetFont(Font(FONTSIZE+1))
166 structure_sizer = wx.BoxSizer(wx.VERTICAL)
167 structure_sizer.Add(wids['structure_text'], 1, LEFT|wx.GROW, 1)
168 pack(structure_panel, structure_sizer)
170 feff_panel = wx.Panel(scrolledpanel)
171 wids['feff_text'] = wx.TextCtrl(feff_panel,
172 value='<Feff Input Text>',
173 style=wx.TE_MULTILINE,
174 size=(300, 350))
175 wids['feff_text'].CanCopy()
177 feff_panel.onPanelExposed = self.onGetFeff
178 wids['feff_text'].SetFont(Font(FONTSIZE+1))
179 feff_sizer = wx.BoxSizer(wx.VERTICAL)
180 feff_sizer.Add(wids['feff_text'], 1, LEFT|wx.GROW, 1)
181 pack(feff_panel, feff_sizer)
183 feffout_panel = wx.Panel(scrolledpanel)
184 wids['feffout_text'] = wx.TextCtrl(feffout_panel,
185 value='<Feff Output>',
186 style=wx.TE_MULTILINE,
187 size=(300, 350))
188 wids['feffout_text'].CanCopy()
189 wids['feffout_text'].SetFont(Font(FONTSIZE+1))
190 feffout_sizer = wx.BoxSizer(wx.VERTICAL)
191 feffout_sizer.Add(wids['feffout_text'], 1, LEFT|wx.GROW, 1)
192 pack(feffout_panel, feffout_sizer)
194 self.nbpages = []
195 for label, page in (('Structure Text', structure_panel),
196 ('Feff Input Text', feff_panel),
197 ('Feff Output Text', feffout_panel),
198 ('Feff Results', self.feffresults),
199 ):
200 self.nb.AddPage(page, label, True)
201 self.nbpages.append((label, page))
202 self.nb.SetSelection(0)
204 r_sizer = wx.BoxSizer(wx.VERTICAL)
205 r_sizer.Add(panel, 0, LEFT|wx.GROW|wx.ALL)
206 r_sizer.Add(self.nb, 1, LEFT|wx.GROW, 2)
207 pack(scrolledpanel, r_sizer)
208 scrolledpanel.SetupScrolling()
210 def get_nbpage(self, name):
211 "get nb page by name"
212 name = name.lower()
213 for i, dat in enumerate(self.nbpages):
214 label, page = dat
215 if name in label.lower():
216 return i, page
217 return (0, self.npbages[0][1])
219 def onCentralAtom(self, event=None):
220 structure = self.current_structure
221 if structure is None:
222 return
223 catom = event.GetString()
224 try:
225 sites = structure2feff.structure_sites(structure['structure_text'], absorber=catom, fmt=structure['fmt'])
226 sites = ['%d' % (i+1) for i in range(len(sites))]
227 self.wids['site'].Clear()
228 self.wids['site'].AppendItems(sites)
229 self.wids['site'].Select(0)
230 except:
231 self.write_message(f"could not get sites for central atom '{catom}'")
232 title = f"Could not get sites for central atom '{catom}'"
233 message = []
234 ExceptionPopup(self, title, message)
236 edge_val = 'K' if atomic_number(catom) < 60 else 'L3'
237 self.wids['edge'].SetStringSelection(edge_val)
238 self.onGetFeff()
240 def onGetFeff(self, event=None):
241 structure = self.current_structure
242 if structure is None:
243 return
244 edge = self.wids['edge'].GetStringSelection()
245 version8 = '8' == self.wids['feffvers'].GetStringSelection()
246 catom = self.wids['central_atom'].GetStringSelection()
247 asite = int(self.wids['site'].GetStringSelection())
248 csize = self.wids['cluster_size'].GetValue()
249 with_h = not self.wids['without_h'].IsChecked()
250 folder = f'{catom:s}{asite:d}_{edge:s}'
251 folder = unique_name(fix_filename(folder), self.runs_list)
253 fefftext = structure2feff.structure2feffinp(structure['structure_text'], catom, edge=edge,
254 cluster_size=csize,
255 absorber_site=asite,
256 version8=version8,
257 with_h=with_h,
258 fmt=structure['fmt'])
260 self.wids['run_folder'].SetValue(folder)
261 self.wids['feff_text'].SetValue(fefftext)
262 self.wids['run_feff'].Enable()
263 i, p = self.get_nbpage('Feff Input')
264 self.nb.SetSelection(i)
266 def onRunFeff(self, event=None):
267 fefftext = self.wids['feff_text'].GetValue()
268 if len(fefftext) < 100 or 'ATOMS' not in fefftext:
269 return
271 structure_text = self.wids['structure_text'].GetValue()
272 structure = self.current_structure
273 structure_fname = None
275 if structure is not None:
276 structure_fname = structure['fname']
278 version8 = '8' == self.wids['feffvers'].GetStringSelection()
280 fname = self.wids['run_folder'].GetValue()
281 fname = unique_name(fix_filename(fname), self.runs_list)
282 self.runs_list.append(fname)
283 folder = unixpath(os.path.join(self.feff_folder, fname))
284 mkdir(folder)
286 ix, p = self.get_nbpage('Feff Output')
287 self.nb.SetSelection(ix)
289 self.folder = folder
290 out = self.wids['feffout_text']
291 out.Clear()
292 out.SetInsertionPoint(0)
293 out.WriteText(f'########\n###\n# Run Feff in folder: {folder:s}\n')
294 out.SetInsertionPoint(out.GetLastPosition())
295 out.WriteText('###\n########\n')
296 out.SetInsertionPoint(out.GetLastPosition())
298 fname = unixpath(os.path.join(folder, 'feff.inp'))
299 with open(fname, 'w', encoding=sys.getdefaultencoding()) as fh:
300 fh.write(strict_ascii(fefftext))
302 if structure_fname is not None:
303 cname = unixpath(os.path.join(folder, structure_fname))
304 with open(cname, 'w', encoding=sys.getdefaultencoding()) as fh:
305 fh.write(strict_ascii(structure_text))
307 wx.CallAfter(self.run_feff, folder, version8=version8)
309 def run_feff(self, folder=None, version8=True):
310 _, dname = os.path.split(folder)
311 prog, cmd = feff8l, 'feff8l'
312 if not version8:
313 prog, cmd = feff6l, 'feff6l'
314 command = f"{cmd:s}(folder='{folder:s}')"
315 self.larch.eval(f"## running Feff as:\n# {command:s}\n##\n")
317 prog(folder=folder, message_writer=self.feff_output)
318 self.larch.eval("## gathering results:\n")
319 self.larch.eval(f"_sys._feffruns['{dname:s}'] = get_feff_pathinfo('{folder:s}')")
320 this_feffrun = self.larch.symtable._sys._feffruns[f'{dname:s}']
321 self.feffresults.set_feffresult(this_feffrun)
322 ix, p = self.get_nbpage('Feff Results')
323 self.nb.SetSelection(ix)
325 # clean up unused, intermediate Feff files
326 for fname in os.listdir(folder):
327 if (fname.endswith('.json') or fname.endswith('.pad') or
328 fname.endswith('.bin') or fname.startswith('log') or
329 fname in ('chi.dat', 'xmu.dat', 'misc.dat')):
330 os.unlink(unixpath(os.path.join(folder, fname)))
332 def feff_output(self, text):
333 out = self.wids['feffout_text']
334 ix, p = self.get_nbpage('Feff Output')
335 self.nb.SetSelection(ix)
336 pos0 = out.GetLastPosition()
337 if not text.endswith('\n'):
338 text = '%s\n' % text
339 out.WriteText(text)
340 out.SetInsertionPoint(out.GetLastPosition())
341 out.Update()
342 out.Refresh()
344 def onExportFeff(self, event=None):
345 if self.current_structure is None:
346 return
347 fefftext = self.wids['feff_text'].GetValue()
348 if len(fefftext) < 20:
349 return
350 cc = self.current_structure
351 fname = f'{cc["fname"]}_feff.inp'
352 wildcard = 'Feff Inut files (*.inp)|*.inp|All files (*.*)|*.*'
353 path = FileSave(self, message='Save Feff File',
354 wildcard=wildcard,
355 default_file=fname)
356 if path is not None:
357 with open(path, 'w', encoding=sys.getdefaultencoding()) as fh:
358 fh.write(fefftext)
359 self.write_message("Wrote Feff file %s" % path, 0)
361 def onExportStructure(self, event=None):
362 if self.current_structure is None:
363 return
365 cc = self.current_structure
366 fname = cc["fname"]
367 wildcard = f'Sturcture files (*.{cc["fmt"]})|*.{cc["fmt"]}|All files (*.*)|*.*'
368 path = FileSave(self, message='Save Structure File',
369 wildcard=wildcard,
370 default_file=fname)
372 if path is not None:
373 with open(path, 'w', encoding=sys.getdefaultencoding()) as fh:
374 fh.write(cc['structure_text'])
375 self.write_message("Wrote structure file %s" % path, 0)
377 def onImportStructure(self, event=None):
378 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 (*.*)|*.*'
379 path = FileOpen(self, message='Open Structure File',
380 wildcard=wildcard, default_file='My.cif')
382 if path is not None:
384 fmt = path.split('.')[-1]
385 fname = os.path.basename(path)
387 with open(path, 'r', encoding=sys.getdefaultencoding()) as f:
388 structure_text = f.read()
391 self.current_structure = structure2feff.parse_structure(structure_text=structure_text, fmt=fmt, fname=fname)
393 self.wids['structure_text'].SetValue(self.current_structure['structure_text'])
395 # use pytmatgen to get formula
396 elems = chemparse(self.current_structure['formula'].replace(' ', ''))
398 self.wids['central_atom'].Enable()
399 self.wids['edge'].Enable()
400 self.wids['cluster_size'].Enable()
402 self.wids['central_atom'].Clear()
403 self.wids['central_atom'].AppendItems(list(elems.keys()))
404 self.wids['central_atom'].Select(0)
408 el0 = list(elems.keys())[0]
409 edge_val = 'K' if atomic_number(el0) < 60 else 'L3'
410 self.wids['edge'].SetStringSelection(edge_val)
412 # sites
413 sites = structure2feff.structure_sites(self.current_structure['structure_text'], fmt=self.current_structure["fmt"], absorber=el0)
414 try:
415 sites = ['%d' % (i+1) for i in range(len(sites))]
416 except:
417 title = "Could not make sense of atomic sites"
418 message = [f"Elements: {list(elems.keys())}",
419 f"Sites: {sites}"]
420 ExceptionPopup(self, title, message)
423 self.wids['site'].Clear()
424 self.wids['site'].AppendItems(sites)
425 self.wids['site'].Select(0)
426 i, p = self.get_nbpage('Structure Text')
427 self.nb.SetSelection(i)
429 def onImportFeff(self, event=None):
430 wildcard = 'Feff input files (*.inp)|*.inp|All files (*.*)|*.*'
431 path = FileOpen(self, message='Open Feff Input File',
432 wildcard=wildcard, default_file='feff.inp')
433 if path is not None:
434 fefftext = None
435 _, fname = os.path.split(path)
436 fname = fname.replace('.inp', '_run')
437 fname = unique_name(fix_filename(fname), self.runs_list)
438 fefftext = read_textfile(path)
439 if fefftext is not None:
440 self.wids['feff_text'].SetValue(fefftext)
441 self.wids['run_folder'].SetValue(fname)
442 self.wids['run_feff'].Enable()
443 i, p = self.get_nbpage('Feff Input')
444 self.nb.SetSelection(i)
446 def onFeffFolder(self, eventa=None):
447 "prompt for Feff Folder"
448 dlg = wx.DirDialog(self, 'Select Main Folder for Feff Calculations',
449 style=wx.DD_DEFAULT_STYLE|wx.DD_CHANGE_DIR)
451 dlg.SetPath(self.feff_folder)
452 if dlg.ShowModal() == wx.ID_CANCEL:
453 return None
454 self.feff_folder = os.path.abspath(dlg.GetPath())
455 mkdir(self.feff_folder)
457 def onNBChanged(self, event=None):
458 callback = getattr(self.nb.GetCurrentPage(), 'onPanelExposed', None)
459 if callable(callback):
460 callback()
462 def onSelAll(self, event=None):
463 self.controller.filelist.select_all()
465 def onSelNone(self, event=None):
466 self.controller.filelist.select_none()
468 def write_message(self, msg, panel=0):
469 """write a message to the Status Bar"""
470 self.statusbar.SetStatusText(msg, panel)
472 def createMenus(self):
473 self.menubar = wx.MenuBar()
474 fmenu = wx.Menu()
475 group_menu = wx.Menu()
476 data_menu = wx.Menu()
477 ppeak_menu = wx.Menu()
478 m = {}
480 MenuItem(self, fmenu, "&Open Structure File\tCtrl+O",
481 "Open Structure File", self.onImportStructure)
483 MenuItem(self, fmenu, "&Save Structure File\tCtrl+S",
484 "Save Structure File", self.onExportStructure)
486 MenuItem(self, fmenu, "Open Feff Input File",
487 "Open Feff input File", self.onImportFeff)
489 MenuItem(self, fmenu, "Save &Feff Inp File\tCtrl+F",
490 "Save Feff6 File", self.onExportFeff)
492 fmenu.AppendSeparator()
493 MenuItem(self, fmenu, "Select Main Feff Folder",
494 "Select Main Folder for running Feff",
495 self.onFeffFolder)
496 fmenu.AppendSeparator()
497 MenuItem(self, fmenu, "Quit", "Exit", self.onClose)
499 self.menubar.Append(fmenu, "&File")
501 self.SetMenuBar(self.menubar)
502 self.Bind(wx.EVT_CLOSE, self.onClose)
504 def onClose(self, event=None):
505 self.Destroy()
508class Structure2FeffViewer(LarchWxApp):
509 def __init__(self, filename=None, version_info=None, **kws):
510 self.filename = filename
511 LarchWxApp.__init__(self, version_info=version_info, **kws)
513 def createApp(self):
514 frame = Structure2FeffFrame(filename=self.filename,
515 version_info=self.version_info)
516 self.SetTopWindow(frame)
517 return True
519def structure_viewer(**kws):
520 Structure2FeffViewer(**kws)
522if __name__ == '__main__':
523 Structure2FeffViewer().MainLoop()