Coverage for /Users/Newville/Codes/xraylarch/larch/wxxas/lincombo_panel.py: 8%
722 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"""
3Linear Combination panel
4"""
5import os
6import sys
7import time
9import wx
10import wx.lib.scrolledpanel as scrolled
11import wx.dataview as dv
12import numpy as np
14from functools import partial
16import lmfit
17from lmfit.printfuncs import fit_report
19from larch import Group
20from larch.math import index_of
21from larch.xafs import etok, ktoe
23from larch.wxlib import (BitmapButton, FloatCtrl, FloatSpin, ToggleButton,
24 GridPanel, get_icon, SimpleText, pack, Button,
25 HLine, Choice, Check, CEN, LEFT, Font, FONTSIZE,
26 FONTSIZE_FW, MenuItem, FRAMESTYLE, COLORS,
27 set_color, FileSave, EditableListBox,
28 DataTableGrid)
30from .taskpanel import TaskPanel
31from .config import ARRAYS, Linear_ArrayChoices, PlotWindowChoices
32from larch.io import write_ascii
33from larch.utils import gformat
35np.seterr(all='ignore')
37# plot options:
38Plot_Choices = ['Data + Sum', 'Data + Sum + Components']
40DVSTYLE = dv.DV_SINGLE|dv.DV_VERT_RULES|dv.DV_ROW_LINES
42MAX_COMPONENTS = 12
44def make_lcfplot(dgroup, form, with_fit=True, nfit=0):
45 """make larch plot commands to plot LCF fit from form"""
46 form['group'] = dgroup.groupname
47 form['filename'] = dgroup.filename
48 form['nfit'] = nfit
49 form['label'] = label = 'Fit #%2.2d' % (nfit+1)
51 if 'win' not in form: form['win'] = 1
52 kspace = form['arrayname'].startswith('chi')
53 if kspace:
54 kw = 0
55 if len(form['arrayname']) > 3:
56 kw = int(form['arrayname'][3:])
57 form['plotopt'] = 'kweight=%d' % kw
59 cmds = ["""plot_chik({group:s}, {plotopt:s}, delay_draw=False, label='data',
60 show_window=False, title='{filename:s}, {label:s}', win={win:d})"""]
62 else:
63 form['plotopt'] = 'show_norm=False'
64 if form['arrayname'] == 'norm':
65 form['plotopt'] = 'show_norm=True'
66 elif form['arrayname'] == 'flat':
67 form['plotopt'] = 'show_flat=True'
68 elif form['arrayname'] == 'dmude':
69 form['plotopt'] = 'show_deriv=True'
71 erange = form['ehi'] - form['elo']
72 form['pemin'] = 10*int( (form['elo'] - 5 - erange/4.0) / 10.0)
73 form['pemax'] = 10*int( (form['ehi'] + 5 + erange/4.0) / 10.0)
75 cmds = ["""plot_mu({group:s}, {plotopt:s}, delay_draw=True, label='data',
76 emin={pemin:.1f}, emax={pemax:.1f}, title='{filename:s}, {label:s}', win={win:d})"""]
78 if with_fit and hasattr(dgroup, 'lcf_result'):
79 with_comps = True # "Components" in form['plotchoice']
80 delay = 'delay_draw=True' if with_comps else 'delay_draw=False'
81 xarr = "{group:s}.lcf_result[{nfit:d}].xdata"
82 yfit = "{group:s}.lcf_result[{nfit:d}].yfit"
83 ycmp = "{group:s}.lcf_result[{nfit:d}].ycomps"
84 cmds.append("plot(%s, %s, label='%s', zorder=30, %s, win={win:d})" % (xarr, yfit, label, delay))
85 ncomps = len(dgroup.lcf_result[nfit].ycomps)
86 if with_comps:
87 for i, key in enumerate(dgroup.lcf_result[nfit].ycomps):
88 delay = 'delay_draw=False' if i==(ncomps-1) else 'delay_draw=True'
89 cmds.append("plot(%s, %s['%s'], label='%s', %s, win={win:d})" % (xarr, ycmp, key, key, delay))
91 # if form['show_e0']:
92 # cmds.append("plot_axvline({e0:1f}, color='#DDDDCC', zorder=-10)")
93 if form['show_fitrange']:
94 cmds.append("plot_axvline({elo:1f}, color='#EECCCC', zorder=-10, win={win:d})")
95 cmds.append("plot_axvline({ehi:1f}, color='#EECCCC', zorder=-10, win={win:d})")
97 script = "\n".join(cmds)
98 return script.format(**form)
100class LinComboResultFrame(wx.Frame):
101 def __init__(self, parent=None, datagroup=None, mainpanel=None, **kws):
102 wx.Frame.__init__(self, None, -1, title='Linear Combination Results',
103 style=FRAMESTYLE, size=(925, 675), **kws)
104 self.parent = parent
105 self.mainpanel = mainpanel
106 self.datagroup = datagroup
107 self.datasets = {}
108 self.form = self.mainpanel.read_form()
109 self.larch_eval = self.mainpanel.larch_eval
110 self.current_fit = 0
111 self.createMenus()
112 self.build()
114 if self.mainpanel is not None:
115 symtab = self.mainpanel.larch.symtable
116 xasgroups = getattr(symtab, '_xasgroups', None)
117 if xasgroups is not None:
118 for dname, dgroup in xasgroups.items():
119 dgroup = getattr(symtab, dgroup, None)
120 hist = getattr(dgroup, 'lcf_result', None)
121 if hist is not None:
122 self.add_results(dgroup, show=False)
124 def createMenus(self):
125 self.menubar = wx.MenuBar()
126 fmenu = wx.Menu()
127 m = {}
129 MenuItem(self, fmenu, "Export current fit as group",
130 "Export current fit to a new group in the main panel", self.onExportGroupFit)
132 MenuItem(self, fmenu, "Save Fit And Components for Current Group",
133 "Save Fit and Components to Data File for Current Group", self.onSaveGroupFit)
135 MenuItem(self, fmenu, "Save Statistics for Best N Fits for Current Group",
136 "Save Statistics and Weights for Best N Fits for Current Group", self.onSaveGroupStats)
138 MenuItem(self, fmenu, "Save Data and Best N Fits for Current Group",
139 "Save Data and Best N Fits for Current Group", self.onSaveGroupMultiFits)
141 fmenu.AppendSeparator()
142 MenuItem(self, fmenu, "Save Statistics Report for All Fitted Groups",
143 "Save Statistics for All Fitted Groups", self.onSaveAllStats)
145 self.menubar.Append(fmenu, "&File")
146 self.SetMenuBar(self.menubar)
148 def build(self):
149 sizer = wx.GridBagSizer(3, 3)
150 sizer.SetVGap(3)
151 sizer.SetHGap(3)
153 splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
154 splitter.SetMinimumPaneSize(200)
156 dl = self.filelist = EditableListBox(splitter, self.ShowDataSet,
157 size=(250, -1))
158 set_color(self.filelist, 'list_fg', bg='list_bg')
161 panel = scrolled.ScrolledPanel(splitter)
163 self.SetMinSize((650, 600))
165 self.font_fixedwidth = wx.Font(FONTSIZE_FW, wx.MODERN, wx.NORMAL, wx.BOLD)
167 self.wids = wids = {}
168 wids['plot_one'] = Button(panel, 'Plot This Fit', size=(125, -1),
169 action=self.onPlotOne)
170 wids['plot_sel'] = Button(panel, 'Plot N Best Fits', size=(125, -1),
171 action=self.onPlotSel)
173 wids['plot_win'] = Choice(panel, choices=PlotWindowChoices,
174 action=self.onPlotOne, size=(60, -1))
175 wids['plot_win'].SetStringSelection('1')
177 wids['plot_wtitle'] = SimpleText(panel, 'Plot Window: ')
178 wids['plot_ntitle'] = SimpleText(panel, 'N fits to plot: ')
180 wids['plot_nchoice'] = Choice(panel, size=(60, -1),
181 choices=['%d' % i for i in range(1, 21)])
182 wids['plot_nchoice'].SetStringSelection('5')
184 wids['data_title'] = SimpleText(panel, 'Linear Combination Result: <> ',
185 font=Font(FONTSIZE+2),
186 size=(400, -1),
187 colour=COLORS['title'], style=LEFT)
188 wids['nfits_title'] = SimpleText(panel, 'showing 5 best fits')
189 wids['fitspace_title'] = SimpleText(panel, 'Array Fit: ')
191 copts = dict(size=(125, 30), default=True, action=self.onPlotOne)
192 # wids['show_e0'] = Check(panel, label='show E0?', **copts)
193 wids['show_fitrange'] = Check(panel, label='show fit range?', **copts)
195 irow = 0
196 sizer.Add(wids['data_title'], (irow, 0), (1, 3), LEFT)
198 irow += 1
199 sizer.Add(wids['nfits_title'], (irow, 0), (1, 1), LEFT)
200 sizer.Add(wids['fitspace_title'], (irow, 1), (1, 2), LEFT)
203 irow += 1
204 self.wids['paramstitle'] = SimpleText(panel, '[[Parameters]]',
205 font=Font(FONTSIZE+2),
206 colour=COLORS['title'], style=LEFT)
207 sizer.Add(self.wids['paramstitle'], (irow, 0), (1, 3), LEFT)
210 pview = self.wids['params'] = dv.DataViewListCtrl(panel, style=DVSTYLE)
211 pview.SetFont(self.font_fixedwidth)
212 pview.SetMinSize((500, 200))
213 pview.AppendTextColumn(' Parameter ', width=180)
214 pview.AppendTextColumn(' Best-Fit Value', width=150)
215 pview.AppendTextColumn(' Standard Error ', width=150)
216 for col in range(3):
217 this = pview.Columns[col]
218 isort, align = True, wx.ALIGN_RIGHT
219 if col == 0:
220 align = wx.ALIGN_LEFT
221 this.Sortable = isort
222 this.Alignment = this.Renderer.Alignment = align
224 irow += 1
225 sizer.Add(self.wids['params'], (irow, 0), (7, 2), LEFT)
226 sizer.Add(self.wids['plot_one'], (irow, 2), (1, 2), LEFT)
228 sizer.Add(self.wids['plot_wtitle'], (irow+1, 2), (1, 1), LEFT)
229 sizer.Add(self.wids['plot_win'], (irow+1, 3), (1, 1), LEFT)
232 sizer.Add(self.wids['show_fitrange'],(irow+2, 2), (1, 2), LEFT)
233 sizer.Add((5, 5), (irow+3, 2), (1, 2), LEFT)
234 sizer.Add(self.wids['plot_sel'], (irow+4, 2), (1, 2), LEFT)
235 sizer.Add(self.wids['plot_ntitle'], (irow+5, 2), (1, 1), LEFT)
236 sizer.Add(self.wids['plot_nchoice'], (irow+5, 3), (1, 1), LEFT)
237 # sizer.Add(self.wids['show_e0'], (irow+3, 1), (1, 2), LEFT)
240 irow += 7
241 sizer.Add(HLine(panel, size=(675, 3)), (irow, 0), (1, 4), LEFT)
243 sview = self.wids['stats'] = dv.DataViewListCtrl(panel, style=DVSTYLE)
244 sview.SetFont(self.font_fixedwidth)
245 sview.Bind(dv.EVT_DATAVIEW_SELECTION_CHANGED, self.onSelectFitStat)
246 sview.AppendTextColumn(' Fit #', width=65)
247 sview.AppendTextColumn(' N_vary', width=80)
248 sview.AppendTextColumn(' N_eval', width=80)
249 sview.AppendTextColumn(' \u03c7\u00B2', width=100)
250 sview.AppendTextColumn(' \u03c7\u00B2_reduced', width=100)
251 sview.AppendTextColumn(' R Factor', width=100)
252 sview.AppendTextColumn(' Akaike Info', width=100)
254 for col in range(sview.ColumnCount):
255 this = sview.Columns[col]
256 isort, align = True, wx.ALIGN_RIGHT
257 if col == 0:
258 align = wx.ALIGN_CENTER
259 this.Sortable = isort
260 this.Alignment = this.Renderer.Alignment = align
262 sview.SetMinSize((700, 175))
264 irow += 1
265 title = SimpleText(panel, '[[Fit Statistics]]', font=Font(FONTSIZE+2),
266 colour=COLORS['title'], style=LEFT)
267 sizer.Add(title, (irow, 0), (1, 4), LEFT)
269 irow += 1
270 sizer.Add(sview, (irow, 0), (1, 4), LEFT)
272 irow += 1
273 sizer.Add(HLine(panel, size=(675, 3)), (irow, 0), (1, 4), LEFT)
275 irow += 1
276 title = SimpleText(panel, '[[Weights]]', font=Font(FONTSIZE+2),
277 colour=COLORS['title'], style=LEFT)
278 sizer.Add(title, (irow, 0), (1, 4), LEFT)
279 self.wids['weightspanel'] = ppan = wx.Panel(panel)
281 p1 = SimpleText(ppan, ' < Weights > ')
282 os = wx.BoxSizer(wx.VERTICAL)
283 os.Add(p1, 1, 3)
284 pack(ppan, os)
285 ppan.SetMinSize((700, 175))
287 irow += 1
288 sizer.Add(ppan, (irow, 0), (1, 4), LEFT)
290 irow += 1
291 sizer.Add(HLine(panel, size=(675, 3)), (irow, 0), (1, 4), LEFT)
293 pack(panel, sizer)
294 panel.SetupScrolling()
296 splitter.SplitVertically(self.filelist, panel, 1)
298 mainsizer = wx.BoxSizer(wx.VERTICAL)
299 mainsizer.Add(splitter, 1, wx.GROW|wx.ALL, 5)
301 pack(self, mainsizer)
302 # self.SetSize((725, 750))
303 self.Show()
304 self.Raise()
306 def ShowDataSet(self, evt=None):
307 dataset = evt.GetString()
308 group = self.datasets.get(evt.GetString(), None)
309 if group is not None:
310 self.show_results(datagroup=group)
312 def add_results(self, dgroup, form=None, larch_eval=None, show=True):
313 name = dgroup.filename
314 if name not in self.filelist.GetItems():
315 self.filelist.Append(name)
316 self.datasets[name] = dgroup
317 if show:
318 self.show_results(datagroup=dgroup, form=form, larch_eval=larch_eval)
320 def show_results(self, datagroup=None, form=None, larch_eval=None):
321 if datagroup is not None:
322 self.datagroup = datagroup
323 if form is not None:
324 self.form = form
325 if larch_eval is not None:
326 self.larch_eval = larch_eval
328 form = self.form
329 if form is None:
330 form = self.mainpanel.read_form()
331 datagroup = self.datagroup
333 wids = self.wids
334 wids['data_title'].SetLabel('Linear Combination Result: %s ' % self.datagroup.filename)
335 wids['show_fitrange'].SetValue(form['show_fitrange'])
337 wids['stats'].DeleteAllItems()
338 if not hasattr(self.datagroup, 'lcf_result'):
339 return
340 results = self.datagroup.lcf_result[:20]
341 self.nresults = len(results)
342 wids['nfits_title'].SetLabel('showing %i best results' % (self.nresults,))
343 wids['fitspace_title'].SetLabel('Array Fit: %s' % ARRAYS.get(results[0].arrayname, 'unknown'))
345 for i, res in enumerate(results):
346 res.result.rfactor = getattr(res, 'rfactor', 0)
347 args = ['%2.2d' % (i+1)]
348 for attr in ('nvarys', 'nfev', 'chisqr', 'redchi', 'rfactor', 'aic'):
349 val = getattr(res.result, attr)
350 if isinstance(val, int):
351 val = '%d' % val
352 elif attr in ('aic',):
353 val = "%.2f" % val
354 else:
355 val = gformat(val, 10)
356 args.append(val)
357 wids['stats'].AppendItem(tuple(args))
359 wpan = self.wids['weightspanel']
360 wpan.DestroyChildren()
362 wview = self.wids['weights'] = dv.DataViewListCtrl(wpan, style=DVSTYLE)
363 wview.SetFont(self.font_fixedwidth)
364 wview.Bind(dv.EVT_DATAVIEW_SELECTION_CHANGED, self.onSelectFitParam)
365 wview.AppendTextColumn(' Fit #', width=65)
366 wview.AppendTextColumn(' E shift', width=80)
368 for i, cname in enumerate(form['comp_names']):
369 wview.AppendTextColumn(cname, width=100)
370 wview.AppendTextColumn('Total', width=100)
372 for col in range(len(form['comp_names'])+2):
373 this = wview.Columns[col]
374 isort, align = True, wx.ALIGN_RIGHT
375 if col == 0:
376 align = wx.ALIGN_CENTER
377 this.Sortable = isort
378 this.Alignment = this.Renderer.Alignment = align
380 for i, res in enumerate(results):
381 args = ['%2.2d' % (i+1), "%.4f" % res.params['e0_shift'].value]
382 for cname in form['comp_names'] + ['total']:
383 val = '--'
384 if cname in res.params:
385 val = "%.4f" % res.params[cname].value
386 args.append(val)
387 wview.AppendItem(tuple(args))
389 os = wx.BoxSizer(wx.VERTICAL)
390 os.Add(wview, 1, wx.GROW|wx.ALL)
391 pack(wpan, os)
393 wview.SetMinSize((700, 500))
394 s1, s2 = self.GetSize()
395 if s2 % 2 == 0:
396 s2 = s2 + 1
397 else:
398 s2 = s2 - 1
399 self.SetSize((s1, s2))
400 self.show_fitresult(0)
401 self.Refresh()
403 def onSelectFitParam(self, evt=None):
404 if self.wids['weights'] is None:
405 return
406 item = self.wids['weights'].GetSelectedRow()
407 self.show_fitresult(item)
409 def onSelectFitStat(self, evt=None):
410 if self.wids['stats'] is None:
411 return
412 item = self.wids['stats'].GetSelectedRow()
413 self.show_fitresult(item)
415 def show_fitresult(self, n):
416 fit_result = self.datagroup.lcf_result[n]
417 self.current_fit = n
418 wids = self.wids
419 wids['nfits_title'].SetLabel('Showing Fit # %2.2d' % (n+1,))
420 wids['fitspace_title'].SetLabel('Array Fit: %s' % ARRAYS.get(fit_result.arrayname, 'unknown'))
421 wids['paramstitle'].SetLabel('[[Parameters for Fit # %2.2d]]' % (n+1))
423 wids['params'].DeleteAllItems()
425 for pname, par in fit_result.params.items():
426 args = [pname, gformat(par.value, 10), '--']
427 if par.stderr is not None:
428 args[2] = gformat(par.stderr, 10)
429 self.wids['params'].AppendItem(tuple(args))
431 def onPlotOne(self, evt=None):
432 self.form = self.mainpanel.read_form()
433 self.form['show_fitrange'] = self.wids['show_fitrange'].GetValue()
434 self.form['win'] = int(self.wids['plot_win'].GetStringSelection())
435 self.larch_eval(make_lcfplot(self.datagroup,
436 self.form, nfit=self.current_fit))
437 self.parent.controller.set_focus(topwin=self)
439 def onPlotSel(self, evt=None):
440 if self.form is None or self.larch_eval is None:
441 return
442 self.form['show_fitrange'] = self.wids['show_fitrange'].GetValue()
443 self.form['win'] = int(self.wids['plot_win'].GetStringSelection())
444 form = self.form
445 dgroup = self.datagroup
447 form['plotopt'] = 'show_norm=True'
448 if form['arrayname'] == 'dmude':
449 form['plotopt'] = 'show_deriv=True'
450 if form['arrayname'] == 'flat':
451 form['plotopt'] = 'show_flat=True'
453 erange = form['ehi'] - form['elo']
454 form['pemin'] = 10*int( (form['elo'] - 5 - erange/4.0) / 10.0)
455 form['pemax'] = 10*int( (form['ehi'] + 5 + erange/4.0) / 10.0)
457 cmds = ["""plot_mu({group:s}, {plotopt:s}, delay_draw=True, label='data',
458 emin={pemin:.1f}, emax={pemax:.1f}, title='{filename:s}', win={win:d})"""]
460 nfits = int(self.wids['plot_nchoice'].GetStringSelection())
461 for i in range(nfits):
462 delay = 'delay_draw=True' if i<nfits-1 else 'delay_draw=False'
463 xarr = "{group:s}.lcf_result[%i].xdata" % i
464 yfit = "{group:s}.lcf_result[%i].yfit" % i
465 lab = 'Fit #%2.2d' % (i+1)
466 cmds.append("plot(%s, %s, label='%s', zorder=30, %s)" % (xarr, yfit, lab, delay))
468 if form['show_fitrange']:
469 cmds.append("plot_axvline({elo:1f}, color='#EECCCC', zorder=-10)")
470 cmds.append("plot_axvline({ehi:1f}, color='#EECCCC', zorder=-10)")
472 script = "\n".join(cmds)
473 self.larch_eval(script.format(**form))
474 self.parent.controller.set_focus(topwin=self)
476 def onExportGroupFit(self, evt=None):
477 "Export current fit to a new group in the main panel"
479 nfit = self.current_fit
480 dgroup = self.datagroup
481 xarr = dgroup.lcf_result[nfit].xdata
482 yfit = dgroup.lcf_result[nfit].yfit
483 i0 = np.ones_like(xarr)
485 controller = self.parent.controller
486 label = f"lcf_fit_{nfit}"
487 groupname = new_group = f"{dgroup.groupname}_{label}"
488 filename = f"{dgroup.filename}_{label}"
489 cmdstr = f"""{new_group} = group(name="{groupname}", groupname="{groupname}", filename="{filename}")"""
490 controller.larch.eval(cmdstr)
491 g = controller.symtable.get_group(new_group)
492 g.energy = g.xdat = xarr
493 g.mu = g.ydat = g.norm = yfit
494 g.i0 = i0
495 g.datatype = 'xas'
496 controller.install_group(groupname, filename, source="exported from Linear Combo / Fit Results")
498 def onSaveGroupFit(self, evt=None):
499 "Save Fit and Compoents for current fit to Data File"
500 nfit = self.current_fit
501 dgroup = self.datagroup
502 nfits = int(self.wids['plot_nchoice'].GetStringSelection())
504 deffile = "%s_LinearFit%i.dat" % (dgroup.filename, nfit+1)
505 wcards = 'Data Files (*.dat)|*.dat|All files (*.*)|*.*'
506 path = FileSave(self, 'Save Fit and Components to File',
507 default_file=deffile, wildcard=wcards)
508 if path is None:
509 return
511 form = self.form
512 label = [' energy ',
513 ' data ',
514 ' best_fit ']
515 result = dgroup.lcf_result[nfit]
517 header = ['Larch Linear Fit Result for Fit: #%2.2d' % (nfit+1),
518 'Dataset filename: %s ' % dgroup.filename,
519 'Larch group: %s ' % dgroup.groupname,
520 'Array name: %s' % form['arrayname'],
521 'Energy fit range: [%f, %f]' % (form['elo'], form['ehi']),
522 'Components: ']
523 for key, val in result.weights.items():
524 header.append(' %s: %f' % (key, val))
526 report = fit_report(result.result).split('\n')
527 header.extend(report)
529 out = [result.xdata, result.ydata, result.yfit]
530 for compname, compdata in result.ycomps.items():
531 label.append(' %s' % (compname + ' '*(max(1, 15-len(compname)))))
532 out.append(compdata)
534 label = ' '.join(label)
535 _larch = self.parent.controller.larch
536 write_ascii(path, header=header, label=label, _larch=_larch, *out)
539 def onSaveGroupStats(self, evt=None):
540 "Save Statistics and Weights for Best N Fits for the current group"
541 dgroup = self.datagroup
542 nfits = int(self.wids['plot_nchoice'].GetStringSelection())
543 results = dgroup.lcf_result[:nfits]
544 nresults = len(results)
545 deffile = "%s_LinearStats%i.dat" % (dgroup.filename, nresults)
546 wcards = 'Data Files (*.dat)|*.dat|All files (*.*)|*.*'
548 path = FileSave(self, 'Save Statistics and Weights for Best N Fits',
549 default_file=deffile, wildcard=wcards)
550 if path is None:
551 return
552 form = self.form
554 header = ['Larch Linear Fit Statistics for %2.2d best results' % (nresults),
555 'Dataset filename: %s ' % dgroup.filename,
556 'Larch group: %s ' % dgroup.groupname,
557 'Array name: %s' % form['arrayname'],
558 'Energy fit range: [%f, %f]' % (form['elo'], form['ehi']),
559 'N_Data: %d' % len(results[0].xdata)]
561 label = ['fit #', 'n_varys', 'n_eval', 'chi2',
562 'chi2_reduced', 'akaike_info', 'bayesian_info']
563 label.extend(form['comp_names'])
564 label.append('Total')
565 for i in range(len(label)):
566 if len(label[i]) < 13:
567 label[i] = (" %s " % label[i])[:13]
568 label = ' '.join(label)
570 out = []
571 for i, res in enumerate(results):
572 dat = [(i+1)]
573 for attr in ('nvarys', 'nfev', 'chisqr', 'redchi', 'aic', 'bic'):
574 dat.append(getattr(res.result, attr))
575 for cname in form['comp_names'] + ['total']:
576 val = 0.0
577 if cname in res.params:
578 val = res.params[cname].value
579 dat.append(val)
580 out.append(dat)
582 out = np.array(out).transpose()
583 _larch = self.parent.controller.larch
584 write_ascii(path, header=header, label=label, _larch=_larch, *out)
586 def onSaveGroupMultiFits(self, evt=None):
587 "Save Data and Best N Fits for the current group"
588 dgroup = self.datagroup
589 nfits = int(self.wids['plot_nchoice'].GetStringSelection())
590 results = dgroup.lcf_result[:nfits]
591 nresults = len(results)
593 deffile = "%s_LinearFits%i.dat" % (dgroup.filename, nresults)
594 wcards = 'Data Files (*.dat)|*.dat|All files (*.*)|*.*'
596 path = FileSave(self, 'Save Best N Fits',
597 default_file=deffile, wildcard=wcards)
598 if path is None:
599 return
600 form = self.form
601 header = ['Larch Linear Arrays for %2.2d best results' % (nresults),
602 'Dataset filename: %s ' % dgroup.filename,
603 'Larch group: %s ' % dgroup.groupname,
604 'Array name: %s' % form['arrayname'],
605 'Energy fit range: [%f, %f]' % (form['elo'], form['ehi'])]
607 label = [' energy ', ' data ']
608 label.extend([' fit_%2.2d ' % i for i in range(nresults)])
609 label = ' '.join(label)
611 out = [results[0].xdata, results[0].ydata]
612 for i, res in enumerate(results):
613 out.append(results[i].yfit)
615 _larch = self.parent.controller.larch
616 write_ascii(path, header=header, label=label, _larch=_larch, *out)
618 def onSaveAllStats(self, evt=None):
619 "Save All Statistics and Weights "
620 deffile = "LinearFitStats.csv"
621 wcards = 'CVS Files (*.csv)|*.csv|All files (*.*)|*.*'
622 path = FileSave(self, 'Save Statistics Report',
623 default_file=deffile, wildcard=wcards)
624 if path is None:
625 return
626 form = self.form
628 out = ['# Larch Linear Fit Statistics Report (best results) %s' % time.ctime(),
629 '# Array name: %s' % form['arrayname'],
630 '# Energy fit range: [%f, %f]' % (form['elo'], form['ehi'])]
632 label = [('Data Set' + ' '*25)[:25],
633 'n_varys', 'chi-square',
634 'chi-square_red', 'akaike_info', 'bayesian_info']
635 label.extend(form['comp_names'])
636 label.append('Total')
637 for i in range(len(label)):
638 if len(label[i]) < 12:
639 label[i] = (" %s " % label[i])[:12]
640 label = ', '.join(label)
641 out.append('# %s' % label)
643 for name, dgroup in self.datasets.items():
644 res = dgroup.lcf_result[0]
645 label = dgroup.filename
646 if len(label) < 25:
647 label = (label + ' '*25)[:25]
648 dat = [label]
649 for attr in ('nvarys', 'chisqr', 'redchi', 'aic', 'bic'):
650 dat.append(gformat(getattr(res.result, attr), 10))
651 for cname in form['comp_names'] + ['total']:
652 val = 0
653 if cname in res.params:
654 val = res.params[cname].value
655 dat.append(gformat(val, 10))
656 out.append(', '.join(dat))
657 out.append('')
659 with open(path, 'w', encoding=sys.getdefaultencoding()) as fh:
660 fh.write('\n'.join(out))
662class LinearComboPanel(TaskPanel):
663 """Liear Combination Panel"""
664 def __init__(self, parent, controller, **kws):
665 TaskPanel.__init__(self, parent, controller, panel='lincombo', **kws)
667 def process(self, dgroup, **kws):
668 """ handle linear combo processing"""
669 if self.skip_process:
670 return
671 form = self.read_form()
672 conf = self.get_config(dgroup)
673 for key in ('elo', 'ehi', 'max_ncomps', 'fitspace', 'all_combos',
674 'vary_e0', 'sum_to_one', 'show_fitrange'):
675 conf[key] = form[key]
676 self.update_config(conf, dgroup=dgroup)
678 def build_display(self):
679 panel = self.panel
680 wids = self.wids
681 self.skip_process = True
683 wids['fitspace'] = Choice(panel, choices=list(Linear_ArrayChoices.keys()),
684 action=self.onFitSpace, size=(175, -1))
685 wids['fitspace'].SetSelection(0)
687 add_text = self.add_text
689 opts = dict(digits=2, increment=1.0, relative_e0=False)
690 defaults = self.get_defaultconfig()
692 self.make_fit_xspace_widgets(elo=defaults['elo_rel'], ehi=defaults['ehi_rel'])
694 wids['fit_group'] = Button(panel, 'Fit this Group', size=(150, -1),
695 action=self.onFitOne)
696 wids['fit_selected'] = Button(panel, 'Fit Selected Groups', size=(175, -1),
697 action=self.onFitAll)
699 wids['fit_group'].Disable()
700 wids['fit_selected'].Disable()
702 wids['show_results'] = Button(panel, 'Show Fit Results',
703 action=self.onShowResults, size=(150, -1))
704 wids['show_results'].Disable()
706 wids['add_selected'] = Button(panel, 'Use Selected Groups as Components',
707 size=(300, -1), action=self.onUseSelected)
709 opts = dict(default=True, size=(75, -1), action=self.onPlotOne)
711 wids['show_fitrange'] = Check(panel, label='show?', **opts)
713 wids['vary_e0'] = Check(panel, label='Allow energy shift in fit?', default=False)
714 wids['sum_to_one'] = Check(panel, label='Weights Must Sum to 1?', default=False)
715 wids['all_combos'] = Check(panel, label='Fit All Combinations?', default=True)
716 max_ncomps = self.add_floatspin('max_ncomps', value=5, digits=0, increment=1,
717 min_val=0, max_val=MAX_COMPONENTS, size=(60, -1),
718 with_pin=False)
720 panel.Add(SimpleText(panel, 'Linear Combination Analysis',
721 size=(350, -1), **self.titleopts), style=LEFT, dcol=4)
723 add_text('Array to Fit: ', newrow=True)
724 panel.Add(wids['fitspace'], dcol=3)
725 panel.Add(wids['show_results'])
727 panel.Add(wids['fitspace_label'], newrow=True)
728 panel.Add(self.elo_wids)
729 add_text(' : ', newrow=False)
730 panel.Add(self.ehi_wids)
731 panel.Add(wids['show_fitrange'])
733 panel.Add(HLine(panel, size=(625, 3)), dcol=5, newrow=True)
735 add_text('Build Model : ')
736 panel.Add(wids['add_selected'], dcol=4)
738 collabels = [' File /Group Name ', 'weight', 'min', 'max']
739 colsizes = [325, 100, 100, 100]
740 coltypes = ['str', 'float:12,4', 'float:12,4', 'float:12,4']
741 coldefs = ['', 1.0/MAX_COMPONENTS, 0.0, 1.0]
743 self.font_fixedwidth = wx.Font(FONTSIZE_FW, wx.MODERN, wx.NORMAL, wx.BOLD)
744 wids['table'] = DataTableGrid(panel, nrows=MAX_COMPONENTS,
745 collabels=collabels,
746 datatypes=coltypes, defaults=coldefs,
747 colsizes=colsizes)
749 wids['table'].SetMinSize((700, 250))
750 wids['table'].SetFont(self.font_fixedwidth)
751 panel.Add(wids['table'], newrow=True, dcol=6)
753 panel.Add(HLine(panel, size=(625, 3)), dcol=5, newrow=True)
754 add_text('Fit with this Model: ')
755 panel.Add(wids['fit_group'], dcol=2)
756 panel.Add(wids['fit_selected'], dcol=3)
757 add_text('Fit Options: ')
758 panel.Add(wids['vary_e0'], dcol=2)
759 panel.Add(wids['sum_to_one'], dcol=2)
760 panel.Add((10, 10), dcol=1, newrow=True)
761 panel.Add(wids['all_combos'], dcol=2)
762 add_text('Max # Components: ', newrow=False)
763 panel.Add(max_ncomps, dcol=2)
765 panel.Add(HLine(panel, size=(625, 3)), dcol=5, newrow=True)
766 # panel.Add(wids['saveconf'], dcol=4, newrow=True)
767 panel.pack()
769 sizer = wx.BoxSizer(wx.VERTICAL)
770 sizer.Add((10, 10), 0, LEFT, 3)
771 sizer.Add(panel, 1, LEFT, 3)
772 pack(self, sizer)
773 self.skip_process = False
775 def onPanelExposed(self, **kws):
776 # called when notebook is selected
777 try:
778 fname = self.controller.filelist.GetStringSelection()
779 gname = self.controller.file_groups[fname]
780 dgroup = self.controller.get_group(gname)
781 self.ensure_xas_processed(dgroup)
782 self.fill_form(dgroup)
783 except:
784 pass # print(" Cannot Fill prepeak panel from group ")
786 lcf_result = getattr(self.larch.symtable, 'lcf_result', None)
787 if lcf_result is None:
788 return
789 self.wids['show_results'].Enable()
790 self.skip_process = True
791 selected_groups = []
792 for r in lcf_result[:100]:
793 for gname in r.weights:
794 if gname not in selected_groups:
795 selected_groups.append(gname)
797 if len(selected_groups) > 0:
798 if len(selected_groups) >= MAX_COMPONENTS:
799 selected_groups = selected_groups[:MAX_COMPONENTS]
800 weight = 1.0/len(selected_groups)
801 grid_data = []
802 for grp in selected_groups:
803 grid_data.append([grp, weight, 0, 1])
805 self.wids['fit_group'].Enable()
806 self.wids['fit_selected'].Enable()
807 self.wids['table'].table.data = grid_data
808 self.wids['table'].table.View.Refresh()
809 self.skip_process = False
812 def onFitSpace(self, evt=None):
813 fitspace = self.wids['fitspace'].GetStringSelection()
814 self.update_config(dict(fitspace=fitspace))
816 arrname = Linear_ArrayChoices.get(fitspace, 'norm')
817 self.update_fit_xspace(arrname)
818 self.plot()
820 def onComponent(self, evt=None, comp=None):
821 if comp is None or evt is None:
822 return
824 comps = []
825 for wname, wid in self.wids.items():
826 if wname.startswith('compchoice'):
827 pref, n = wname.split('_')
828 if wid.GetSelection() > 0:
829 caomps.append((int(n), wid.GetStringSelection()))
830 else:
831 self.wids["compval_%s" % n].SetValue(0)
833 cnames = set([elem[1] for elem in comps])
834 if len(cnames) < len(comps):
835 comps.remove((comp, evt.GetString()))
836 self.wids["compchoice_%2.2d" % comp].SetSelection(0)
838 weight = 1.0 / len(comps)
840 for n, cname in comps:
841 self.wids["compval_%2.2d" % n].SetValue(weight)
844 def fill_form(self, dgroup):
845 """fill in form from a data group"""
846 opts = self.get_config(dgroup, with_erange=True)
847 self.dgroup = dgroup
848 self.ensure_xas_processed(dgroup)
849 defaults = self.get_defaultconfig()
851 self.skip_process = True
852 wids = self.wids
854 for attr in ('all_combos', 'sum_to_one', 'show_fitrange'):
855 wids[attr].SetValue(opts.get(attr, True))
857 for attr in ('elo', 'ehi', ):
858 val = opts.get(attr, None)
859 if val is not None:
860 wids[attr].SetValue(val)
862 for attr in ('fitspace', ):
863 if attr in opts:
864 wids[attr].SetStringSelection(opts[attr])
866 fitspace = self.wids['fitspace'].GetStringSelection()
867 self.update_config(dict(fitspace=fitspace))
868 arrname = Linear_ArrayChoices.get(fitspace, 'norm')
869 self.update_fit_xspace(arrname)
871 self.skip_process = False
873 def read_form(self, dgroup=None):
874 "read form, return dict of values"
875 self.skiap_process = True
876 if dgroup is None:
877 dgroup = self.controller.get_group()
878 self.dgroup = dgroup
879 if dgroup is None:
880 opts = {'group': '', 'filename': ''}
881 else:
882 opts = {'group': dgroup.groupname, 'filename':dgroup.filename}
884 wids = self.wids
885 for attr in ('elo', 'ehi', 'max_ncomps'):
886 opts[attr] = wids[attr].GetValue()
888 opts['fitspace'] = wids['fitspace'].GetStringSelection()
890 for attr in ('all_combos', 'vary_e0', 'sum_to_one', 'show_fitrange'):
891 opts[attr] = wids[attr].GetValue()
893 for attr, wid in wids.items():
894 if attr.startswith('compchoice'):
895 opts[attr] = wid.GetStringSelection()
896 elif attr.startswith('comp'):
897 opts[attr] = wid.GetValue()
899 comps, cnames, wval, wmin, wmax = [], [], [], [], []
901 table_data = self.wids['table'].table.data
902 for _cname, _wval, _wmin, _wmax in table_data:
903 if _cname.strip() in ('', None) or len(_cname) < 1:
904 break
905 cnames.append(_cname)
906 comps.append(self.controller.file_groups[_cname])
907 wval.append("%.5f" % _wval)
908 wmin.append("%.5f" % _wmin)
909 wmax.append("%.5f" % _wmax)
911 opts['comp_names'] = cnames
912 opts['comps'] = ', '.join(comps)
913 opts['weights'] = ', '.join(wval)
914 opts['minvals'] = ', '.join(wmin)
915 opts['maxvals'] = ', '.join(wmax)
916 opts['func'] = 'lincombo_fit'
917 if opts['all_combos']:
918 opts['func'] = 'lincombo_fitall'
920 opts['arrayname'] = Linear_ArrayChoices.get(opts['fitspace'], 'norm')
921 self.skip_process = False
922 return opts
924 def onSaveConfigBtn(self, evt=None):
925 conf = self.get_config()
926 conf.update(self.read_form())
927 self.set_defaultconfig(conf)
929 def onUseSelected(self, event=None):
930 """ use selected groups as standards"""
931 self.skip_process = True
932 selected_groups = self.controller.filelist.GetCheckedStrings()
933 if len(selected_groups) == 0:
934 return
935 if len(selected_groups) >= MAX_COMPONENTS:
936 selected_groups = selected_groups[:MAX_COMPONENTS]
937 weight = 1.0/len(selected_groups)
939 grid_data = []
940 for grp in selected_groups:
941 grid_data.append([grp, weight, 0, 1])
943 self.wids['fit_group'].Enable()
944 self.wids['fit_selected'].Enable()
945 self.wids['table'].table.data = grid_data
946 self.wids['table'].table.View.Refresh()
947 self.skip_process = False
949 def do_fit(self, groupname, form, plot=True):
950 """run lincombo fit for a group"""
951 form['gname'] = groupname
952 dgroup = self.controller.get_group(groupname)
953 self.ensure_xas_processed(dgroup)
955 if len(groupname) == 0:
956 print("no group to fit?")
957 return
959 script = """# do LCF for {gname:s}
960lcf_result = {func:s}({gname:s}, [{comps:s}],
961 xmin={elo:.4f}, xmax={ehi:.4f},
962 arrayname='{arrayname:s}',
963 sum_to_one={sum_to_one}, vary_e0={vary_e0},
964 weights=[{weights:s}],
965 minvals=[{minvals:s}],
966 maxvals=[{maxvals:s}],
967 max_ncomps={max_ncomps:.0f})
968"""
969 if form['all_combos']:
970 script = "%s\n{gname:s}.lcf_result = lcf_result\n" % script
971 else:
972 script = "%s\n{gname:s}.lcf_result = [lcf_result]\n" % script
974 self.larch_eval(script.format(**form))
976 dgroup = self.controller.get_group(groupname)
977 self.show_subframe('lcf_result', LinComboResultFrame,
978 datagroup=dgroup, mainpanel=self)
980 self.subframes['lcf_result'].add_results(dgroup, form=form,
981 larch_eval=self.larch_eval, show=plot)
982 if plot:
983 self.plot(dgroup=dgroup, with_fit=True)
985 def onShowResults(self, event=None):
986 self.show_subframe('lcf_result', LinComboResultFrame, mainpanel=self)
988 def onFitOne(self, event=None):
989 """ handle process events"""
990 if self.skip_process:
991 return
993 self.skip_process = True
994 form = self.read_form()
995 self.update_config(form)
996 self.do_fit(form['group'], form)
997 self.skip_process = False
999 def onFitAll(self, event=None):
1000 """ handle process events"""
1001 if self.skip_process:
1002 return
1003 self.skip_process = True
1004 form = self.read_form()
1005 groups = self.controller.filelist.GetCheckedStrings()
1006 for i, sel in enumerate(groups):
1007 gname = self.controller.file_groups[sel]
1008 self.do_fit(gname, form, plot=(i==len(groups)-1))
1009 self.skip_process = False
1011 def plot(self, dgroup=None, with_fit=False):
1012 if self.skip_plotting:
1013 return
1015 if dgroup is None:
1016 dgroup = self.controller.get_group()
1018 form = self.read_form(dgroup=dgroup)
1019 script = make_lcfplot(dgroup, form, with_fit=with_fit, nfit=0)
1020 self.larch_eval(script)
1021 self.controller.set_focus()