Coverage for /Users/Newville/Codes/xraylarch/larch/wxlib/cif_browser.py: 11%
559 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"""
3Browse CIF Files, maybe run Feff
4"""
6import os
7import sys
8import time
9import copy
10# from threading import Thread
11import numpy as np
12np.seterr(all='ignore')
14from functools import partial
15import wx
16import wx.lib.scrolledpanel as scrolled
17import wx.lib.agw.flatnotebook as fnb
18from wx.adv import AboutBox, AboutDialogInfo
19from matplotlib.ticker import FuncFormatter
21from wxmplot import PlotPanel
22from xraydb.chemparser import chemparse
23from xraydb import atomic_number
25import larch
26from larch import Group
27from larch.xafs import feff8l, feff6l
28from larch.xrd.cif2feff import cif_sites
29from larch.utils import read_textfile, mkdir
30from larch.utils.paths import unixpath
31from larch.utils.strutils import fix_filename, unique_name, strict_ascii
32from larch.site_config import user_larchdir
34from larch.wxlib import (LarchFrame, FloatSpin, EditableListBox,
35 FloatCtrl, SetTip, get_icon, SimpleText, pack,
36 Button, Popup, HLine, FileSave, FileOpen, Choice,
37 Check, MenuItem, CEN, LEFT, FRAMESTYLE,
38 Font, FONTSIZE, flatnotebook, LarchUpdaterDialog,
39 PeriodicTablePanel, FeffResultsPanel, LarchWxApp,
40 ExceptionPopup, set_color)
42from larch.xrd import CifStructure, get_amcsd, find_cifs, get_cif, parse_cif_file
44LEFT = wx.ALIGN_LEFT
45CEN |= wx.ALL
46FNB_STYLE = fnb.FNB_NO_X_BUTTON|fnb.FNB_SMART_TABS
47FNB_STYLE |= fnb.FNB_NO_NAV_BUTTONS|fnb.FNB_NODRAG
49MAINSIZE = (1000, 650)
51class CIFFrame(wx.Frame):
52 _about = """Larch Crystallographic Information File Browser
53 Data from American Mineralogist Crystal Structure Database
55 Matt Newville <newville @ cars.uchicago.edu>
56 """
58 def __init__(self, parent=None, _larch=None, with_feff=False,
59 with_fdmnes=False, usecif_callback=None, path_importer=None,
60 filename=None, **kws):
62 wx.Frame.__init__(self, parent, -1, size=MAINSIZE, style=FRAMESTYLE)
64 title = "Larch American Mineralogist CIF Browser"
65 self.with_feff = with_feff
66 self.with_fdmnes = with_fdmnes
67 self.usecif_callback = usecif_callback
68 self.larch = _larch
69 if _larch is None:
70 self.larch = larch.Interpreter()
71 self.larch.eval("# started CIF browser\n")
73 self.path_importer = path_importer
74 self.cifdb = get_amcsd()
75 self.all_minerals = self.cifdb.all_minerals()
76 self.subframes = {}
77 self.has_xrd1d = False
78 self.xrd1d_thread = None
79 self.current_cif = None
80 self.SetTitle(title)
81 self.SetSize(MAINSIZE)
82 self.SetFont(Font(FONTSIZE))
84 self.createMainPanel()
85 self.createMenus()
87 if with_feff:
88 self.larch.eval("if not hasattr('_sys', '_feffruns'): _sys._feffruns = {}")
89 self.feff_folder = unixpath(os.path.join(user_larchdir, 'feff'))
90 mkdir(self.feff_folder)
91 self.feffruns_list = []
92 for fname in os.listdir(self.feff_folder):
93 full = os.path.join(self.feff_folder, fname)
94 if os.path.isdir(full):
95 self.feffruns_list.append(fname)
97 self.statusbar = self.CreateStatusBar(2, style=wx.STB_DEFAULT_STYLE)
98 self.statusbar.SetStatusWidths([-3, -1])
99 statusbar_fields = [" ", ""]
100 for i in range(len(statusbar_fields)):
101 self.statusbar.SetStatusText(statusbar_fields[i], i)
102 self.Show()
104 def createMainPanel(self):
105 display0 = wx.Display(0)
106 client_area = display0.ClientArea
107 xmin, ymin, xmax, ymax = client_area
108 xpos = int((xmax-xmin)*0.07) + xmin
109 ypos = int((ymax-ymin)*0.09) + ymin
110 self.SetPosition((xpos, ypos))
112 splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
113 splitter.SetMinimumPaneSize(250)
115 leftpanel = wx.Panel(splitter)
116 self.ciflist = EditableListBox(leftpanel,
117 self.onShowCIF, size=(300,-1))
118 set_color(self.ciflist, 'list_fg', bg='list_bg')
119 self.cif_selections = {}
121 sizer = wx.BoxSizer(wx.VERTICAL)
122 sizer.Add(self.ciflist, 1, LEFT|wx.GROW|wx.ALL, 1)
123 pack(leftpanel, sizer)
125 # right hand side
126 rightpanel = scrolled.ScrolledPanel(splitter)
127 panel = wx.Panel(rightpanel)
128 sizer = wx.GridBagSizer(2, 2)
130 self.title = SimpleText(panel, 'Search American Mineralogical CIF Database:',
131 size=(700, -1), style=LEFT)
132 self.title.SetFont(Font(FONTSIZE+2))
133 wids = self.wids = {}
136 minlab = SimpleText(panel, ' Mineral Name: ')
137 minhint= SimpleText(panel, ' example: hem* ')
138 wids['mineral'] = wx.TextCtrl(panel, value='', size=(250, -1),
139 style=wx.TE_PROCESS_ENTER)
140 wids['mineral'].Bind(wx.EVT_TEXT_ENTER, self.onSearch)
142 authlab = SimpleText(panel, ' Author Name: ')
143 wids['author'] = wx.TextCtrl(panel, value='', size=(250, -1),
144 style=wx.TE_PROCESS_ENTER)
145 wids['author'].Bind(wx.EVT_TEXT_ENTER, self.onSearch)
147 journlab = SimpleText(panel, ' Journal Name: ')
148 wids['journal'] = wx.TextCtrl(panel, value='', size=(250, -1),
149 style=wx.TE_PROCESS_ENTER)
150 wids['journal'].Bind(wx.EVT_TEXT_ENTER, self.onSearch)
152 elemlab = SimpleText(panel, ' Include Elements: ')
153 elemhint= SimpleText(panel, ' example: O, Fe, Si ')
155 wids['contains_elements'] = wx.TextCtrl(panel, value='', size=(250, -1),
156 style=wx.TE_PROCESS_ENTER)
157 wids['contains_elements'].Bind(wx.EVT_TEXT_ENTER, self.onSearch)
159 exelemlab = SimpleText(panel, ' Exclude Elements: ')
160 wids['excludes_elements'] = wx.TextCtrl(panel, value='', size=(250, -1),
161 style=wx.TE_PROCESS_ENTER)
162 wids['excludes_elements'].Bind(wx.EVT_TEXT_ENTER, self.onSearch)
164 wids['excludes_elements'].Enable()
165 wids['strict_contains'] = Check(panel, default=False,
166 label='Include only the elements listed',
167 action=self.onStrict)
169 wids['full_occupancy'] = Check(panel, default=False,
170 label='Only Structures with Full Occupancy')
172 wids['search'] = Button(panel, 'Search for CIFs', action=self.onSearch)
176 ir = 0
177 sizer.Add(self.title, (0, 0), (1, 6), LEFT, 2)
179 ir += 1
180 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 6), LEFT, 3)
182 ir += 1
183 sizer.Add(minlab, (ir, 0), (1, 1), LEFT, 3)
184 sizer.Add(wids['mineral'], (ir, 1), (1, 3), LEFT, 3)
185 sizer.Add(minhint, (ir, 4), (1, 2), LEFT, 3)
186 ir += 1
187 sizer.Add(authlab, (ir, 0), (1, 1), LEFT, 3)
188 sizer.Add(wids['author'], (ir, 1), (1, 3), LEFT, 3)
190 ir += 1
191 sizer.Add(journlab, (ir, 0), (1, 1), LEFT, 3)
192 sizer.Add(wids['journal'], (ir, 1), (1, 3), LEFT, 3)
194 ir += 1
195 sizer.Add(elemlab, (ir, 0), (1, 1), LEFT, 3)
196 sizer.Add(wids['contains_elements'], (ir, 1), (1, 3), LEFT, 3)
197 sizer.Add(elemhint, (ir, 4), (1, 3), LEFT, 2)
199 ir += 1
200 sizer.Add(exelemlab, (ir, 0), (1, 1), LEFT, 3)
201 sizer.Add(wids['excludes_elements'], (ir, 1), (1, 3), LEFT, 3)
203 ir += 1
204 sizer.Add(wids['search'], (ir, 0), (1, 1), LEFT, 3)
205 sizer.Add(wids['strict_contains'], (ir, 1), (1, 4), LEFT, 3)
207 ir += 1
208 sizer.Add(wids['full_occupancy'], (ir, 1), (1, 4), LEFT, 3)
210 #
211 if self.with_feff:
212 wids['feff_runfolder'] = wx.TextCtrl(panel, value='calc1', size=(250, -1))
213 wids['feff_runbutton'] = Button(panel, ' Run Feff ', action=self.onRunFeff)
214 wids['feff_runbutton'].Disable()
215 wids['feff_without_h'] = Check(panel, default=True, label='Remove H atoms',
216 action=self.onGetFeff)
219 wids['feff_central_atom'] = Choice(panel, choices=['<empty>'], size=(80, -1),
220 action=self.onFeffCentralAtom)
221 wids['feff_edge'] = Choice(panel, choices=['K', 'L3', 'L2', 'L1',
222 'M5', 'M4'],
223 size=(80, -1),
224 action=self.onGetFeff)
226 wids['feffvers'] = Choice(panel, choices=['6', '8'], default=1,
227 size=(80, -1),
228 action=self.onGetFeff)
229 wids['feff_site'] = Choice(panel, choices=['1', '2', '3', '4'],
230 size=(80, -1),
231 action=self.onGetFeff)
232 wids['feff_cluster_size'] = FloatSpin(panel, value=7.0, digits=2,
233 increment=0.1, max_val=10,
234 action=self.onGetFeff)
235 wids['feff_central_atom'].Disable()
236 wids['feff_edge'].Disable()
237 wids['feff_cluster_size'].Disable()
239 ir += 1
240 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 6), LEFT, 3)
242 ir += 1
244 sizer.Add(SimpleText(panel, ' Absorbing Atom: '), (ir, 0), (1, 1), LEFT, 3)
245 sizer.Add(wids['feff_central_atom'], (ir, 1), (1, 1), LEFT, 3)
246 sizer.Add(SimpleText(panel, ' Crystal Site: '), (ir, 2), (1, 1), LEFT, 3)
247 sizer.Add(wids['feff_site'], (ir, 3), (1, 1), LEFT, 3)
248 sizer.Add(SimpleText(panel, ' Edge: '), (ir, 4), (1, 1), LEFT, 3)
249 sizer.Add(wids['feff_edge'], (ir, 5), (1, 1), LEFT, 3)
251 ir += 1
252 sizer.Add(SimpleText(panel, ' Cluster Size (\u212B): '), (ir, 0), (1, 1), LEFT, 3)
253 sizer.Add(wids['feff_cluster_size'], (ir, 1), (1, 1), LEFT, 3)
254 sizer.Add(SimpleText(panel, ' Feff Version:'), (ir, 2), (1, 1), LEFT, 3)
255 sizer.Add(wids['feffvers'], (ir, 3), (1, 1), LEFT, 3)
256 sizer.Add(wids['feff_without_h'], (ir, 4), (1, 2), LEFT, 3)
258 ir += 1
259 sizer.Add(SimpleText(panel, ' Feff Folder: '), (ir, 0), (1, 1), LEFT, 3)
260 sizer.Add(wids['feff_runfolder'], (ir, 1), (1, 4), LEFT, 3)
261 sizer.Add(wids['feff_runbutton'], (ir, 5), (1, 1), LEFT, 3)
263 if self.usecif_callback is not None:
264 wids['cif_use_button'] = Button(panel, ' Use This CIF', action=self.onUseCIF)
265 wids['cif_use_button'].Disable()
267 ir += 1
268 sizer.Add(wids['cif_use_button'], (ir, 5), (1, 1), LEFT, 3)
271 ir += 1
272 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 6), LEFT, 3)
274 pack(panel, sizer)
276 self.nb = flatnotebook(rightpanel, {}, on_change=self.onNBChanged)
279 def _swallow_plot_messages(s, panel=0):
280 pass
282 self.plotpanel = PlotPanel(rightpanel, messenger=_swallow_plot_messages)
283 try:
284 plotopts = self.larch.symtable._sys.wx.plotopts
285 self.plotpanel.conf.set_theme(plotopts['theme'])
286 self.plotpanel.conf.enable_grid(plotopts['show_grid'])
287 except:
288 pass
290 self.plotpanel.SetMinSize((250, 250))
291 self.plotpanel.SetMaxSize((675, 400))
292 self.plotpanel.onPanelExposed = self.showXRD1D
294 cif_panel = wx.Panel(rightpanel)
295 wids['cif_text'] = wx.TextCtrl(cif_panel, value='<CIF TEXT>',
296 style=wx.TE_MULTILINE|wx.TE_READONLY,
297 size=(700, 450))
298 wids['cif_text'].SetFont(Font(FONTSIZE+1))
299 cif_sizer = wx.BoxSizer(wx.VERTICAL)
300 cif_sizer.Add(wids['cif_text'], 0, LEFT, 1)
301 pack(cif_panel, cif_sizer)
304 self.nbpages = []
305 for label, page in (('CIF Text', cif_panel),
306 ('1-D XRD Pattern', self.plotpanel),
307 ):
308 self.nb.AddPage(page, label, True)
309 self.nbpages.append((label, page))
311 if self.with_feff:
312 self.feffresults = FeffResultsPanel(rightpanel,
313 path_importer=self.path_importer,
314 _larch=self.larch)
316 feffinp_panel = wx.Panel(rightpanel)
317 wids['feff_text'] = wx.TextCtrl(feffinp_panel,
318 value='<Feff Input Text>',
319 style=wx.TE_MULTILINE,
320 size=(700, 450))
321 wids['feff_text'].CanCopy()
323 feffinp_panel.onPanelExposed = self.onGetFeff
324 wids['feff_text'].SetFont(Font(FONTSIZE+1))
325 feff_sizer = wx.BoxSizer(wx.VERTICAL)
326 feff_sizer.Add(wids['feff_text'], 0, LEFT, 1)
327 pack(feffinp_panel, feff_sizer)
329 feffout_panel = wx.Panel(rightpanel)
330 wids['feffout_text'] = wx.TextCtrl(feffout_panel,
331 value='<Feff Output>',
332 style=wx.TE_MULTILINE,
333 size=(700, 450))
334 wids['feffout_text'].CanCopy()
335 wids['feffout_text'].SetFont(Font(FONTSIZE+1))
336 feffout_sizer = wx.BoxSizer(wx.VERTICAL)
337 feffout_sizer.Add(wids['feffout_text'], 0, LEFT, 1)
338 pack(feffout_panel, feffout_sizer)
340 for label, page in (('Feff Input Text', feffinp_panel),
341 ('Feff Output Text', feffout_panel),
342 ('Feff Results', self.feffresults),
343 ):
344 self.nb.AddPage(page, label, True)
345 self.nbpages.append((label, page))
346 self.nb.SetSelection(0)
348 r_sizer = wx.BoxSizer(wx.VERTICAL)
349 r_sizer.Add(panel, 0, LEFT|wx.GROW|wx.ALL)
350 r_sizer.Add(self.nb, 1, LEFT|wx.GROW, 2)
351 pack(rightpanel, r_sizer)
352 rightpanel.SetupScrolling()
353 splitter.SplitVertically(leftpanel, rightpanel, 1)
355 def get_nbpage(self, name):
356 "get nb page by name"
357 name = name.lower()
358 for i, dat in enumerate(self.nbpages):
359 label, page = dat
360 if name in label.lower():
361 return i, page
362 return (0, self.nbpages[0][1])
364 def onStrict(self, event=None):
365 strict = self.wids['strict_contains'].IsChecked()
366 self.wids['excludes_elements'].Enable(not strict)
368 def onSearch(self, event=None):
369 mineral_name = self.wids['mineral'].GetValue().strip()
370 if len(mineral_name) < 1:
371 mineral_name = None
372 author_name = self.wids['author'].GetValue().strip()
373 if len(author_name) < 1:
374 author_name = None
375 journal_name = self.wids['journal'].GetValue().strip()
376 if len(journal_name) < 1:
377 journal_name = None
378 contains_elements = self.wids['contains_elements'].GetValue().strip()
379 if len(contains_elements) < 1:
380 contains_elements = None
381 else:
382 contains_elements = [a.strip().title() for a in contains_elements.split(',')]
383 excludes_elements = self.wids['excludes_elements'].GetValue().strip()
384 if len(excludes_elements) < 1:
385 excludes_elements = None
386 else:
387 excludes_elements = [a.strip().title() for a in excludes_elements.split(',')]
388 strict_contains = self.wids['strict_contains'].IsChecked()
389 full_occupancy = self.wids['full_occupancy'].IsChecked()
390 all_cifs = find_cifs(mineral_name=mineral_name,
391 journal_name=journal_name,
392 author_name=author_name,
393 contains_elements=contains_elements,
394 excludes_elements=excludes_elements,
395 strict_contains=strict_contains,
396 full_occupancy=full_occupancy)
397 if len(all_cifs) == 0:
398 all_cifs = find_cifs(mineral_name=mineral_name + '*',
399 journal_name=journal_name,
400 author_name=author_name,
401 contains_elements=contains_elements,
402 excludes_elements=excludes_elements,
403 strict_contains=strict_contains,
404 full_occupancy=full_occupancy)
405 self.cif_selections = {}
406 self.ciflist.Clear()
407 for cif in all_cifs:
408 try:
409 label = cif.formula.replace(' ', '')
410 mineral = cif.get_mineralname()
411 year = cif.publication.year
412 journal= cif.publication.journalname
413 label = f'{label}: {mineral}, {year} {journal}'
414 except:
415 label = None
416 if label is not None:
417 self.cif_selections[label] = cif.ams_id
418 self.ciflist.Append(label)
420 def onShowCIF(self, event=None, cif_id=None):
421 if cif_id is not None:
422 cif = get_cif(cif_id)
423 self.cif_label = '%d' % cif_id
424 elif event is not None:
425 self.cif_label = event.GetString()
426 cif = get_cif(self.cif_selections[self.cif_label])
427 self.current_cif = cif
428 self.has_xrd1d = False
429 self.wids['cif_text'].SetValue(cif.ciftext)
431 if self.with_feff:
432 elems = chemparse(cif.formula.replace(' ', ''))
433 self.wids['feff_central_atom'].Enable()
434 self.wids['feff_edge'].Enable()
435 self.wids['feff_cluster_size'].Enable()
437 self.wids['feff_central_atom'].Clear()
438 self.wids['feff_central_atom'].AppendItems(list(elems.keys()))
439 self.wids['feff_central_atom'].Select(0)
441 el0 = list(elems.keys())[0]
442 edge_val = 'K' if atomic_number(el0) < 60 else 'L3'
443 self.wids['feff_edge'].SetStringSelection(edge_val)
445 sites = cif_sites(cif.ciftext, absorber=el0)
446 try:
447 sites = ['%d' % (i+1) for i in range(len(sites))]
448 except:
449 title = "Could not make sense of atomic sites"
450 message = [f"Elements: {list(elems.keys())}",
451 f"Sites: {sites}"]
452 ExceptionPopup(self, title, message)
454 self.wids['feff_site'].Clear()
455 self.wids['feff_site'].AppendItems(sites)
456 self.wids['feff_site'].Select(0)
458 if self.usecif_callback is not None:
459 self.wids['cif_use_button'].Enable()
461 i, p = self.get_nbpage('CIF Text')
462 self.nb.SetSelection(i)
464 def onUseCIF(self, event=None):
465 if self.usecif_callback is not None:
466 self.usecif_callback(cif=self.current_cif)
469 def onFeffCentralAtom(self, event=None):
470 cif = self.current_cif
471 if cif is None:
472 return
473 catom = event.GetString()
474 try:
475 sites = cif_sites(cif.ciftext, absorber=catom)
476 sites = ['%d' % (i+1) for i in range(len(sites))]
477 self.wids['feff_site'].Clear()
478 self.wids['feff_site'].AppendItems(sites)
479 self.wids['feff_site'].Select(0)
480 except:
481 self.write_message(f"could not get sites for central atom '{catom}'")
482 title = f"Could not get sites for central atom '{catom}'"
483 message = []
484 ExceptionPopup(self, title, message)
486 edge_val = 'K' if atomic_number(catom) < 60 else 'L3'
487 self.wids['feff_edge'].SetStringSelection(edge_val)
488 self.onGetFeff()
490 def onGetFeff(self, event=None):
491 cif = self.current_cif
492 if cif is None or not self.with_feff:
493 return
495 edge = self.wids['feff_edge'].GetStringSelection()
496 version8 = '8' == self.wids['feffvers'].GetStringSelection()
497 catom = self.wids['feff_central_atom'].GetStringSelection()
498 asite = int(self.wids['feff_site'].GetStringSelection())
499 csize = self.wids['feff_cluster_size'].GetValue()
500 with_h = not self.wids['feff_without_h'].IsChecked()
501 mineral = cif.get_mineralname()
502 folder = f'{catom:s}{asite:d}_{edge:s}_{mineral}_cif{cif.ams_id:d}'
503 folder = unique_name(fix_filename(folder), self.feffruns_list)
505 fefftext = cif.get_feffinp(catom, edge=edge, cluster_size=csize,
506 absorber_site=asite, version8=version8,
507 with_h=with_h)
509 self.wids['feff_runfolder'].SetValue(folder)
510 self.wids['feff_text'].SetValue(fefftext)
511 self.wids['feff_runbutton'].Enable()
512 i, p = self.get_nbpage('Feff Input')
513 self.nb.SetSelection(i)
515 def onRunFeff(self, event=None):
516 fefftext = self.wids['feff_text'].GetValue()
517 if len(fefftext) < 100 or 'ATOMS' not in fefftext or not self.with_feff:
518 return
520 ciftext = self.wids['cif_text'].GetValue()
521 cif = self.current_cif
522 cif_fname = None
523 if cif is not None and len(ciftext) > 100:
524 mineral = cif.get_mineralname()
525 cif_fname = f'{mineral}_cif{cif.ams_id:d}.cif'
527 # cc = self.current_cif
528 # edge = self.wids['feff_edge'].GetStringSelection()
529 # catom = self.wids['feff_central_atom'].GetStringSelection()
530 # asite = int(self.wids['feff_site'].GetStringSelection())
531 # mineral = cc.get_mineralname()
532 # folder = f'{catom:s}{asite:d}_{edge:s}_{mineral}_cif{cc.ams_id:d}'
533 # folder = unixpath(os.path.join(self.feff_folder, folder))
534 version8 = '8' == self.wids['feffvers'].GetStringSelection()
536 fname = self.wids['feff_runfolder'].GetValue()
537 fname = unique_name(fix_filename(fname), self.feffruns_list)
538 self.feffruns_list.append(fname)
539 self.folder = folder = unixpath(os.path.join(self.feff_folder, fname))
540 mkdir(self.folder)
541 ix, p = self.get_nbpage('Feff Output')
542 self.nb.SetSelection(ix)
544 out = self.wids['feffout_text']
545 out.Clear()
546 out.SetInsertionPoint(0)
547 out.WriteText(f'########\n###\n# Run Feff in folder: {folder:s}\n')
548 out.SetInsertionPoint(out.GetLastPosition())
549 out.WriteText('###\n########\n')
550 out.SetInsertionPoint(out.GetLastPosition())
552 fname = unixpath(os.path.join(folder, 'feff.inp'))
553 with open(fname, 'w', encoding=sys.getdefaultencoding()) as fh:
554 fh.write(strict_ascii(fefftext))
556 if cif_fname is not None:
557 cname = unixpath(os.path.join(folder, fix_filename(cif_fname)))
558 with open(cname, 'w', encoding=sys.getdefaultencoding()) as fh:
559 fh.write(strict_ascii(ciftext))
560 wx.CallAfter(self.run_feff, folder, version8=version8)
562 def run_feff(self, folder=None, version8=True):
563 print("RUN FEFF ", folder)
564 _, dname = os.path.split(folder)
565 prog, cmd = feff8l, 'feff8l'
566 if not version8:
567 prog, cmd = feff6l, 'feff6l'
568 command = f"{cmd:s}(folder='{folder:s}')"
569 self.larch.eval(f"## running Feff as:\n# {command:s}\n##\n")
571 prog(folder=folder, message_writer=self.feff_output)
572 self.larch.eval("## gathering results:\n")
573 self.larch.eval(f"_sys._feffruns['{dname:s}'] = get_feff_pathinfo('{folder:s}')")
574 this_feffrun = self.larch.symtable._sys._feffruns[f'{dname:s}']
575 self.feffresults.set_feffresult(this_feffrun)
576 ix, p = self.get_nbpage('Feff Results')
577 self.nb.SetSelection(ix)
579 # clean up unused, intermediate Feff files
580 for fname in os.listdir(folder):
581 if (fname.endswith('.json') or fname.endswith('.pad') or
582 fname.endswith('.bin') or fname.startswith('log') or
583 fname in ('chi.dat', 'xmu.dat', 'misc.dat')):
584 os.unlink(unixpath(os.path.join(folder, fname)))
586 def feff_output(self, text):
587 out = self.wids['feffout_text']
588 ix, p = self.get_nbpage('Feff Output')
589 self.nb.SetSelection(ix)
590 pos0 = out.GetLastPosition()
591 if not text.endswith('\n'):
592 text = '%s\n' % text
593 out.WriteText(text)
594 out.SetInsertionPoint(out.GetLastPosition())
595 out.Update()
596 out.Refresh()
598 def onExportFeff(self, event=None):
599 if self.current_cif is None:
600 return
601 fefftext = self.wids['feff_text'].GetValue()
602 if len(fefftext) < 20:
603 return
604 cc = self.current_cif
605 minname = cc.get_mineralname()
606 fname = f'{minname}_cif{cc.ams_id:d}_feff.inp'
607 wildcard = 'Feff Inut files (*.inp)|*.inp|All files (*.*)|*.*'
608 path = FileSave(self, message='Save Feff File',
609 wildcard=wildcard,
610 default_file=fname)
611 if path is not None:
612 with open(path, 'w', encoding=sys.getdefaultencoding()) as fh:
613 fh.write(fefftext)
614 self.write_message("Wrote Feff file %s" % path, 0)
617 def onExportCIF(self, event=None):
618 if self.current_cif is None:
619 return
620 cc = self.current_cif
621 minname = cc.get_mineralname()
622 fname = f'{minname}_cif{cc.ams_id:d}.cif'
623 wildcard = 'CIF files (*.cif)|*.cif|All files (*.*)|*.*'
624 path = FileSave(self, message='Save CIF File',
625 wildcard=wildcard,
626 default_file=fname)
627 if path is not None:
628 with open(path, 'w', encoding=sys.getdefaultencoding()) as fh:
629 fh.write(cc.ciftext)
630 self.write_message("Wrote CIF file %s" % path, 0)
632 def onImportCIF(self, event=None):
633 wildcard = 'CIF files (*.cif)|*.cif|All files (*.*)|*.*'
634 path = FileOpen(self, message='Open CIF File',
635 wildcard=wildcard, default_file='My.cif')
637 if path is not None:
638 try:
639 cif_data = parse_cif_file(path)
640 except:
641 title = f"Cannot parse CIF file '{path}'"
642 message = [f"Error reading CIF File: {path}"]
643 ExceptionPopup(self, title, message)
644 return
646 try:
647 cif_id = self.cifdb.add_ciffile(path)
648 except:
649 title = f"Cannot add CIF from '{path}' to CIF database"
650 message = [f"Error adding CIF File to database: {path}"]
651 ExceptionPopup(self, title, message)
652 return
654 try:
655 self.onShowCIF(cif_id=cif_id)
656 except:
657 title = f"Cannot show CIF from '{path}'"
658 message = [f"Error displaying CIF File: {path}"]
659 ExceptionPopup(self, title, message)
661 def onImportFeff(self, event=None):
662 if not self.with_feff:
663 return
664 wildcard = 'Feff input files (*.inp)|*.inp|All files (*.*)|*.*'
665 path = FileOpen(self, message='Open Feff Input File',
666 wildcard=wildcard, default_file='feff.inp')
667 if path is not None:
668 fefftext = None
669 _, fname = os.path.split(path)
670 fname = fname.replace('.inp', '_run')
671 fname = unique_name(fix_filename(fname), self.feffruns_list)
672 fefftext = read_textfile(path)
673 if fefftext is not None:
674 self.wids['feff_text'].SetValue(fefftext)
675 self.wids['feff_runfolder'].SetValue(fname)
676 self.wids['feff_runbutton'].Enable()
677 i, p = self.get_nbpage('Feff Input')
678 self.nb.SetSelection(i)
680 def onFeffFolder(self, eventa=None):
681 "prompt for Feff Folder"
682 dlg = wx.DirDialog(self, 'Select Main Folder for Feff Calculations',
683 style=wx.DD_DEFAULT_STYLE|wx.DD_CHANGE_DIR)
685 dlg.SetPath(self.feff_folder)
686 if dlg.ShowModal() == wx.ID_CANCEL:
687 return None
688 self.feff_folder = os.path.abspath(dlg.GetPath())
689 mkdir(self.feff_folder)
691 def onNBChanged(self, event=None):
692 callback = getattr(self.nb.GetCurrentPage(), 'onPanelExposed', None)
693 if callable(callback):
694 callback()
696 def showXRD1D(self, event=None):
697 if self.has_xrd1d or self.current_cif is None:
698 return
700 def display_xrd1d():
701 t0 = time.time()
702 sfact = self.current_cif.get_structure_factors()
703 try:
704 self.cifdb.set_hkls(self.current_cif.ams_id, sfact.hkls)
705 except:
706 pass
708 max_ = sfact.intensity.max()
709 mask = np.where(sfact.intensity>max_/10.0)[0]
710 qval = sfact.q[mask]
711 ival = sfact.intensity[mask]
712 ival = ival/(1.0*ival.max())
714 def qd_formatter(q, pos):
715 qval = float(q)
716 dval = '\n[%.2f]' % (2*np.pi/max(qval, 1.e-6))
717 return r"%.2f%s" % (qval, dval)
719 qd_label = r'$Q\rm\,(\AA^{-1}) \,\> [d \rm\,(\AA)]$'
720 title = self.cif_label + '\n' + '(cif %d)' % (self.current_cif.ams_id)
721 ppan = self.plotpanel
722 ppan.plot(qval, ival, linewidth=0, marker='o', markersize=2,
723 xlabel=qd_label, ylabel='Relative Intensity',
724 title=title, titlefontsize=8, delay_draw=True)
726 ppan.axes.bar(qval, ival, 0.1, color='blue')
727 ppan.axes.xaxis.set_major_formatter(FuncFormatter(qd_formatter))
728 ppan.canvas.draw()
729 self.has_xrd1d = True
731 display_xrd1d()
732# self.xrd1d_thread = Thread(target=display_xrd1d)
733# self.xrd1d_thread.start()
734# time.sleep(0.25)
735# self.xrd1d_thread.join()
738 def onSelAll(self, event=None):
739 self.controller.filelist.select_all()
741 def onSelNone(self, event=None):
742 self.controller.filelist.select_none()
744 def write_message(self, msg, panel=0):
745 """write a message to the Status Bar"""
746 self.statusbar.SetStatusText(msg, panel)
748 def createMenus(self):
749 # ppnl = self.plotpanel
750 self.menubar = wx.MenuBar()
751 fmenu = wx.Menu()
752 group_menu = wx.Menu()
753 data_menu = wx.Menu()
754 ppeak_menu = wx.Menu()
755 m = {}
757 MenuItem(self, fmenu, "&Open CIF File\tCtrl+O",
758 "Open CIF File", self.onImportCIF)
760 MenuItem(self, fmenu, "&Save CIF File\tCtrl+S",
761 "Save CIF File", self.onExportCIF)
763 MenuItem(self, fmenu, "Open Feff Input File",
764 "Open Feff input File", self.onImportFeff)
766 MenuItem(self, fmenu, "Save &Feff Inp File\tCtrl+F",
767 "Save Feff6 File", self.onExportFeff)
769 fmenu.AppendSeparator()
770 MenuItem(self, fmenu, "Select Main Feff Folder",
771 "Select Main Folder for running Feff",
772 self.onFeffFolder)
773 fmenu.AppendSeparator()
774 MenuItem(self, fmenu, "Quit", "Exit", self.onClose)
776 self.menubar.Append(fmenu, "&File")
778 self.SetMenuBar(self.menubar)
779 self.Bind(wx.EVT_CLOSE, self.onClose)
781 def onClose(self, event=None):
782 self.Destroy()
785class CIFViewer(LarchWxApp):
786 def __init__(self, filename=None, version_info=None, **kws):
787 self.filename = filename
788 LarchWxApp.__init__(self, version_info=version_info, **kws)
790 def createApp(self):
791 frame = CIFFrame(filename=self.filename,
792 version_info=self.version_info)
793 self.SetTopWindow(frame)
794 return True
796def cif_viewer(**kws):
797 CIFViewer(**kws)
799if __name__ == '__main__':
800 CIFViewer().MainLoop()