Coverage for /Users/Newville/Codes/xraylarch/larch/wxxas/feffit_panel.py: 9%
1542 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
1import time
2import os
3import sys
4import ast
5import shutil
6import string
7import json
8import math
9from copy import deepcopy
10from sys import exc_info
11from string import printable
12from functools import partial
14import numpy as np
15np.seterr(all='ignore')
18import wx
19import wx.lib.scrolledpanel as scrolled
21import wx.dataview as dv
23from lmfit import Parameter
24from lmfit.model import (save_modelresult, load_modelresult,
25 save_model, load_model)
27import lmfit.models as lm_models
29from larch import Group, site_config
30from larch.math import index_of
31from larch.fitting import group2params, param
32from larch.utils.jsonutils import encode4js, decode4js
33from larch.utils import fix_varname, fix_filename, gformat, mkdir
34from larch.io.export_modelresult import export_modelresult
35from larch.xafs import feffit_report, feffpath
36from larch.xafs.feffdat import FEFFDAT_VALUES
37from larch.xafs.xafsutils import FT_WINDOWS
39from larch.wxlib import (ReportFrame, BitmapButton, FloatCtrl, FloatSpin,
40 SetTip, GridPanel, get_icon, SimpleText, pack,
41 Button, HLine, Choice, Check, MenuItem, GUIColors,
42 CEN, RIGHT, LEFT, FRAMESTYLE, Font, FONTSIZE,
43 COLORS, set_color, FONTSIZE_FW, FileSave,
44 FileOpen, flatnotebook, EditableListBox, Popup,
45 ExceptionPopup)
47from larch.wxlib.parameter import ParameterWidgets
48from larch.wxlib.plotter import last_cursor_pos
49from .taskpanel import TaskPanel
51from .config import (Feffit_KWChoices, Feffit_SpaceChoices,
52 Feffit_PlotChoices, make_array_choice,
53 PlotWindowChoices)
55DVSTYLE = dv.DV_SINGLE|dv.DV_VERT_RULES|dv.DV_ROW_LINES
57# PlotOne_Choices = [chik, chirmag, chirre, chirmr]
59PlotOne_Choices = make_array_choice(['chi','chir_mag', 'chir_re', 'chir_mag+chir_re', 'chiq'])
60PlotAlt_Choices = make_array_choice(['noplot', 'chi','chir_mag', 'chir_re', 'chir_mag+chir_re'])
62# PlotAlt_Choices = [noplot] + PlotOne_Choices
64ScriptWcards = "Fit Models(*.lar)|*.lar|All files (*.*)|*.*"
66MIN_CORREL = 0.10
68COMMANDS = {}
69COMMANDS['feffit_top'] = """## saved {ctime}
70## commmands to reproduce Feffit
71## to use from python, uncomment these lines:
72#from larch.xafs import feffit, feffit_dataset, feffit_transform, feffit_report
73#from larch.xafs import pre_edge, autobk, xftf, xftr, ff2chi, feffpath
74#from larch.fitting import param_group, param
75#from larch.io import read_ascii, read_athena, read_xdi, read_specfile
76#
77#### for interactive plotting from python (but not the Larch shell!) use:
78#from larch.wxlib.xafsplots import plot_chik, plot_chir
79#from wxmplot.interactive import get_wxapp
80#wxapp = get_wxapp() # <- needed for plotting to work from python command-line
81####
82"""
84COMMANDS['data_source'] = """# you will need to add how the data chi(k) gets built:
85## data group = {groupname}
86## from source = {filename}
87## some processing steps for this group (comment out as needed):
88"""
90COMMANDS['xft'] = """# ffts on group {groupname:s}
91xftf({groupname:s}, kmin={kmin:.3f}, kmax={kmax:.3f}, dk={dk:.3f}, window='{kwindow:s}', kweight={kweight:.3f})
92xftr({groupname:s}, rmin={rmin:.3f}, rmax={rmax:.3f}, dr={dr:.3f}, window='{rwindow:s}')
93"""
95COMMANDS['feffit_params_init'] = """# create feffit Parameter Group to hold fit parameters
96_feffit_params = param_group(reff=-1.0)
97"""
99COMMANDS['feffit_trans'] = """# define Fourier transform and fitting space
100_feffit_trans = feffit_transform(kmin={fit_kmin:.3f}, kmax={fit_kmax:.3f}, dk={fit_dk:.4f}, kw={fit_kwstring:s},
101 window='{fit_kwindow:s}', fitspace='{fit_space:s}', rmin={fit_rmin:.3f}, rmax={fit_rmax:.3f})
102"""
104COMMANDS['paths_init'] = """# make sure dictionary for Feff Paths exists
105try:
106 npaths = len(_feffpaths.keys())
107except:
108 _feffcache = {'paths':{}, 'runs':{}} # group of all paths, info about Feff runs
109 _feffpaths = {} # dict of paths currently in use, copied from _feffcache.paths
110#endtry
111"""
113COMMANDS['paths_reset'] = """# clear existing paths
114npaths = 0
115_feffpaths = {}
116#endtry
117"""
119COMMANDS['cache_path'] = """
120_feffcache['paths']['{title:s}'] = feffpath('{fullpath:s}',
121 label='{title:s}',feffrun='{feffrun:s}', degen=1)
122"""
124COMMANDS['use_path'] = """
125_feffpaths['{title:s}'] = use_feffpath(_feffcache['paths'], '{title:s}',
126 s02='{amp:s}', e0='{e0:s}',
127 deltar='{delr:s}', sigma2='{sigma2:s}',
128 third='{third:s}', ei='{ei:s}', use={use})
129"""
131COMMANDS['ff2chi'] = """# sum paths using a list of paths and a group of parameters
132_pathsum = ff2chi({paths:s}, paramgroup=_feffit_params)
133"""
135COMMANDS['do_feffit'] = """# build feffit dataset, run feffit
136_feffit_dataset = feffit_dataset(data={groupname:s}, transform={trans:s}, paths={paths:s})
137_feffit_result = feffit({params}, _feffit_dataset)
138if not hasattr({groupname:s}, 'feffit_history'): {groupname}.feffit_history = []
139{groupname:s}.feffit_history.insert(0, _feffit_result)
140"""
142COMMANDS['path2chi'] = """# generate chi(k) and chi(R) for each path
143for label, path in {paths_name:s}.items():
144 path.calc_chi_from_params({pargroup_name:s})
145 xftf(path, kmin={kmin:.3f}, kmax={kmax:.3f}, dk={dk:.3f},
146 window='{kwindow:s}', kweight={kweight:.3f})
147#endfor
148"""
151class ParametersModel(dv.DataViewIndexListModel):
152 def __init__(self, paramgroup, selected=None, pathkeys=None):
153 dv.DataViewIndexListModel.__init__(self, 0)
154 self.data = []
155 if selected is None:
156 selected = []
157 self.selected = selected
159 if pathkeys is None:
160 pathkeys = []
161 self.pathkeys = pathkeys
163 self.paramgroup = paramgroup
164 self.read_data()
166 def set_data(self, paramgroup, selected=None, pathkeys=None):
167 self.paramgroup = paramgroup
168 if selected is not None:
169 self.selected = selected
170 if pathkeys is not None:
171 self.pathkeys = pathkeys
172 self.read_data()
174 def read_data(self):
175 self.data = []
176 if self.paramgroup is None:
177 self.data.append(['param name', False, 'vary', '0.0'])
178 else:
179 for pname, par in group2params(self.paramgroup).items():
180 if any([pname.endswith('_%s' % phash) for phash in self.pathkeys]):
181 continue
182 ptype = 'vary'
183 if not par.vary:
184 pytype = 'fixed'
185 if getattr(par, 'skip', None) not in (False, None):
186 ptype = 'skip'
187 par.skip = ptype == 'skip'
188 try:
189 value = str(par.value)
190 except:
191 value = 'INVALID '
192 if par.expr is not None:
193 ptype = 'constraint'
194 value = "%s := %s" % (value, par.expr)
195 sel = pname in self.selected
196 self.data.append([pname, sel, ptype, value])
197 self.Reset(len(self.data))
199 def select_all(self, value=True):
200 self.selected = []
201 for irow, row in enumerate(self.data):
202 self.SetValueByRow(value, irow, 1)
203 if value:
204 self.selected.append(row[0])
206 def select_none(self):
207 self.select_all(value=False)
209 def GetColumnType(self, col):
210 return "bool" if col == 2 else "string"
212 def GetValueByRow(self, row, col):
213 return self.data[row][col]
215 def SetValueByRow(self, value, row, col):
216 self.data[row][col] = value
217 return True
219 def GetColumnCount(self):
220 return len(self.data[0])
222 def GetCount(self):
223 return len(self.data)
225 def GetAttrByRow(self, row, col, attr):
226 """set row/col attributes (color, etc)"""
227 ptype = self.data[row][2]
228 if ptype == 'vary':
229 attr.SetColour('#000000')
230 elif ptype == 'fixed':
231 attr.SetColour('#AA2020')
232 elif ptype == 'skip':
233 attr.SetColour('#50AA50')
234 else:
235 attr.SetColour('#2010BB')
236 return True
238class EditParamsFrame(wx.Frame):
239 """ edit parameters"""
240 def __init__(self, parent=None, feffit_panel=None,
241 paramgroup=None, selected=None):
242 wx.Frame.__init__(self, None, -1,
243 'Edit Feffit Parameters',
244 style=FRAMESTYLE, size=(550, 325))
246 self.parent = parent
247 self.feffit_panel = feffit_panel
248 self.paramgroup = paramgroup
250 spanel = scrolled.ScrolledPanel(self, size=(500, 275))
251 spanel.SetBackgroundColour('#EEEEEE')
253 self.font_fixedwidth = wx.Font(FONTSIZE_FW, wx.MODERN, wx.NORMAL, wx.BOLD)
255 self.dvc = dv.DataViewCtrl(spanel, style=DVSTYLE)
256 self.dvc.SetFont(self.font_fixedwidth)
257 self.SetMinSize((500, 250))
259 self.model = ParametersModel(paramgroup, selected)
260 self.dvc.AssociateModel(self.model)
262 sizer = wx.BoxSizer(wx.VERTICAL)
263 sizer.Add(self.dvc, 1, LEFT|wx.ALL|wx.GROW)
264 pack(spanel, sizer)
266 spanel.SetupScrolling()
268 toppan = GridPanel(self, ncols=4, pad=1, itemstyle=LEFT)
270 bkws = dict(size=(200, -1))
271 toppan.Add(Button(toppan, "Select All", action=self.onSelAll, size=(175, -1)))
272 toppan.Add(Button(toppan, "Select None", action=self.onSelNone, size=(175, -1)))
273 toppan.Add(Button(toppan, "Select Unused Variables", action=self.onSelUnused, size=(200, -1)))
274 toppan.Add(Button(toppan, "Remove Selected", action=self.onRemove, size=(175,-1)), newrow=True)
275 toppan.Add(Button(toppan, "'Skip' Selected", action=self.onSkip, size=(175, -1)))
276 toppan.Add(Button(toppan, "Force Refresh", action=self.onRefresh, size=(200, -1)))
277 npan = wx.Panel(toppan)
278 nsiz = wx.BoxSizer(wx.HORIZONTAL)
280 self.par_name = wx.TextCtrl(npan, -1, value='par_name', size=(125, -1),
281 style=wx.TE_PROCESS_ENTER)
282 self.par_expr = wx.TextCtrl(npan, -1, value='<expression or value>', size=(250, -1),
283 style=wx.TE_PROCESS_ENTER)
284 nsiz.Add(SimpleText(npan, "Add Parameter:"), 0)
285 nsiz.Add(self.par_name, 0)
286 nsiz.Add(self.par_expr, 1, wx.GROW|wx.ALL)
287 nsiz.Add(Button(npan, label='Add', action=self.onAddParam), 0)
288 pack(npan, nsiz)
290 toppan.Add(npan, dcol=4, newrow=True)
291 toppan.Add(HLine(toppan, size=(500, 2)), dcol=5, newrow=True)
292 toppan.pack()
294 mainsizer = wx.BoxSizer(wx.VERTICAL)
295 mainsizer.Add(toppan, 0, wx.GROW|wx.ALL, 1)
296 mainsizer.Add(spanel, 1, wx.GROW|wx.ALL, 1)
297 pack(self, mainsizer)
299 columns = [('Parameter', 150, 'text'),
300 ('Select', 75, 'bool'),
301 ('Type', 75, 'text'),
302 ('Value', 200, 'text')]
304 for icol, dat in enumerate(columns):
305 label, width, dtype = dat
306 method = self.dvc.AppendTextColumn
307 mode = dv.DATAVIEW_CELL_EDITABLE
308 if dtype == 'bool':
309 method = self.dvc.AppendToggleColumn
310 mode = dv.DATAVIEW_CELL_ACTIVATABLE
311 method(label, icol, width=width, mode=mode)
312 c = self.dvc.Columns[icol]
313 c.Alignment = c.Renderer.Alignment = wx.ALIGN_LEFT
314 c.SetSortable(False)
316 self.dvc.EnsureVisible(self.model.GetItem(0))
317 self.Bind(wx.EVT_CLOSE, self.onClose)
319 self.Show()
320 self.Raise()
321 wx.CallAfter(self.onSelUnused)
323 def onSelAll(self, event=None):
324 self.model.select_all()
325 self.model.read_data()
327 def onSelNone(self, event=None):
328 self.model.select_none()
329 self.model.read_data()
331 def onSelUnused(self, event=None):
332 curr_syms = self.feffit_panel.get_used_params()
333 unused = []
334 for pname, par in group2params(self.paramgroup).items():
335 if pname not in curr_syms: # and par.vary:
336 unused.append(pname)
337 self.model.set_data(self.paramgroup, selected=unused,
338 pathkeys=self.feffit_panel.get_pathkeys())
340 def onRemove(self, event=None):
341 out = []
342 for pname, sel, ptype, val in self.model.data:
343 if sel:
344 out.append(pname)
345 nout = len(out)
347 msg = f"Remove {nout:d} Parameters? \n This is not easy to undo!"
348 dlg = wx.MessageDialog(self, msg, 'Warning', wx.YES | wx.NO )
349 if (wx.ID_YES == dlg.ShowModal()):
350 for pname, sel, ptype, val in self.model.data:
351 if sel:
352 out.append(pname)
353 if hasattr(self.paramgroup, pname):
354 delattr(self.paramgroup, pname)
356 self.model.set_data(self.paramgroup, selected=None,
357 pathkeys=self.feffit_panel.get_pathkeys())
358 self.model.read_data()
359 self.feffit_panel.get_pathpage('parameters').Rebuild()
360 dlg.Destroy()
362 def onSkip(self, event=None):
363 for pname, sel, ptype, val in self.model.data:
364 if sel:
365 par = getattr(self.paramgroup, pname, None)
366 if par is not None:
367 par.skip = True
368 self.model.read_data()
369 self.feffit_panel.get_pathpage('parameters').Rebuild()
372 def onAddParam(self, event=None):
373 par_name = self.par_name.GetValue()
374 par_expr = self.par_expr.GetValue()
376 try:
377 val = float(par_expr)
378 ptype = 'vary'
379 except:
380 val = par_expr
381 ptype = 'expr'
383 if ptype == 'vary':
384 cmd = f"_feffit_params.{par_name} = param({val}, vary=True)"
385 else:
386 cmd = f"_feffit_params.{par_name} = param(expr='{val}')"
388 self.feffit_panel.larch_eval(cmd)
389 self.onRefresh()
391 def onRefresh(self, event=None):
392 self.paramgroup = self.feffit_panel.get_paramgroup()
393 self.model.set_data(self.paramgroup,
394 pathkeys=self.feffit_panel.get_pathkeys())
395 self.model.read_data()
396 self.feffit_panel.get_pathpage('parameters').Rebuild()
398 def onClose(self, event=None):
399 self.Destroy()
402class FeffitParamsPanel(wx.Panel):
403 def __init__(self, parent=None, feffit_panel=None, **kws):
404 wx.Panel.__init__(self, parent, -1, size=(550, 250))
405 self.feffit_panel = feffit_panel
406 self.parwids = {}
407 self.SetFont(Font(FONTSIZE))
408 spanel = scrolled.ScrolledPanel(self)
409 spanel.SetSize((250, 250))
410 spanel.SetMinSize((50, 50))
411 panel = self.panel = GridPanel(spanel, ncols=8, nrows=30, pad=1, itemstyle=LEFT)
412 panel.SetFont(Font(FONTSIZE))
414 def SLabel(label, size=(80, -1), **kws):
415 return SimpleText(panel, label, size=size, style=wx.ALIGN_LEFT, **kws)
417 panel.Add(SLabel("Feffit Parameters ", colour='#0000AA', size=(200, -1)), dcol=2)
418 panel.Add(Button(panel, 'Edit Parameters', action=self.onEditParams), dcol=2)
419 panel.Add(Button(panel, 'Force Refresh', action=self.Rebuild), dcol=3)
421 panel.Add(SLabel("Parameter "), style=wx.ALIGN_LEFT, newrow=True)
422 panel.AddMany((SLabel(" Value"), SLabel(" Type"), SLabel(' Bounds'),
423 SLabel(" Min", size=(60, -1)),
424 SLabel(" Max", size=(60, -1)),
425 SLabel(" Expression")))
427 self.update()
428 panel.pack()
429 ssizer = wx.BoxSizer(wx.VERTICAL)
430 ssizer.Add(panel, 1, wx.GROW|wx.ALL, 2)
431 pack(spanel, ssizer)
433 spanel.SetupScrolling()
434 mainsizer = wx.BoxSizer(wx.VERTICAL)
435 mainsizer.Add(spanel, 1, wx.GROW|wx.ALL, 2)
436 pack(self, mainsizer)
438 def Rebuild(self, event=None):
439 for pname, parwid in self.parwids.items():
440 for x in parwid.widgets:
441 x.Destroy()
442 self.panel.irow = 1
443 self.parwids = {}
444 self.update()
446 def set_init_values(self, params):
447 for pname, par in params.items():
448 if pname in self.parwids and par.vary:
449 stderr = getattr(par, 'stderr', 0.001)
450 try:
451 prec = max(1, min(8, round(2-math.log10(stderr))))
452 except:
453 prec = 5
454 self.parwids[pname].value.SetValue(("%%.%.df" % prec) % par.value)
456 def update(self):
457 pargroup = self.feffit_panel.get_paramgroup()
458 hashkeys = self.feffit_panel.get_pathkeys()
459 params = group2params(pargroup)
460 for pname, par in params.items():
461 if any([pname.endswith('_%s' % phash) for phash in hashkeys]):
462 continue
463 if pname not in self.parwids and not hasattr(par, '_is_pathparam'):
464 pwids = ParameterWidgets(self.panel, par, name_size=100,
465 expr_size=150, float_size=70,
466 with_skip=True,
467 widgets=('name', 'value',
468 'minval', 'maxval',
469 'vary', 'expr'))
471 self.parwids[pname] = pwids
472 self.panel.Add(pwids.name, newrow=True)
473 self.panel.AddMany((pwids.value, pwids.vary, pwids.bounds,
474 pwids.minval, pwids.maxval, pwids.expr))
475 self.panel.pack()
477 pwids = self.parwids[pname]
478 varstr = 'vary' if par.vary else 'fix'
479 if par.expr is not None:
480 varstr = 'constrain'
481 pwids.expr.SetValue(par.expr)
482 if getattr(par, 'skip', None) not in (False, None):
483 varstr = 'skip'
484 pwids.vary.SetStringSelection(varstr)
485 if varstr != 'skip':
486 pwids.value.SetValue(par.value)
487 pwids.minval.SetValue(par.min)
488 pwids.maxval.SetValue(par.max)
489 pwids.onVaryChoice()
490 self.panel.Update()
492 def onEditParams(self, event=None):
493 pargroup = self.feffit_panel.get_paramgroup()
494 self.feffit_panel.show_subframe('edit_params', EditParamsFrame,
495 paramgroup=pargroup,
496 feffit_panel=self.feffit_panel)
498 def RemoveParams(self, event=None, name=None):
499 if name is None:
500 return
501 pargroup = self.feffit_panel.get_paramgroup()
503 if hasattr(pargroup, name):
504 delattr(pargroup, name)
505 if name in self.parwids:
506 pwids = self.parwids.pop(name)
507 pwids.name.Destroy()
508 pwids.value.Destroy()
509 pwids.vary.Destroy()
510 pwids.bounds.Destroy()
511 pwids.minval.Destroy()
512 pwids.maxval.Destroy()
513 pwids.expr.Destroy()
514 pwids.remover.Destroy()
516 def generate_params(self, event=None):
517 s = []
518 s.append(COMMANDS['feffit_params_init'])
519 for name, pwids in self.parwids.items():
520 param = pwids.param
521 args = [f'{param.value}']
522 minval = pwids.minval.GetValue()
523 if np.isfinite(minval):
524 args.append(f'min={minval}')
525 maxval = pwids.maxval.GetValue()
526 if np.isfinite(maxval):
527 args.append(f'max={maxval}')
529 varstr = pwids.vary.GetStringSelection()
530 if varstr == 'skip':
531 args.append('skip=True, vary=False')
532 elif param.expr is not None and varstr == 'constrain':
533 args.append(f"expr='{param.expr}'")
534 elif varstr == 'vary':
535 args.append(f'vary=True')
536 else:
537 args.append(f'vary=False')
538 args = ', '.join(args)
539 cmd = f'_feffit_params.{name} = param({args})'
540 s.append(cmd)
541 return s
544class FeffPathPanel(wx.Panel):
545 """Feff Path """
546 def __init__(self, parent, feffit_panel, filename, title, user_label,
547 geomstr, absorber, shell, reff, nleg, degen,
548 par_amp, par_e0, par_delr, par_sigma2, par_third, par_ei):
550 self.parent = parent
551 self.title = title
552 self.user_label = fix_varname(f'{title:s}')
553 self.feffit_panel = feffit_panel
554 self.editing_enabled = False
556 wx.Panel.__init__(self, parent, -1, size=(550, 250))
557 self.SetFont(Font(FONTSIZE))
558 panel = GridPanel(self, ncols=4, nrows=4, pad=2, itemstyle=LEFT)
560 self.fullpath = filename
561 par, feffdat_file = os.path.split(filename)
562 parent_folder, dirname = os.path.split(par)
564 self.user_label = user_label
566 self.nleg = nleg
567 self.reff = reff
568 self.geomstr = geomstr
569 # self.geometry = geometry
571 def SLabel(label, size=(80, -1), **kws):
572 return SimpleText(panel, label, size=size, style=LEFT, **kws)
574 self.wids = wids = {}
575 for name, expr in (('label', user_label),
576 ('amp', par_amp),
577 ('e0', par_e0),
578 ('delr', par_delr),
579 ('sigma2', par_sigma2),
580 ('third', par_third),
581 ('ei', par_ei)):
582 self.wids[name] = wx.TextCtrl(panel, -1, size=(250, -1),
583 value=expr, style=wx.TE_PROCESS_ENTER)
584 wids[name+'_val'] = SimpleText(panel, '', size=(150, -1), style=LEFT)
586 wids['use'] = Check(panel, default=True, label='Use in Fit?', size=(100, -1))
587 wids['del'] = Button(panel, 'Remove This Path', size=(150, -1),
588 action=self.onRemovePath)
589 wids['plot_feffdat'] = Button(panel, 'Plot F(k)', size=(150, -1),
590 action=self.onPlotFeffDat)
592 scatt = {2: 'Single', 3: 'Double', 4: 'Triple',
593 5: 'Quadruple'}.get(nleg, f'{nleg-1:d}-atom')
594 scatt = scatt + ' Scattering'
597 title1 = f'{dirname:s}: {feffdat_file:s} {absorber:s} {shell:s} edge'
598 title2 = f'Reff={reff:.4f}, Degen={degen:.1f}, {scatt:s}: {geomstr:s}'
600 panel.Add(SLabel(title1, size=(375, -1), colour='#0000AA'),
601 dcol=2, style=wx.ALIGN_LEFT, newrow=True)
602 panel.Add(wids['use'])
603 panel.Add(wids['del'])
604 panel.Add(SLabel(title2, size=(425, -1)),
605 dcol=3, style=wx.ALIGN_LEFT, newrow=True)
606 panel.Add(wids['plot_feffdat'])
608 panel.AddMany((SLabel('Label'), wids['label'], wids['label_val']), newrow=True)
609 panel.AddMany((SLabel('Amplitude'), wids['amp'], wids['amp_val']), newrow=True)
610 panel.AddMany((SLabel('E0 '), wids['e0'], wids['e0_val']), newrow=True)
611 panel.AddMany((SLabel('Delta R'), wids['delr'], wids['delr_val']), newrow=True)
612 panel.AddMany((SLabel('sigma2'), wids['sigma2'], wids['sigma2_val']),newrow=True)
613 panel.AddMany((SLabel('third'), wids['third'], wids['third_val']), newrow=True)
614 panel.AddMany((SLabel('Eimag'), wids['ei'], wids['ei_val']), newrow=True)
615 panel.pack()
616 sizer= wx.BoxSizer(wx.VERTICAL)
617 sizer.Add(panel, 1, LEFT|wx.GROW|wx.ALL, 2)
618 pack(self, sizer)
621 def enable_editing(self):
622 for name in ('label', 'amp', 'e0', 'delr', 'sigma2', 'third', 'ei'):
623 self.wids[name].Bind(wx.EVT_TEXT_ENTER, partial(self.onExpression, name=name))
624 self.wids[name].Bind(wx.EVT_KILL_FOCUS, partial(self.onExpression, name=name))
625 self.editing_enabled = True
626 self.wids['label'].SetValue(self.user_label)
628 def set_userlabel(self, label):
629 self.wids['label'].SetValue(label)
631 def get_expressions(self):
632 out = {'use': self.wids['use'].IsChecked()}
633 for key in ('label', 'amp', 'e0', 'delr', 'sigma2', 'third', 'ei'):
634 val = self.wids[key].GetValue().strip()
635 if len(val) == 0: val = '0'
636 out[key] = val
637 return out
639 def onExpression(self, event=None, name=None):
640 if name is None:
641 return
642 expr = self.wids[name].GetValue()
643 if name == 'label':
644 time.sleep(0.001)
645 return
647 expr = self.wids[name].GetValue().strip()
648 if len(expr) < 1:
649 return
650 opts= dict(value=1.e-3, minval=None, maxval=None)
651 if name == 'sigma2':
652 opts['minval'] = 0
653 opts['maxval'] = 1
654 opts['value'] = np.sqrt(self.reff)/200.0
655 elif name == 'delr':
656 opts['minval'] = -0.75
657 opts['maxval'] = 0.75
658 elif name == 'amp':
659 opts['value'] = 1
660 result = self.feffit_panel.update_params_for_expr(expr, **opts)
661 if result:
662 pargroup = self.feffit_panel.get_paramgroup()
663 _eval = pargroup.__params__._asteval
664 try:
665 value = _eval.eval(expr, show_errors=False, raise_errors=False)
666 if value is not None:
667 value = gformat(value, 11)
668 self.wids[name + '_val'].SetLabel(f'= {value}')
669 except:
670 result = False
672 if result:
673 bgcol, fgcol = 'white', 'black'
674 else:
675 bgcol, fgcol = '#AAAA4488', '#AA0000'
676 self.wids[name].SetForegroundColour(fgcol)
677 self.wids[name].SetBackgroundColour(bgcol)
678 self.wids[name].SetOwnBackgroundColour(bgcol)
679 if event is not None:
680 event.Skip()
683 def onPlotFeffDat(self, event=None):
684 cmd = f"plot_feffdat(_feffpaths['{self.title}'], title='Feff data for path {self.title}')"
685 self.feffit_panel.larch_eval(cmd)
687 def onRemovePath(self, event=None):
688 msg = f"Delete Path {self.title:s}?"
689 dlg = wx.MessageDialog(self, msg, 'Warning', wx.YES | wx.NO )
690 if (wx.ID_YES == dlg.ShowModal()):
691 self.feffit_panel.paths_data.pop(self.title)
692 self.feffit_panel.model_needs_build = True
693 path_nb = self.feffit_panel.paths_nb
694 for i in range(path_nb.GetPageCount()):
695 if self.title == path_nb.GetPageText(i).strip():
696 path_nb.DeletePage(i)
697 self.feffit_panel.skip_unused_params()
698 dlg.Destroy()
700 def update_values(self):
701 pargroup = self.feffit_panel.get_paramgroup()
702 _eval = pargroup.__params__._asteval
703 for par in ('amp', 'e0', 'delr', 'sigma2', 'third', 'ei'):
704 expr = self.wids[par].GetValue().strip()
705 if len(expr) > 0:
706 try:
707 value = _eval.eval(expr, show_errors=False, raise_errors=False)
708 if value is not None:
709 value = gformat(value, 10)
710 self.wids[par + '_val'].SetLabel(f'= {value}')
711 except:
712 self.feffit_panel.update_params_for_expr(expr)
715class FeffitPanel(TaskPanel):
716 def __init__(self, parent=None, controller=None, **kws):
717 TaskPanel.__init__(self, parent, controller, panel='feffit', **kws)
718 self.paths_data = {}
719 self.resetting = False
720 self.model_needs_rebuild = False
721 self.config_saved = self.get_defaultconfig()
722 self.dgroup = None
724 def onPanelExposed(self, **kws):
725 # called when notebook is selected
726 dgroup = self.controller.get_group()
727 try:
728 pargroup = self.get_paramgroup()
729 self.params_panel.update()
730 fname = self.controller.filelist.GetStringSelection()
731 gname = self.controller.file_groups[fname]
732 dgroup = self.controller.get_group(gname)
733 if not hasattr(dgroup, 'chi'):
734 self.xasmain.process_exafs(dgroup)
735 self.fill_form(dgroup)
736 except:
737 pass # print(" Cannot Fill feffit panel from group ")
738 self.dgroup = dgroup
739 feffpaths = getattr(self.larch.symtable, '_feffpaths', None)
742 try:
743 has_fit_hist = len(dgroup.feffit_history) > 0
744 except:
745 has_fit_hist = False
748 if not has_fit_hist:
749 has_fit_hist = getattr(self.larch.symtable, '_feffit_dataset', None) is not None
751 if has_fit_hist:
752 self.wids['show_results'].Enable()
753 if feffpath is not None:
754 self.reset_paths()
756 def build_display(self):
757 self.paths_nb = flatnotebook(self, {}, on_change=self.onPathsNBChanged,
758 with_dropdown=True)
760 self.params_panel = FeffitParamsPanel(parent=self.paths_nb,
761 feffit_panel=self)
762 self.paths_nb.AddPage(self.params_panel, ' Parameters ', True)
763 pan = self.panel # = GridPanel(self, ncols=4, nrows=4, pad=2, itemstyle=LEFT)
765 self.wids = wids = {}
767 fsopts = dict(digits=2, increment=0.1, with_pin=True)
769 fit_kmin = self.add_floatspin('fit_kmin', value=2, **fsopts)
770 fit_kmax = self.add_floatspin('fit_kmax', value=17, **fsopts)
771 fit_dk = self.add_floatspin('fit_dk', value=4, **fsopts)
772 fit_rmin = self.add_floatspin('fit_rmin', value=1, **fsopts)
773 fit_rmax = self.add_floatspin('fit_rmax', value=5, **fsopts)
775 wids['fit_kwstring'] = Choice(pan, size=(150, -1),
776 choices=list(Feffit_KWChoices.keys()))
777 wids['fit_kwstring'].SetSelection(1)
779 wids['fit_kwindow'] = Choice(pan, choices=list(FT_WINDOWS), size=(150, -1))
781 wids['fit_space'] = Choice(pan, choices=list(Feffit_SpaceChoices.keys()),
782 size=(150, -1))
784 wids['plotone_op'] = Choice(pan, choices=list(PlotOne_Choices.keys()),
785 action=self.onPlot, size=(150, -1))
786 wids['plotone_op'].SetSelection(1)
787 wids['plotalt_op'] = Choice(pan, choices=list(PlotAlt_Choices.keys()),
788 action=self.onPlot, size=(150, -1))
790 wids['plot_win'] = Choice(pan, choices=PlotWindowChoices,
791 action=self.onPlot, size=(60, -1))
792 wids['plot_win'].SetStringSelection('2')
794 wids['plot_voffset'] = FloatSpin(pan, value=0, digits=2, increment=0.25,
795 size=(100, -1), action=self.onPlot)
798 ppanel = wx.Panel(pan)
799 ppanel.SetMinSize((450, 20))
801 wids['plot_paths'] = Check(ppanel, default=False, label='Plot Each Path',
802 action=self.onPlot)
803 wids['plot_ftwindows'] = Check(ppanel, default=False, label='Plot FT Windows',
804 action=self.onPlot)
806 psizer = wx.BoxSizer(wx.HORIZONTAL)
807 psizer.Add(wids['plot_paths'], 0, LEFT, 2)
808 psizer.Add(wids['plot_ftwindows'], 0, LEFT, 2)
809 #psizer.Add(SimpleText(ppanel, ' Offset ', size=(100, -1) ), 0, LEFT, 2)
810 #psizer.Add(wids['plot_voffset'], 0, LEFT, 2)
811 pack(ppanel, psizer)
812 wids['plot_current'] = Button(pan,'Plot Current Model',
813 action=self.onPlot, size=(175, -1))
814 wids['do_fit'] = Button(pan, 'Fit Data to Model',
815 action=self.onFitModel, size=(175, -1))
816 wids['show_results'] = Button(pan, 'Show Fit Results',
817 action=self.onShowResults, size=(175, -1))
818 wids['show_results'].Disable()
820# wids['do_fit_sel']= Button(pan, 'Fit Selected Groups',
821# action=self.onFitSelected, size=(125, -1))
822# wids['do_fit_sel'].Disable()
823 def add_text(text, dcol=1, newrow=True):
824 pan.Add(SimpleText(pan, text), dcol=dcol, newrow=newrow)
826 pan.Add(SimpleText(pan, 'Feff Fitting',
827 size=(150, -1), **self.titleopts), style=LEFT, dcol=1, newrow=True)
828 pan.Add(SimpleText(pan, 'To add paths, use Feff->Browse Feff Calculations',
829 size=(350, -1)), style=LEFT, dcol=3)
831 add_text('Fitting Space: ')
832 pan.Add(wids['fit_space'])
834 add_text('k weightings: ', newrow=False)
835 pan.Add(wids['fit_kwstring'])
837 add_text('k min: ')
838 pan.Add(fit_kmin)
839 add_text(' k max: ', newrow=False)
840 pan.Add(fit_kmax)
842 add_text('k Window: ')
843 pan.Add(wids['fit_kwindow'])
844 add_text('dk: ', newrow=False)
845 pan.Add(fit_dk)
847 add_text('R min: ')
848 pan.Add(fit_rmin)
849 add_text('R max: ', newrow=False)
850 pan.Add(fit_rmax)
852 pan.Add(HLine(pan, size=(600, 2)), dcol=6, newrow=True)
854 pan.Add(wids['plot_current'], dcol=1, newrow=True)
855 pan.Add(wids['plotone_op'], dcol=1)
856 pan.Add(ppanel, dcol=4)
857 add_text(' ', dcol=2, newrow=True)
858 add_text('Vertical Offset' , newrow=False)
859 pan.Add(wids['plot_voffset'])
861 add_text('Second Plot: ', newrow=True)
862 pan.Add(wids['plotalt_op'], dcol=1)
863 add_text('Plot Window: ', newrow=False)
864 pan.Add(wids['plot_win'], dcol=1)
866 pan.Add(wids['do_fit'], dcol=3, newrow=True)
867 pan.Add(wids['show_results'])
868 pan.Add((5, 5), newrow=True)
870 pan.Add(HLine(pan, size=(600, 2)), dcol=6, newrow=True)
871 pan.pack()
873 sizer = wx.BoxSizer(wx.VERTICAL)
874 sizer.Add(pan, 0, LEFT, 3)
875 sizer.Add((10, 10), 0, LEFT, 3)
876 sizer.Add(self.paths_nb, 1, LEFT|wx.GROW, 5)
877 pack(self, sizer)
879 def onPathsNBChanged(self, event=None):
880 updater = getattr(self.paths_nb.GetCurrentPage(), 'update_values', None)
881 if callable(updater) and not self.resetting:
882 updater()
884 def get_config(self, dgroup=None):
885 """get and set processing configuration for a group"""
886 if dgroup is None:
887 dgroup = self.controller.get_group()
888 if dgroup is None:
889 conf = None
890 if not hasattr(dgroup, 'chi'):
891 self.xasmain.process_exafs(dgroup)
893 # print("Get Config ", dgroup, self.configname, hasattr(dgroup, 'config'))
895 dconf = self.get_defaultconfig()
896 if dgroup is None:
897 return dconf
898 if not hasattr(dgroup, 'config'):
899 dgroup.config = Group()
901 conf = getattr(dgroup.config, self.configname, dconf)
902 for k, v in dconf.items():
903 if k not in conf:
904 conf[k] = v
906 econf = getattr(dgroup.config, 'exafs', {})
907 for key in ('fit_kmin', 'fit_kmax', 'fit_dk',
908 'fit_rmin', 'fit_rmax', 'fit_dr'
909 'fit_kwindow', 'fit_rwindow'):
910 alt = key.replace('fit', 'fft')
911 val = conf.get(key, -1)
912 if val in (None, -1, 'Auto') and alt in econf:
913 conf[key] = econf[alt]
915 setattr(dgroup.config, self.configname, conf)
916 self.config_saved = conf
917 return conf
920 def process(self, dgroup=None, **kws):
921 if dgroup is None:
922 dgroup = self.controller.get_group()
924 conf = self.get_config(dgroup=dgroup)
925 conf.update(kws)
926 if dgroup is None:
927 return conf
929 self.dgroup = dgroup
930 opts = self.read_form(dgroup=dgroup)
932 for attr in ('fit_kmin', 'fit_kmax', 'fit_dk', 'fit_rmin',
933 'fit_rmax', 'fit_kwindow', 'fit_rwindow',
934 'fit_dr', 'fit_kwstring', 'fit_space',
935 'fit_plot', 'plot_paths'):
937 conf[attr] = opts.get(attr, None)
939 if not hasattr(dgroup, 'config'):
940 dgroup.config = Group()
941 setattr(dgroup.config, self.configname, conf)
943 def fill_form(self, dat):
944 dgroup = self.controller.get_group()
945 conf = self.get_config(dat)
946 for attr in ('fit_kmin', 'fit_kmax', 'fit_rmin', 'fit_rmax', 'fit_dk'):
947 self.wids[attr].SetValue(conf[attr])
949 self.wids['fit_kwindow'].SetStringSelection(conf['fit_kwindow'])
951 fit_space = conf.get('fit_space', 'r')
953 for key, val in Feffit_SpaceChoices.items():
954 if fit_space in (key, val):
955 self.wids['fit_space'].SetStringSelection(key)
957 for key, val in Feffit_KWChoices.items():
958 if conf['fit_kwstring'] == val:
959 self.wids['fit_kwstring'].SetStringSelection(key)
961 def read_form(self, dgroup=None):
962 "read form, returning dict of values"
964 if dgroup is None:
965 try:
966 fname = self.controller.filelist.GetStringSelection()
967 gname = self.controller.file_groups[fname]
968 dgroup = self.controller.get_group()
969 except:
970 gname = fname = dgroup = None
971 else:
973 gname = dgroup.groupname
974 fname = dgroup.filename
976 form_opts = {'datagroup': dgroup, 'groupname': gname, 'filename': fname}
977 wids = self.wids
979 for attr in ('fit_kmin', 'fit_kmax', 'fit_rmin', 'fit_rmax', 'fit_dk'):
980 form_opts[attr] = wids[attr].GetValue()
981 form_opts['fit_kwstring'] = Feffit_KWChoices[wids['fit_kwstring'].GetStringSelection()]
982 if len(form_opts['fit_kwstring']) == 1:
983 d = form_opts['fit_kwstring']
984 else:
985 d = form_opts['fit_kwstring'].replace('[', '').strip(',').split()[0]
986 try:
987 form_opts['fit_kweight'] = int(d)
988 except:
989 form_opts['fit_kweight'] = 2
992 form_opts['fit_space'] = Feffit_SpaceChoices[wids['fit_space'].GetStringSelection()]
994 form_opts['fit_kwindow'] = wids['fit_kwindow'].GetStringSelection()
995 form_opts['plot_ftwindows'] = wids['plot_ftwindows'].IsChecked()
996 form_opts['plot_paths'] = wids['plot_paths'].IsChecked()
997 form_opts['plotone_op'] = PlotOne_Choices[wids['plotone_op'].GetStringSelection()]
998 form_opts['plotalt_op'] = PlotAlt_Choices[wids['plotalt_op'].GetStringSelection()]
999 form_opts['plot_voffset'] = wids['plot_voffset'].GetValue()
1000 form_opts['plot_win'] = int(wids['plot_win'].GetStringSelection())
1002 return form_opts
1005 def fill_model_params(self, prefix, params):
1006 comp = self.fit_components[prefix]
1007 parwids = comp.parwids
1008 for pname, par in params.items():
1009 pname = prefix + pname
1010 if pname in parwids:
1011 wids = parwids[pname]
1012 if wids.minval is not None:
1013 wids.minval.SetValue(par.min)
1014 if wids.maxval is not None:
1015 wids.maxval.SetValue(par.max)
1016 wids.value.SetValue(par.value)
1017 varstr = 'vary' if par.vary else 'fix'
1018 if par.expr is not None:
1019 varstr = 'constrain'
1020 if wids.vary is not None:
1021 wids.vary.SetStringSelection(varstr)
1023 def onPlot(self, evt=None, dgroup=None, pargroup_name='_feffit_params',
1024 paths_name='_feffpaths', pathsum_name='_pathsum', title=None,
1025 dataset_name=None, build_fitmodel=True, topwin=None, **kws):
1027 self.process(dgroup)
1028 opts = self.read_form(dgroup=dgroup)
1029 opts.update(**kws)
1030 fname = opts['filename']
1031 if title is None:
1032 title = fname
1033 if title is None:
1034 title = 'Feff Sum'
1035 if "'" in title:
1036 title = title.replace("'", "\\'")
1038 gname = opts['groupname']
1039 if dataset_name is None:
1040 dataset_name = gname
1042 if dgroup is None:
1043 dgroup = opts['datagroup']
1045 exafs_conf = self.xasmain.get_nbpage('exafs')[1].read_form()
1046 plot_rmax = exafs_conf['plot_rmax']
1048 if build_fitmodel:
1049 self.build_fitmodel(dgroup)
1051 try:
1052 pathsum = self._plain_larch_eval(pathsum_name)
1053 except:
1054 pathsum = None
1056 try:
1057 paths = self._plain_larch_eval(paths_name)
1058 except:
1059 paths = {}
1061 plot1 = opts['plotone_op']
1062 plot2 = opts['plotalt_op']
1063 cmds = []
1065 kw = opts['fit_kweight']
1067 ftargs = dict(kmin=opts['fit_kmin'], kmax=opts['fit_kmax'], dk=opts['fit_dk'],
1068 kwindow=opts['fit_kwindow'], kweight=opts['fit_kweight'],
1069 rmin=opts['fit_rmin'], rmax=opts['fit_rmax'],
1070 dr=opts.get('fit_dr', 0.1), rwindow='hanning')
1072 if pathsum is not None:
1073 cmds.append(COMMANDS['xft'].format(groupname=pathsum_name, **ftargs))
1074 if dataset_name is not None:
1075 cmds.append(COMMANDS['xft'].format(groupname=dataset_name, **ftargs))
1076 if dgroup is not None:
1077 cmds.append(COMMANDS['xft'].format(groupname=gname, **ftargs))
1078 if opts['plot_paths']:
1079 cmds.append(COMMANDS['path2chi'].format(paths_name=paths_name,
1080 pargroup_name=pargroup_name,
1081 **ftargs))
1083 self.larch_eval('\n'.join(cmds))
1084 with_win = opts['plot_ftwindows']
1085 needs_qspace = False
1086 cmds = []
1087 for i, plot in enumerate((plot1, plot2)):
1088 if plot in PlotAlt_Choices:
1089 plot = PlotAlt_Choices[plot]
1091 if plot in ('noplot', '<no plot>'):
1092 continue
1093 plotwin = 1
1094 if i > 0:
1095 plotwin = int(opts.get('plot_win', '2'))
1096 pcmd = 'plot_chir'
1097 pextra = f', win={plotwin:d}'
1098 if plot == 'chi':
1099 pcmd = 'plot_chik'
1100 pextra += f', kweight={kw:d}'
1101 elif plot == 'chir_mag':
1102 pcmd = 'plot_chir'
1103 pextra += f', rmax={plot_rmax}'
1104 elif plot == 'chir_re':
1105 pextra += f', show_mag=False, show_real=True, rmax={plot_rmax}'
1106 elif plot == 'chir_mag+chir_re':
1107 pextra += f', show_mag=True, show_real=True, rmax={plot_rmax}'
1108 elif plot == 'chiq':
1109 pcmd = 'plot_chiq'
1110 pextra += f', show_chik=False'
1111 needs_qspace = True
1112 else:
1113 print(" do not know how to plot ", plot)
1114 continue
1116 newplot = f', show_window={with_win}, new=True'
1117 overplot = f', show_window=False, new=False'
1118 if dgroup is not None:
1119 cmds.append(f"{pcmd}({dataset_name:s}, label='data'{pextra}, title='{title}'{newplot})")
1120 if pathsum is not None:
1121 cmds.append(f"{pcmd}({pathsum_name:s}, label='model'{pextra}{overplot})")
1122 elif pathsum is not None:
1123 cmds.append(f"{pcmd}({pathsum_name:s}, label='Path sum'{pextra}, title='sum of paths'{newplot})")
1124 if opts['plot_paths']:
1125 voff = opts['plot_voffset']
1127 for i, label in enumerate(paths.keys()):
1128 if paths[label].use:
1130 objname = f"{paths_name}['{label:s}']"
1131 if needs_qspace:
1132 xpath = paths.get(label)
1133 if not hasattr(xpath, 'chiq_re'):
1134 cmds.append(COMMANDS['xft'].format(groupname=objname, **ftargs))
1136 cmds.append(f"{pcmd}({objname}, label='{label:s}'{pextra}, offset={(i+1)*voff}{overplot})")
1138 self.larch_eval('\n'.join(cmds))
1139 self.controller.set_focus(topwin=topwin)
1142 def reset_paths(self, event=None):
1143 "reset paths from _feffpaths"
1144 self.resetting = True
1145 def get_pagenames():
1146 allpages = []
1147 for i in range(self.paths_nb.GetPageCount()):
1148 allpages.append(self.paths_nb.GetPage(i).__class__.__name__)
1149 return allpages
1151 allpages = get_pagenames()
1152 t0 = time.time()
1154 while 'FeffPathPanel' in allpages:
1155 for i in range(self.paths_nb.GetPageCount()):
1156 nbpage = self.paths_nb.GetPage(i)
1157 if isinstance(nbpage, FeffPathPanel):
1158 key = self.paths_nb.GetPageText(i)
1159 self.paths_nb.DeletePage(i)
1160 allpages = get_pagenames()
1162 time.sleep(0.1)
1164 self.resetting = False
1165 feffpaths = deepcopy(getattr(self.larch.symtable, '_feffpaths', {}))
1166 self.paths_data = {}
1167 for path in feffpaths.values():
1168 self.add_path(path.filename, feffpath=path)
1169 self.get_pathpage('parameters').Rebuild()
1172 def add_path(self, filename, pathinfo=None, feffpath=None):
1173 """ add new path to cache """
1175 if pathinfo is None and feffpath is None:
1176 raise ValueError("add_path needs a Feff Path or Path information")
1178 parent, fname = os.path.split(filename)
1179 parent, feffrun = os.path.split(parent)
1181 feffcache = getattr(self.larch.symtable, '_feffcache', None)
1182 if feffcache is None:
1183 self.larch_eval(COMMANDS['paths_init'])
1184 feffcache = getattr(self.larch.symtable, '_feffcache', None)
1185 if feffcache is None:
1186 raise ValueError("cannot get feff cache ")
1188 geomstre = None
1189 if pathinfo is not None:
1190 absorber = pathinfo.absorber
1191 shell = pathinfo.shell
1192 reff = float(pathinfo.reff)
1193 nleg = int(pathinfo.nleg)
1194 degen = float(pathinfo.degen)
1195 if hasattr(pathinfo, 'atoms'):
1196 geom = pathinfo.atoms
1197 geomstr = pathinfo.geom # '[Fe] > O > [Fe]'
1198 par_amp = par_e0 = par_delr = par_sigma2 = par_third = par_ei = ''
1200 if feffpath is not None:
1201 absorber = feffpath.absorber
1202 shell = feffpath.shell
1203 reff = feffpath.reff
1204 nleg = feffpath.nleg
1205 degen = float(feffpath.degen)
1206 geomstr = []
1207 for gdat in feffpath.geom: # ('Fe', 26, 0, 55.845, x, y, z)
1208 w = gdat[0]
1209 if gdat[2] == 0: # absorber
1210 w = '[%s]' % w
1211 geomstr.append(w)
1212 geomstr.append(geomstr[0])
1213 geomstr = ' > '.join(geomstr)
1214 par_amp = feffpath.s02
1215 par_e0 = feffpath.e0
1216 par_delr = feffpath.deltar
1217 par_sigma2 = feffpath.sigma2
1218 par_third = feffpath.third
1219 par_ei = feffpath.ei
1221 try:
1222 atoms = [s.strip() for s in geomstr.split('>')]
1223 atoms.pop()
1224 except:
1225 title = "Cannot interpret Feff Path data"
1226 message = [f"Cannot interpret Feff path {filename}"]
1227 ExceptionPopup(self, title, message)
1229 title = '_'.join(atoms) + "%d" % (round(100*reff))
1230 for c in ',.[](){}<>+=-?/\\&%$#@!|:;"\'':
1231 title = title.replace(c, '')
1232 if title in self.paths_data:
1233 btitle = title
1234 i = -1
1235 while title in self.paths_data:
1236 i += 1
1237 title = btitle + '_%s' % string.ascii_lowercase[i]
1239 user_label = fix_varname(title)
1240 self.paths_data[title] = filename
1242 ptitle = title
1243 if ptitle.startswith(absorber):
1244 ptitle = ptitle[len(absorber):]
1245 if ptitle.startswith('_'):
1246 ptitle = ptitle[1:]
1248 # set default Path parameters if not supplied already
1249 if len(par_amp) < 1:
1250 par_amp = f'{degen:.1f} * s02'
1251 if len(par_e0) < 1:
1252 par_e0 = 'e0'
1253 if len(par_delr) < 1:
1254 par_delr = f'delr_{ptitle}'
1255 if len(par_sigma2) < 1:
1256 par_sigma2 = f'sigma2_{ptitle}'
1258 pathpanel = FeffPathPanel(self.paths_nb, self, filename, title,
1259 user_label, geomstr, absorber, shell,
1260 reff, nleg, degen, par_amp, par_e0,
1261 par_delr, par_sigma2, par_third, par_ei)
1263 self.paths_nb.AddPage(pathpanel, f' {title:s} ', True)
1265 for pname in ('amp', 'e0', 'delr', 'sigma2', 'third', 'ei'):
1266 pathpanel.onExpression(name=pname)
1268 pathpanel.enable_editing()
1270 pdat = {'title': title, 'fullpath': filename,
1271 'feffrun': feffrun, 'use':True}
1272 pdat.update(pathpanel.get_expressions())
1274 if title not in feffcache['paths']:
1275 if os.path.exists(filename):
1276 self.larch_eval(COMMANDS['cache_path'].format(**pdat))
1277 else:
1278 print(f"cannot file Feff data file '{filename}'")
1280 self.larch_eval(COMMANDS['use_path'].format(**pdat))
1282 sx,sy = self.GetSize()
1283 self.SetSize((sx, sy+1))
1284 self.SetSize((sx, sy))
1285 ipage, pagepanel = self.xasmain.get_nbpage('feffit')
1286 self.xasmain.nb.SetSelection(ipage)
1287 self.xasmain.Raise()
1289 def get_pathkeys(self):
1290 _feffpaths = getattr(self.larch.symtable, '_feffpaths', {})
1291 return [p.hashkey for p in _feffpaths.values()]
1293 def get_paramgroup(self):
1294 pgroup = getattr(self.larch.symtable, '_feffit_params', None)
1295 if pgroup is None:
1296 self.larch_eval(COMMANDS['feffit_params_init'])
1297 pgroup = getattr(self.larch.symtable, '_feffit_params', None)
1298 if not hasattr(self.larch.symtable, '_feffpaths'):
1299 self.larch_eval(COMMANDS['paths_init'])
1300 return pgroup
1302 def update_params_for_expr(self, expr=None, value=1.e-3,
1303 minval=None, maxval=None):
1304 if expr is None:
1305 return
1306 pargroup = self.get_paramgroup()
1307 symtable = pargroup.__params__._asteval.symtable
1308 extras= ''
1309 if minval is not None:
1310 extras = f', min={minval}'
1311 if maxval is not None:
1312 extras = f'{extras}, max={maxval}'
1314 try:
1315 for node in ast.walk(ast.parse(expr)):
1316 if isinstance(node, ast.Name):
1317 sym = node.id
1318 if sym not in symtable and sym not in FEFFDAT_VALUES:
1319 s = f"_feffit_params.{sym:s} = param({value:.4f}, name='{sym:s}', vary=True{extras:s})"
1320 self.larch_eval(s)
1321 result = True
1322 except:
1323 result = False
1325 self.params_panel.update()
1326 wx.CallAfter(self.skip_unused_params)
1327 return result
1329 def onLoadFitResult(self, event=None):
1330 dlg = wx.FileDialog(self, message="Load Saved Feffit Model",
1331 wildcard=ModelWcards, style=wx.FD_OPEN)
1332 rfile = None
1333 if dlg.ShowModal() == wx.ID_OK:
1334 rfile = dlg.GetPath()
1335 dlg.Destroy()
1337 if rfile is None:
1338 return
1341 def get_xranges(self, x):
1342 if self.dgroup is None:
1343 self.dgroup = self.controller.get_group()
1344 self.process(self.dgroup)
1345 opts = self.read_form(self.dgroup)
1346 dgroup = self.controller.get_group()
1347 en_eps = min(np.diff(dgroup.energy)) / 5.
1349 i1 = index_of(x, opts['emin'] + en_eps)
1350 i2 = index_of(x, opts['emax'] + en_eps) + 1
1351 return i1, i2
1353 def get_pathpage(self, name):
1354 "get nb page for a Path by name"
1355 name = name.lower().strip()
1356 for i in range(self.paths_nb.GetPageCount()):
1357 text = self.paths_nb.GetPageText(i).strip().lower()
1358 if name in text:
1359 return self.paths_nb.GetPage(i)
1361 def build_fitmodel(self, groupname=None):
1362 """ use fit components to build model"""
1363 paths = []
1364 cmds = ["### set up feffit "]
1365 pargroup = self.get_paramgroup()
1366 if self.dgroup is None:
1367 self.dgroup = self.controller.get_group()
1369 cmds.extend(self.params_panel.generate_params())
1371 self.process(self.dgroup)
1372 opts = self.read_form(self.dgroup)
1374 cmds.append(COMMANDS['feffit_trans'].format(**opts))
1376 path_pages = {}
1377 for i in range(self.paths_nb.GetPageCount()):
1378 text = self.paths_nb.GetPageText(i).strip()
1379 path_pages[text] = self.paths_nb.GetPage(i)
1381 _feffpaths = getattr(self.larch.symtable, '_feffpaths', None)
1382 if _feffpaths is None:
1383 cmds.append(COMMANDS['paths_init'])
1384 else:
1385 cmds.append(COMMANDS['paths_reset'])
1387 paths_list = []
1388 opts['paths'] = []
1389 for title, pathdata in self.paths_data.items():
1390 if title not in path_pages:
1391 continue
1392 pdat = {'title': title, 'fullpath': pathdata[0],
1393 'feffrun': pathdata[1], 'use':True}
1394 pdat.update(path_pages[title].get_expressions())
1396 #if pdat['use']:
1397 cmds.append(COMMANDS['use_path'].format(**pdat))
1398 paths_list.append(f"_feffpaths['{title:s}']")
1399 opts['paths'].append(pdat)
1401 paths_string = '[%s]' % (', '.join(paths_list))
1402 cmds.append(COMMANDS['ff2chi'].format(paths=paths_string))
1403 self.larch_eval("\n".join(cmds))
1404 return opts
1407 def get_used_params(self):
1408 used_syms = []
1409 path_pages = {}
1410 for i in range(self.paths_nb.GetPageCount()):
1411 text = self.paths_nb.GetPageText(i).strip()
1412 path_pages[text] = self.paths_nb.GetPage(i)
1413 for title in self.paths_data:
1414 if title not in path_pages:
1415 continue
1416 exprs = path_pages[title].get_expressions()
1417 if exprs['use']:
1418 for ename, expr in exprs.items():
1419 if ename in ('label', 'use'):
1420 continue
1421 for node in ast.walk(ast.parse(expr)):
1422 if isinstance(node, ast.Name):
1423 sym = node.id
1424 if sym not in used_syms:
1425 used_syms.append(sym)
1426 return used_syms
1429 def skip_unused_params(self):
1430 # find unused symbols, set to "skip"
1431 curr_syms = self.get_used_params()
1432 pargroup = self.get_paramgroup()
1433 parpanel = self.params_panel
1434 # print(group2params(pargroup).keys())
1435 for pname, par in group2params(pargroup).items():
1436 if pname not in curr_syms and pname in parpanel.parwids:
1437 par.skip = parpanel.parwids[pname].param.skip = True
1438 parpanel.parwids[pname].vary.SetStringSelection('skip')
1439 parpanel.parwids[pname].onVaryChoice()
1441 elif (pname in curr_syms and pname in parpanel.parwids
1442 and parpanel.parwids[pname].param.skip):
1443 par.skip = parpanel.parwids[pname].param.skip = False
1444 parpanel.parwids[pname].vary.SetStringSelection('vary')
1445 parpanel.parwids[pname].onVaryChoice()
1446 parpanel.update()
1448 def onFitModel(self, event=None, dgroup=None):
1449 session_history = self.get_session_history()
1450 nstart = len(session_history)
1452 script = [COMMANDS['feffit_top'].format(ctime=time.ctime())]
1454 if dgroup is None:
1455 dgroup = self.controller.get_group()
1456 opts = self.build_fitmodel(dgroup)
1458 # dgroup = opts['datagroup']
1459 fopts = dict(groupname=opts['groupname'],
1460 trans='_feffit_trans',
1461 paths='_feffpaths',
1462 params='_feffit_params')
1464 groupname = opts['groupname']
1465 filename = opts['filename']
1466 if dgroup is None:
1467 dgroup = opts['datagroup']
1470 script.append("######################################")
1471 script.append(COMMANDS['data_source'].format(groupname=groupname, filename=filename))
1472 for cmd in session_history:
1473 if groupname in cmd or filename in cmd or 'athena' in cmd or 'session' in cmd:
1474 for cline in cmd.split('\n'):
1475 script.append(f"# {cline}")
1477 script.append("#### end of data reading and preparation")
1478 script.append("######################################")
1479 script.append("## read Feff Paths into '_feffpaths'. You will need to either")
1480 script.append("## read feff.dat from disk files with `feffpath()` or use Paths")
1481 script.append("## cached from a session file into `feffcache`")
1482 script.append("#_feffcache = {'runs': {}, 'paths':{}}")
1483 script.append("#_feffpaths = {}")
1484 for path in opts['paths']:
1485 lab, fname, run = path['title'], path['fullpath'], path['feffrun']
1486 amp, e0, delr, sigma2, third, ei = path['amp'], path['e0'], path['delr'], path['sigma2'], path['third'], path['ei']
1487 script.append(f"""## Path '{lab}' : ############
1488#_feffcache['paths']['{lab}'] = feffpath('{fname}',
1489# label='{lab}', feffrun='{run}', degen=1)
1490#_feffpaths['{lab}'] = use_feffpath(_feffcache['paths'], '{lab}',
1491# s02='{amp:s}', e0='{e0:s}', deltar='{delr:s}',
1492# sigma2='{sigma2:s}', third='{third:s}', ei='{ei:s}')""")
1494 script.append("######################################")
1495 self.larch_eval(COMMANDS['do_feffit'].format(**fopts))
1498 self.wids['show_results'].Enable()
1499 self.onPlot(dgroup=opts['datagroup'], build_fitmodel=False,
1500 pargroup_name='_feffit_result.paramgroup',
1501 paths_name='_feffit_dataset.paths',
1502 pathsum_name='_feffit_dataset.model')
1504 script.extend(self.get_session_history()[nstart:])
1505 script.extend(["print(feffit_report(_feffit_result))",
1506 "#end of autosaved feffit script" , ""])
1508 if not hasattr(dgroup, 'feffit_history'):
1509 dgroup.feffit_history = []
1512 label = now = time.strftime("%b-%d %H:%M")
1513 dgroup.feffit_history[0].commands = script
1514 dgroup.feffit_history[0].timestamp = time.strftime("%Y-%b-%d %H:%M")
1515 dgroup.feffit_history[0].label = label
1517 fitlabels = [fhist.label for fhist in dgroup.feffit_history[1:]]
1518 if label in fitlabels:
1519 count = 1
1520 while label in fitlabels:
1521 label = f'{now:s}_{printable[count]:s}'
1522 count +=1
1523 dgroup.feffit_history[0].label = label
1525 sname = self.autosave_script('\n'.join(script))
1526 self.write_message("wrote feffit script to '%s'" % sname)
1528 self.show_subframe('feffit_result', FeffitResultFrame,
1529 datagroup=opts['datagroup'], feffit_panel=self)
1530 self.subframes['feffit_result'].add_results(dgroup, form=opts)
1532 def onShowResults(self, event=None):
1533 self.show_subframe('feffit_result', FeffitResultFrame,
1534 feffit_panel=self)
1536 def update_start_values(self, params):
1537 """fill parameters with best fit values"""
1538 self.params_panel.set_init_values(params)
1539 for i in range(self.paths_nb.GetPageCount()):
1540 if 'parameters' in self.paths_nb.GetPageText(i).strip().lower():
1541 self.paths_nb.SetSelection(i)
1543 def autosave_script(self, text, fname='feffit_script.lar'):
1544 """autosave model result to user larch folder"""
1545 confdir = self.controller.larix_folder
1546 if fname is None:
1547 fname = 'feffit_script.lar'
1548 fullname = os.path.join(confdir, fname)
1549 if os.path.exists(fullname):
1550 backup = os.path.join(confdir, 'feffit_script_BAK.lar')
1551 shutil.copy(fullname, backup)
1552 with open(fullname, 'w', encoding=sys.getdefaultencoding()) as fh:
1553 fh.write(text)
1554 return fullname
1557###############
1559class FeffitResultFrame(wx.Frame):
1560 def __init__(self, parent=None, feffit_panel=None, datagroup=None, **kws):
1561 wx.Frame.__init__(self, None, -1, title='Feffit Results',
1562 style=FRAMESTYLE, size=(950, 700), **kws)
1564 self.outforms = {'chik': 'chi(k), no k-weight',
1565 'chikw': 'chi(k), k-weighted',
1566 'chir_mag': '|chi(R)|',
1567 'chir_re': 'Real[chi(R)]',
1568 'chiq': 'Filtered \u03c7(k)'
1569 }
1571 self.feffit_panel = feffit_panel
1572 self.datagroup = datagroup
1573 self.feffit_history = getattr(datagroup, 'fit_history', [])
1574 self.parent = parent
1575 self.report_frame = None
1576 self.datasets = {}
1577 self.form = {}
1578 self.larch_eval = feffit_panel.larch_eval
1579 self.nfit = 0
1580 self.createMenus()
1581 self.build()
1583 if datagroup is None:
1584 symtab = self.feffit_panel.larch.symtable
1585 xasgroups = getattr(symtab, '_xasgroups', None)
1586 if xasgroups is not None:
1587 for dname, dgroup in xasgroups.items():
1588 dgroup = getattr(symtab, dgroup, None)
1589 hist = getattr(dgroup, 'feffit_history', None)
1590 if hist is not None:
1591 self.add_results(dgroup, show=True)
1594 def createMenus(self):
1595 self.menubar = wx.MenuBar()
1596 fmenu = wx.Menu()
1597 m = {}
1598 for key, desc in self.outforms.items():
1599 MenuItem(self, fmenu,
1600 f"Save Fit: {desc}",
1601 f"Save data, model, path arrays as {desc}",
1602 partial(self.onSaveFit, form=key))
1604 fmenu.AppendSeparator()
1605 self.menubar.Append(fmenu, "&File")
1606 self.SetMenuBar(self.menubar)
1608 def build(self):
1609 sizer = wx.GridBagSizer(2, 2)
1610 sizer.SetVGap(2)
1611 sizer.SetHGap(2)
1613 splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
1614 splitter.SetMinimumPaneSize(200)
1616 self.filelist = EditableListBox(splitter, self.ShowDataSet,
1617 size=(250, -1))
1618 set_color(self.filelist, 'list_fg', bg='list_bg')
1620 self.font_fixedwidth = wx.Font(FONTSIZE_FW, wx.MODERN, wx.NORMAL, wx.BOLD)
1622 panel = scrolled.ScrolledPanel(splitter)
1624 panel.SetMinSize((725, 575))
1625 panel.SetSize((850, 575))
1627 # title row
1628 self.wids = wids = {}
1629 title = SimpleText(panel, 'Feffit Results', font=Font(FONTSIZE+2),
1630 colour=COLORS['title'], style=LEFT)
1632 wids['data_title'] = SimpleText(panel, '< > ', font=Font(FONTSIZE+2),
1633 minsize=(350, -1),
1634 colour=COLORS['title'], style=LEFT)
1636 wids['plotone_op'] = Choice(panel, choices=list(PlotOne_Choices.keys()),
1637 action=self.onPlot, size=(125, -1))
1638 wids['plotone_op'].SetSelection(1)
1639 wids['plotalt_op'] = Choice(panel, choices=list(PlotAlt_Choices.keys()),
1640 action=self.onPlot, size=(125, -1))
1642 wids['plot_win'] = Choice(panel, choices=PlotWindowChoices,
1643 action=self.onPlot, size=(60, -1))
1644 wids['plot_win'].SetStringSelection('2')
1646 ppanel = wx.Panel(panel)
1647 ppanel.SetMinSize((450, 20))
1648 wids['plot_paths'] = Check(ppanel, default=False, label='Plot Each Path',
1649 action=self.onPlot)
1650 wids['plot_ftwindows'] = Check(ppanel, default=False, label='Plot FT Windows',
1651 action=self.onPlot)
1653 wids['plot_voffset'] = FloatSpin(ppanel, value=0, digits=2, increment=0.25,
1654 action=self.onPlot, size=(100, -1))
1656 psizer = wx.BoxSizer(wx.HORIZONTAL)
1657 psizer.Add( wids['plot_paths'], 0, 2)
1658 psizer.Add( wids['plot_ftwindows'], 0, 2)
1659 psizer.Add(SimpleText(ppanel, ' Offset'), 0, 2)
1660 psizer.Add( wids['plot_voffset'], 0, 2)
1661 pack(ppanel, psizer)
1663 wids['plot_current'] = Button(panel,'Plot Current Model',
1664 action=self.onPlot, size=(175, -1))
1666 wids['show_pathpars'] = Button(panel,'Show Path Parameters',
1667 action=self.onShowPathParams, size=(175, -1))
1668 wids['show_script'] = Button(panel,'Show Fit Script',
1669 action=self.onShowScript, size=(150, -1))
1671 lpanel = wx.Panel(panel)
1672 wids['fit_label'] = wx.TextCtrl(lpanel, -1, ' ', size=(175, -1))
1673 wids['set_label'] = Button(lpanel, 'Update Label', size=(150, -1),
1674 action=self.onUpdateLabel)
1675 wids['del_fit'] = Button(lpanel, 'Remove from Fit History', size=(200, -1),
1676 action=self.onRemoveFromHistory)
1678 lsizer = wx.BoxSizer(wx.HORIZONTAL)
1679 lsizer.Add(wids['fit_label'], 0, 2)
1680 lsizer.Add(wids['set_label'], 0, 2)
1681 lsizer.Add(wids['del_fit'], 0, 2)
1682 pack(lpanel, lsizer)
1684 irow = 0
1685 sizer.Add(title, (irow, 0), (1, 1), LEFT)
1686 sizer.Add(wids['data_title'], (irow, 1), (1, 3), LEFT)
1688 irow += 1
1689 sizer.Add(wids['plot_current'], (irow, 0), (1, 1), LEFT)
1690 sizer.Add(wids['plotone_op'], (irow, 1), (1, 1), LEFT)
1691 sizer.Add(ppanel, (irow, 2), (1, 3), LEFT)
1692 irow += 1
1693 sizer.Add(SimpleText(panel, 'Add Second Plot:', style=LEFT), (irow, 0), (1, 1), LEFT)
1694 sizer.Add(wids['plotalt_op'], (irow, 1), (1, 1), LEFT)
1695 sizer.Add(SimpleText(panel, 'Plot Window:', style=LEFT), (irow, 2), (1, 1), LEFT)
1696 sizer.Add(wids['plot_win'], (irow, 3), (1, 1), LEFT)
1698 irow += 1
1699 sizer.Add(wids['show_pathpars'], (irow, 0), (1, 1), LEFT)
1700 sizer.Add(wids['show_script'], (irow, 1), (1, 1), LEFT)
1701 irow += 1
1702 sizer.Add(HLine(panel, size=(650, 3)), (irow, 0), (1, 5), LEFT)
1704 irow += 1
1705 sizer.Add(SimpleText(panel, 'Fit Label:', style=LEFT), (irow, 0), (1, 1), LEFT)
1706 sizer.Add(lpanel, (irow, 1), (1, 4), LEFT)
1709 irow += 1
1710 title = SimpleText(panel, '[[Fit Statistics]]', font=Font(FONTSIZE+2),
1711 colour=COLORS['title'], style=LEFT)
1712 subtitle = SimpleText(panel, ' (most recent fit is at the top)',
1713 font=Font(FONTSIZE+1), style=LEFT)
1715 sizer.Add(title, (irow, 0), (1, 1), LEFT)
1716 sizer.Add(subtitle, (irow, 1), (1, 3), LEFT)
1718 sview = self.wids['stats'] = dv.DataViewListCtrl(panel, style=DVSTYLE)
1719 sview.SetFont(self.font_fixedwidth)
1720 sview.Bind(dv.EVT_DATAVIEW_SELECTION_CHANGED, self.onSelectFit)
1721 sview.AppendTextColumn(' # ', width=40)
1722 sview.AppendTextColumn('Label', width=140)
1723 sview.AppendTextColumn('Npaths', width=70)
1724 sview.AppendTextColumn('Nvary', width=60)
1725 sview.AppendTextColumn('Nidp', width=60)
1726 sview.AppendTextColumn('\u03c7\u00B2', width=75)
1727 sview.AppendTextColumn('reduced \u03c7\u00B2', width=95)
1728 sview.AppendTextColumn('R Factor', width=80)
1729 sview.AppendTextColumn('Akaike Info', width=85)
1732 for col in range(sview.ColumnCount):
1733 this = sview.Columns[col]
1734 this.Sortable = True
1735 this.Alignment = wx.ALIGN_RIGHT if col > 1 else wx.ALIGN_LEFT
1736 this.Renderer.Alignment = this.Alignment
1738 sview.SetMinSize((750, 150))
1740 irow += 1
1741 sizer.Add(sview, (irow, 0), (1, 5), LEFT)
1743 irow += 1
1744 title = SimpleText(panel, '[[Variables]]', font=Font(FONTSIZE+2),
1745 colour=COLORS['title'], style=LEFT)
1746 sizer.Add(title, (irow, 0), (1, 1), LEFT)
1748 self.wids['copy_params'] = Button(panel, 'Update Model with these values',
1749 size=(225, -1), action=self.onCopyParams)
1751 sizer.Add(self.wids['copy_params'], (irow, 1), (1, 3), LEFT)
1753 pview = self.wids['params'] = dv.DataViewListCtrl(panel, style=DVSTYLE)
1754 pview.SetFont(self.font_fixedwidth)
1755 self.wids['paramsdata'] = []
1756 pview.AppendTextColumn('Parameter', width=175)
1757 pview.AppendTextColumn('Best-Fit Value', width=125)
1758 pview.AppendTextColumn('Standard Error', width=125)
1759 pview.AppendTextColumn('Info ', width=225)
1761 for col in range(4):
1762 this = pview.Columns[col]
1763 this.Sortable = False
1764 this.Alignment = wx.ALIGN_RIGHT if col in (1, 2) else wx.ALIGN_LEFT
1765 this.Renderer.Alignment = this.Alignment
1767 pview.SetMinSize((750, 200))
1768 pview.Bind(dv.EVT_DATAVIEW_SELECTION_CHANGED, self.onSelectParameter)
1770 irow += 1
1771 sizer.Add(pview, (irow, 0), (1, 5), LEFT)
1773 irow += 1
1774 title = SimpleText(panel, '[[Correlations]]', font=Font(FONTSIZE+2),
1775 colour=COLORS['title'], style=LEFT)
1777 ppanel = wx.Panel(panel)
1778 ppanel.SetMinSize((450, 20))
1779 self.wids['all_correl'] = Button(ppanel, 'Show All',
1780 size=(100, -1), action=self.onAllCorrel)
1782 self.wids['min_correl'] = FloatSpin(ppanel, value=MIN_CORREL,
1783 min_val=0, size=(100, -1),
1784 digits=3, increment=0.1)
1786 psizer = wx.BoxSizer(wx.HORIZONTAL)
1787 psizer.Add(SimpleText(ppanel, 'minimum correlation: '), 0, 2)
1788 psizer.Add(self.wids['min_correl'], 0, 2)
1789 psizer.Add(self.wids['all_correl'], 0, 2)
1790 pack(ppanel, psizer)
1792 sizer.Add(title, (irow, 0), (1, 1), LEFT)
1793 sizer.Add(ppanel, (irow, 1), (1, 4), LEFT)
1795 cview = self.wids['correl'] = dv.DataViewListCtrl(panel, style=DVSTYLE)
1796 cview.SetFont(self.font_fixedwidth)
1798 cview.AppendTextColumn('Parameter 1', width=150)
1799 cview.AppendTextColumn('Parameter 2', width=150)
1800 cview.AppendTextColumn('Correlation', width=150)
1802 for col in (0, 1, 2):
1803 this = cview.Columns[col]
1804 this.Sortable = False
1805 align = wx.ALIGN_LEFT
1806 if col == 2:
1807 align = wx.ALIGN_RIGHT
1808 this.Alignment = this.Renderer.Alignment = align
1809 cview.SetMinSize((550, 150))
1811 irow += 1
1812 sizer.Add(cview, (irow, 0), (1, 5), LEFT)
1814 pack(panel, sizer)
1815 panel.SetupScrolling()
1817 splitter.SplitVertically(self.filelist, panel, 1)
1819 mainsizer = wx.BoxSizer(wx.VERTICAL)
1820 mainsizer.Add(splitter, 1, wx.GROW|wx.ALL, 5)
1822 pack(self, mainsizer)
1823 self.Show()
1824 self.Raise()
1826 def show_report(self, text, title='Text', default_filename='out.txt',
1827 wildcard=None):
1828 if wildcard is None:
1829 wildcard='Text Files (*.txt)|*.txt'
1830 try:
1831 self.report_frame.set_text(text)
1832 self.report_frame.SetTitle(title)
1833 self.report_frame.default_filename = default_filename
1834 self.report_frame.wildcard = wildcard
1835 except:
1836 self.report_frame = ReportFrame(parent=self.parent,
1837 text=text, title=title,
1838 default_filename=default_filename,
1839 wildcard=wildcard)
1842 def onShowPathParams(self, event=None):
1843 result = self.get_fitresult()
1844 if result is None:
1845 return
1846 text = f'# Feffit Report for {self.datagroup.filename} fit "{result.label}"\n'
1847 text = text + feffit_report(result)
1848 title = f'Report for {self.datagroup.filename} fit "{result.label}"'
1849 fname = fix_filename(f'{self.datagroup.filename}_{result.label}.txt')
1850 self.show_report(text, title=title, default_filename=fname)
1852 def onShowScript(self, event=None):
1853 result = self.get_fitresult()
1854 if result is None:
1855 return
1856 text = [f'# Feffit Script for {self.datagroup.filename} fit "{result.label}"']
1857 text.extend(result.commands)
1858 text = '\n'.join(text)
1859 title = f'Script for {self.datagroup.filename} fit "{result.label}"'
1860 fname = fix_filename(f'{self.datagroup.filename}_{result.label}.lar')
1861 self.show_report(text, title=title, default_filename=fname,
1862 wildcard='Larch/Python Script (*.lar)|*.lar')
1864 def onUpdateLabel(self, event=None):
1865 result = self.get_fitresult()
1866 if result is None:
1867 return
1868 item = self.wids['stats'].GetSelectedRow()
1869 result.label = self.wids['fit_label'].GetValue()
1870 self.show_results()
1872 def onRemoveFromHistory(self, event=None):
1873 result = self.get_fitresult()
1874 if result is None:
1875 return
1876 if wx.ID_YES != Popup(self,
1877 f"Remove fit '{result.label}' from history?\nThis cannot be undone.",
1878 "Remove fit?", style=wx.YES_NO):
1879 return
1880 self.datagroup.feffit_history.pop(self.nfit)
1881 self.nfit = 0
1882 self.show_results()
1884 def onPlot(self, event=None):
1886 opts = {'build_fitmodel': False}
1887 for key, meth in (('plot_ftwindows', 'IsChecked'),
1888 ('plot_paths', 'IsChecked'),
1889 ('plotone_op', 'GetStringSelection'),
1890 ('plotalt_op', 'GetStringSelection'),
1891 ('plot_win', 'GetStringSelection'),
1892 ('plot_voffset', 'GetValue')):
1893 opts[key] = getattr(self.wids[key], meth)()
1895 opts['plotone_op'] = PlotOne_Choices[opts['plotone_op']]
1896 opts['plotalt_op'] = PlotAlt_Choices[opts['plotalt_op']]
1897 opts['plot_win'] = int(opts['plot_win'])
1899 result = self.get_fitresult()
1900 if result is None:
1901 return
1902 dset = result.datasets[0]
1903 dgroup = dset.data
1904 if not hasattr(dset.data, 'rwin'):
1905 dset._residual(result.params)
1906 dset.save_ffts()
1907 trans = dset.transform
1908 dset.prepare_fit(group2params(result.paramgroup))
1909 dset._residual(result.paramgroup)
1911 result_name = f'{self.datagroup.groupname}.feffit_history[{self.nfit}]'
1912 opts['label'] = f'{result_name}.label'
1913 opts['pargroup_name'] = f'{result_name}.paramgroup'
1914 opts['paths_name'] = f'{result_name}.datasets[0].paths'
1915 opts['pathsum_name'] = f'{result_name}.datasets[0].model'
1916 opts['dataset_name'] = f'{result_name}.datasets[0].data'
1917 opts['dgroup'] = dgroup
1918 opts['title'] = f'{self.datagroup.filename}: {result.label}'
1920 for attr in ('kmin', 'kmax', 'dk', 'rmin', 'rmax', 'fitspace'):
1921 opts[attr] = getattr(trans, attr)
1922 opts['fit_kwstring'] = "%s" % getattr(trans, 'kweight')
1923 opts['kwindow'] = getattr(trans, 'window')
1924 opts['topwin'] = self
1926 self.feffit_panel.onPlot(**opts)
1929 def onSaveFitCommand(self, event=None):
1930 wildcard = 'Larch/Python Script (*.lar)|*.lar|All files (*.*)|*.*'
1931 result = self.get_fitresult()
1932 if result is None:
1933 return
1934 fname = fix_filename(f'{self.datagroup.filename}_{result.label:s}.lar')
1936 path = FileSave(self, message='Save text to file',
1937 wildcard=wildcard, default_file=fname)
1938 if path is not None:
1939 text = '\n'.join(result.commands)
1940 with open(path, 'w', encoding=sys.getdefaultencoding()) as fh:
1941 fh.write(text)
1942 fh.write('')
1945 def onSaveFit(self, evt=None, form='chikw'):
1946 "Save arrays to text file"
1947 result = self.get_fitresult()
1948 if result is None:
1949 return
1951 fname = fix_filename(f'{self.datagroup.filename}_{result.label:s}_{form}')
1952 fname = fname.replace('.', '_')
1953 fname = fname + '.txt'
1955 wildcard = 'Text Files (*.txt)|*.txt|All files (*.*)|*.*'
1956 savefile = FileSave(self, 'Save Fit Model (%s)' % form,
1957 default_file=fname,
1958 wildcard=wildcard)
1959 if savefile is None:
1960 return
1962 text = feffit_report(result)
1963 desc = self.outforms[form]
1964 buff = [f'# Results for {self.datagroup.filename} "{result.label}": {desc}']
1966 for line in text.split('\n'):
1967 buff.append('# %s' % line)
1968 buff.append('## ')
1969 buff.append('#' + '---'*25)
1971 ds0 = result.datasets[0]
1973 xname = 'k' if form.startswith('chik') else 'r'
1974 yname = 'chi' if form.startswith('chik') else form
1975 kw = 0
1976 if form == 'chikw':
1977 kw = ds0.transform.kweight
1979 xarr = getattr(ds0.data, xname)
1980 nx = len(xarr)
1981 ydata = getattr(ds0.data, yname) * xarr**kw
1982 ymodel = getattr(ds0.model, yname) * xarr**kw
1983 out = [xarr, ydata, ymodel]
1985 array_names = [xname, 'expdata', 'model']
1986 for pname, pgroup in ds0.paths.items():
1987 array_names.append(f'feffpath_{pname}')
1988 out.append(getattr(pgroup, yname)[:nx] * xarr**kw)
1990 col_labels = []
1991 for a in array_names:
1992 if len(a) < 13:
1993 a = (a + ' '*13)[:13]
1994 col_labels.append(a)
1996 buff.append('# ' + ' '.join(col_labels))
1998 for i in range(nx):
1999 words = [gformat(x[i], 12) for x in out]
2000 buff.append(' '.join(words))
2001 buff.append('')
2004 with open(savefile, 'w', encoding=sys.getdefaultencoding()) as fh:
2005 fh.write('\n'.join(buff))
2007 def get_fitresult(self, nfit=None):
2008 if nfit is None:
2009 nfit = self.nfit
2010 self.feffit_history = getattr(self.datagroup, 'feffit_history', [])
2011 self.nfit = max(0, nfit)
2012 n_hist = len(self.feffit_history)
2013 if n_hist == 0:
2014 return None
2015 if self.nfit > n_hist:
2016 self.nfit = 0
2017 return self.feffit_history[self.nfit]
2020 def onSelectFit(self, evt=None):
2021 if self.wids['stats'] is None:
2022 return
2023 item = self.wids['stats'].GetSelectedRow()
2024 if item > -1:
2025 self.show_fitresult(nfit=item)
2027 def onSelectParameter(self, evt=None):
2028 if self.wids['params'] is None:
2029 return
2030 if not self.wids['params'].HasSelection():
2031 return
2032 item = self.wids['params'].GetSelectedRow()
2033 pname = self.wids['paramsdata'][item]
2035 cormin= self.wids['min_correl'].GetValue()
2036 self.wids['correl'].DeleteAllItems()
2038 result = self.get_fitresult()
2039 if result is None:
2040 return
2041 this = result.params[pname]
2042 if this.correl is not None:
2043 sort_correl = sorted(this.correl.items(), key=lambda it: abs(it[1]))
2044 for name, corval in reversed(sort_correl):
2045 if abs(corval) > cormin:
2046 self.wids['correl'].AppendItem((pname, name, "% .4f" % corval))
2048 def onAllCorrel(self, evt=None):
2049 result = self.get_fitresult()
2050 if result is None:
2051 return
2052 params = result.params
2053 parnames = list(params.keys())
2055 cormin= self.wids['min_correl'].GetValue()
2056 correls = {}
2057 for i, name in enumerate(parnames):
2058 par = params[name]
2059 if not par.vary:
2060 continue
2061 if hasattr(par, 'correl') and par.correl is not None:
2062 for name2 in parnames[i+1:]:
2063 if (name != name2 and name2 in par.correl and
2064 abs(par.correl[name2]) > cormin):
2065 correls["%s$$%s" % (name, name2)] = par.correl[name2]
2067 sort_correl = sorted(correls.items(), key=lambda it: abs(it[1]))
2068 sort_correl.reverse()
2070 self.wids['correl'].DeleteAllItems()
2072 for namepair, corval in sort_correl:
2073 name1, name2 = namepair.split('$$')
2074 self.wids['correl'].AppendItem((name1, name2, "% .4f" % corval))
2076 def onCopyParams(self, evt=None):
2077 result = self.get_fitresult()
2078 if result is None:
2079 return
2080 self.feffit_panel.update_start_values(result.params)
2082 def ShowDataSet(self, evt=None):
2083 dataset = evt.GetString()
2084 group = self.datasets.get(evt.GetString(), None)
2085 if group is not None:
2086 self.show_results(datagroup=group)
2088 def add_results(self, dgroup, form=None, larch_eval=None, show=True):
2089 name = dgroup.filename
2090 if name not in self.filelist.GetItems():
2091 self.filelist.Append(name)
2092 self.datasets[name] = dgroup
2093 if show:
2094 self.show_results(datagroup=dgroup, form=form, larch_eval=larch_eval)
2096 def show_results(self, datagroup=None, form=None, larch_eval=None):
2097 if datagroup is not None:
2098 self.datagroup = datagroup
2099 if larch_eval is not None:
2100 self.larch_eval = larch_eval
2102 datagroup = self.datagroup
2103 self.feffit_history = getattr(self.datagroup, 'feffit_history', [])
2105 cur = self.get_fitresult()
2106 if cur is None:
2107 return
2108 wids = self.wids
2109 wids['stats'].DeleteAllItems()
2110 for i, res in enumerate(self.feffit_history):
2111 args = ["%d" % (i+1), res.label, "%.d" % (len(res.datasets[0].paths))]
2112 for attr in ('nvarys', 'n_independent', 'chi_square',
2113 'chi2_reduced', 'rfactor', 'aic'):
2114 val = getattr(res, attr)
2115 if isinstance(val, int):
2116 val = '%d' % val
2117 elif attr == 'n_independent':
2118 val = "%.2f" % val
2119 else:
2120 val = "%.4f" % val
2121 # val = gformat(val, 9)
2122 args.append(val)
2123 wids['stats'].AppendItem(tuple(args))
2124 wids['data_title'].SetLabel(self.datagroup.filename)
2125 self.show_fitresult(nfit=0)
2128 def show_fitresult(self, nfit=0, datagroup=None):
2129 if datagroup is not None:
2130 self.datagroup = datagroup
2132 result = self.get_fitresult(nfit=nfit)
2133 if result is None:
2134 return
2136 path_hashkeys = []
2137 for ds in result.datasets:
2138 path_hashkeys.extend([p.hashkey for p in ds.paths.values()])
2140 wids = self.wids
2141 wids['fit_label'].SetValue(result.label)
2142 wids['data_title'].SetLabel(self.datagroup.filename)
2143 wids['params'].DeleteAllItems()
2144 wids['paramsdata'] = []
2145 for param in reversed(result.params.values()):
2146 pname = param.name
2147 if any([pname.endswith('_%s' % phash) for phash in path_hashkeys]):
2148 continue
2149 if getattr(param, 'skip', None) not in (False, None):
2150 continue
2152 try:
2153 val = gformat(param.value, 10)
2154 except (TypeError, ValueError):
2155 val = ' ??? '
2156 serr = ' N/A '
2157 if param.stderr is not None:
2158 serr = gformat(param.stderr, 10)
2159 extra = ' '
2160 if param.expr is not None:
2161 extra = '= %s ' % param.expr
2162 elif not param.vary:
2163 extra = '(fixed)'
2164 elif param.init_value is not None:
2165 extra = '(init=%s)' % gformat(param.init_value, 10)
2167 wids['params'].AppendItem((pname, val, serr, extra))
2168 wids['paramsdata'].append(pname)
2169 self.Refresh()