Coverage for larch/wxxas/lincombo_panel.py: 0%
718 statements
« prev ^ index » next coverage.py v7.6.0, created at 2024-10-16 21:04 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2024-10-16 21:04 +0000
1#!/usr/bin/env python
2"""
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.NORMAL)
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.xplot = xarr
493 g.mu = g.yplot = 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 write_ascii(path, header=header, label=label, *out)
538 def onSaveGroupStats(self, evt=None):
539 "Save Statistics and Weights for Best N Fits for the current group"
540 dgroup = self.datagroup
541 nfits = int(self.wids['plot_nchoice'].GetStringSelection())
542 results = dgroup.lcf_result[:nfits]
543 nresults = len(results)
544 deffile = "%s_LinearStats%i.dat" % (dgroup.filename, nresults)
545 wcards = 'Data Files (*.dat)|*.dat|All files (*.*)|*.*'
547 path = FileSave(self, 'Save Statistics and Weights for Best N Fits',
548 default_file=deffile, wildcard=wcards)
549 if path is None:
550 return
551 form = self.form
553 header = ['Larch Linear Fit Statistics for %2.2d best results' % (nresults),
554 'Dataset filename: %s ' % dgroup.filename,
555 'Larch group: %s ' % dgroup.groupname,
556 'Array name: %s' % form['arrayname'],
557 'Energy fit range: [%f, %f]' % (form['elo'], form['ehi']),
558 'N_Data: %d' % len(results[0].xdata)]
560 label = ['fit #', 'n_varys', 'n_eval', 'chi2',
561 'chi2_reduced', 'akaike_info', 'bayesian_info']
562 label.extend(form['comp_names'])
563 label.append('Total')
564 for i in range(len(label)):
565 if len(label[i]) < 13:
566 label[i] = (" %s " % label[i])[:13]
567 label = ' '.join(label)
569 out = []
570 for i, res in enumerate(results):
571 dat = [(i+1)]
572 for attr in ('nvarys', 'nfev', 'chisqr', 'redchi', 'aic', 'bic'):
573 dat.append(getattr(res.result, attr))
574 for cname in form['comp_names'] + ['total']:
575 val = 0.0
576 if cname in res.params:
577 val = res.params[cname].value
578 dat.append(val)
579 out.append(dat)
581 out = np.array(out).transpose()
582 write_ascii(path, header=header, label=label, *out)
584 def onSaveGroupMultiFits(self, evt=None):
585 "Save Data and Best N Fits for the current group"
586 dgroup = self.datagroup
587 nfits = int(self.wids['plot_nchoice'].GetStringSelection())
588 results = dgroup.lcf_result[:nfits]
589 nresults = len(results)
591 deffile = "%s_LinearFits%i.dat" % (dgroup.filename, nresults)
592 wcards = 'Data Files (*.dat)|*.dat|All files (*.*)|*.*'
594 path = FileSave(self, 'Save Best N Fits',
595 default_file=deffile, wildcard=wcards)
596 if path is None:
597 return
598 form = self.form
599 header = ['Larch Linear Arrays for %2.2d best results' % (nresults),
600 'Dataset filename: %s ' % dgroup.filename,
601 'Larch group: %s ' % dgroup.groupname,
602 'Array name: %s' % form['arrayname'],
603 'Energy fit range: [%f, %f]' % (form['elo'], form['ehi'])]
605 label = [' energy ', ' data ']
606 label.extend([' fit_%2.2d ' % i for i in range(nresults)])
607 label = ' '.join(label)
609 out = [results[0].xdata, results[0].ydata]
610 for i, res in enumerate(results):
611 out.append(results[i].yfit)
613 write_ascii(path, header=header, label=label, *out)
615 def onSaveAllStats(self, evt=None):
616 "Save All Statistics and Weights "
617 deffile = "LinearFitStats.csv"
618 wcards = 'CVS Files (*.csv)|*.csv|All files (*.*)|*.*'
619 path = FileSave(self, 'Save Statistics Report',
620 default_file=deffile, wildcard=wcards)
621 if path is None:
622 return
623 form = self.form
625 out = ['# Larch Linear Fit Statistics Report (best results) %s' % time.ctime(),
626 '# Array name: %s' % form['arrayname'],
627 '# Energy fit range: [%f, %f]' % (form['elo'], form['ehi'])]
629 label = [('Data Set' + ' '*25)[:25],
630 'n_varys', 'chi-square',
631 'chi-square_red', 'akaike_info', 'bayesian_info']
632 label.extend(form['comp_names'])
633 label.append('Total')
634 for i in range(len(label)):
635 if len(label[i]) < 12:
636 label[i] = (" %s " % label[i])[:12]
637 label = ', '.join(label)
638 out.append('# %s' % label)
640 for name, dgroup in self.datasets.items():
641 res = dgroup.lcf_result[0]
642 label = dgroup.filename
643 if len(label) < 25:
644 label = (label + ' '*25)[:25]
645 dat = [label]
646 for attr in ('nvarys', 'chisqr', 'redchi', 'aic', 'bic'):
647 dat.append(gformat(getattr(res.result, attr), 10))
648 for cname in form['comp_names'] + ['total']:
649 val = 0
650 if cname in res.params:
651 val = res.params[cname].value
652 dat.append(gformat(val, 10))
653 out.append(', '.join(dat))
654 out.append('')
656 with open(path, 'w', encoding=sys.getdefaultencoding()) as fh:
657 fh.write('\n'.join(out))
659class LinearComboPanel(TaskPanel):
660 """Liear Combination Panel"""
661 def __init__(self, parent, controller, **kws):
662 TaskPanel.__init__(self, parent, controller, panel='lincombo', **kws)
664 def process(self, dgroup, **kws):
665 """ handle linear combo processing"""
666 if self.skip_process:
667 return
668 form = self.read_form()
669 conf = self.get_config(dgroup)
670 for key in ('elo', 'ehi', 'max_ncomps', 'fitspace', 'all_combos',
671 'vary_e0', 'sum_to_one', 'show_fitrange'):
672 conf[key] = form[key]
673 self.update_config(conf, dgroup=dgroup)
675 def build_display(self):
676 panel = self.panel
677 wids = self.wids
678 self.skip_process = True
680 wids['fitspace'] = Choice(panel, choices=list(Linear_ArrayChoices.keys()),
681 action=self.onFitSpace, size=(175, -1))
682 wids['fitspace'].SetSelection(0)
684 add_text = self.add_text
686 opts = dict(digits=2, increment=1.0, relative_e0=False)
687 defaults = self.get_defaultconfig()
689 self.make_fit_xspace_widgets(elo=defaults['elo_rel'], ehi=defaults['ehi_rel'])
691 wids['fit_group'] = Button(panel, 'Fit this Group', size=(150, -1),
692 action=self.onFitOne)
693 wids['fit_selected'] = Button(panel, 'Fit Selected Groups', size=(175, -1),
694 action=self.onFitAll)
696 wids['fit_group'].Disable()
697 wids['fit_selected'].Disable()
699 wids['show_results'] = Button(panel, 'Show Fit Results',
700 action=self.onShowResults, size=(150, -1))
701 wids['show_results'].Disable()
703 wids['add_selected'] = Button(panel, 'Use Selected Groups as Components',
704 size=(300, -1), action=self.onUseSelected)
706 opts = dict(default=True, size=(75, -1), action=self.onPlotOne)
708 wids['show_fitrange'] = Check(panel, label='show?', **opts)
710 wids['vary_e0'] = Check(panel, label='Allow energy shift in fit?', default=False)
711 wids['sum_to_one'] = Check(panel, label='Weights Must Sum to 1?', default=False)
712 wids['all_combos'] = Check(panel, label='Fit All Combinations?', default=True)
713 max_ncomps = self.add_floatspin('max_ncomps', value=5, digits=0, increment=1,
714 min_val=0, max_val=MAX_COMPONENTS, size=(60, -1),
715 with_pin=False)
717 panel.Add(SimpleText(panel, 'Linear Combination Analysis',
718 size=(350, -1), **self.titleopts), style=LEFT, dcol=4)
720 add_text('Array to Fit: ', newrow=True)
721 panel.Add(wids['fitspace'], dcol=3)
722 panel.Add(wids['show_results'])
724 panel.Add(wids['fitspace_label'], newrow=True)
725 panel.Add(self.elo_wids)
726 add_text(' : ', newrow=False)
727 panel.Add(self.ehi_wids)
728 panel.Add(wids['show_fitrange'])
730 panel.Add(HLine(panel, size=(625, 3)), dcol=5, newrow=True)
732 add_text('Build Model : ')
733 panel.Add(wids['add_selected'], dcol=4)
735 collabels = [' File /Group Name ', 'weight', 'min', 'max']
736 colsizes = [325, 100, 100, 100]
737 coltypes = ['str', 'float:12,4', 'float:12,4', 'float:12,4']
738 coldefs = ['', 1.0/MAX_COMPONENTS, 0.0, 1.0]
740 self.font_fixedwidth = wx.Font(FONTSIZE_FW, wx.MODERN, wx.NORMAL, wx.NORMAL)
741 wids['table'] = DataTableGrid(panel, nrows=MAX_COMPONENTS,
742 collabels=collabels,
743 datatypes=coltypes, defaults=coldefs,
744 colsizes=colsizes)
746 wids['table'].SetMinSize((700, 250))
747 wids['table'].SetFont(self.font_fixedwidth)
748 panel.Add(wids['table'], newrow=True, dcol=6)
750 panel.Add(HLine(panel, size=(625, 3)), dcol=5, newrow=True)
751 add_text('Fit with this Model: ')
752 panel.Add(wids['fit_group'], dcol=2)
753 panel.Add(wids['fit_selected'], dcol=3)
754 add_text('Fit Options: ')
755 panel.Add(wids['vary_e0'], dcol=2)
756 panel.Add(wids['sum_to_one'], dcol=2)
757 panel.Add((10, 10), dcol=1, newrow=True)
758 panel.Add(wids['all_combos'], dcol=2)
759 add_text('Max # Components: ', newrow=False)
760 panel.Add(max_ncomps, dcol=2)
762 panel.Add(HLine(panel, size=(625, 3)), dcol=5, newrow=True)
763 # panel.Add(wids['saveconf'], dcol=4, newrow=True)
764 panel.pack()
766 sizer = wx.BoxSizer(wx.VERTICAL)
767 sizer.Add((10, 10), 0, LEFT, 3)
768 sizer.Add(panel, 1, LEFT, 3)
769 pack(self, sizer)
770 self.skip_process = False
772 def onPanelExposed(self, **kws):
773 # called when notebook is selected
774 try:
775 fname = self.controller.filelist.GetStringSelection()
776 gname = self.controller.file_groups[fname]
777 dgroup = self.controller.get_group(gname)
778 self.ensure_xas_processed(dgroup)
779 self.fill_form(dgroup)
780 except:
781 pass # print(" Cannot Fill prepeak panel from group ")
783 lcf_result = getattr(self.larch.symtable, 'lcf_result', None)
784 if lcf_result is None:
785 return
786 self.wids['show_results'].Enable()
787 self.skip_process = True
788 selected_groups = []
789 for r in lcf_result[:100]:
790 for gname in r.weights:
791 if gname not in selected_groups:
792 selected_groups.append(gname)
794 if len(selected_groups) > 0:
795 if len(selected_groups) >= MAX_COMPONENTS:
796 selected_groups = selected_groups[:MAX_COMPONENTS]
797 weight = 1.0/len(selected_groups)
798 grid_data = []
799 for grp in selected_groups:
800 grid_data.append([grp, weight, 0, 1])
802 self.wids['fit_group'].Enable()
803 self.wids['fit_selected'].Enable()
804 self.wids['table'].table.data = grid_data
805 self.wids['table'].table.View.Refresh()
806 self.skip_process = False
809 def onFitSpace(self, evt=None):
810 fitspace = self.wids['fitspace'].GetStringSelection()
811 self.update_config(dict(fitspace=fitspace))
813 arrname = Linear_ArrayChoices.get(fitspace, 'norm')
814 self.update_fit_xspace(arrname)
815 self.plot()
817 def onComponent(self, evt=None, comp=None):
818 if comp is None or evt is None:
819 return
821 comps = []
822 for wname, wid in self.wids.items():
823 if wname.startswith('compchoice'):
824 pref, n = wname.split('_')
825 if wid.GetSelection() > 0:
826 caomps.append((int(n), wid.GetStringSelection()))
827 else:
828 self.wids["compval_%s" % n].SetValue(0)
830 cnames = set([elem[1] for elem in comps])
831 if len(cnames) < len(comps):
832 comps.remove((comp, evt.GetString()))
833 self.wids["compchoice_%2.2d" % comp].SetSelection(0)
835 weight = 1.0 / len(comps)
837 for n, cname in comps:
838 self.wids["compval_%2.2d" % n].SetValue(weight)
841 def fill_form(self, dgroup):
842 """fill in form from a data group"""
843 opts = self.get_config(dgroup, with_erange=True)
844 self.dgroup = dgroup
845 self.ensure_xas_processed(dgroup)
846 defaults = self.get_defaultconfig()
848 self.skip_process = True
849 wids = self.wids
851 for attr in ('all_combos', 'sum_to_one', 'show_fitrange'):
852 wids[attr].SetValue(opts.get(attr, True))
854 for attr in ('elo', 'ehi', ):
855 val = opts.get(attr, None)
856 if val is not None:
857 wids[attr].SetValue(val)
859 for attr in ('fitspace', ):
860 if attr in opts:
861 wids[attr].SetStringSelection(opts[attr])
863 fitspace = self.wids['fitspace'].GetStringSelection()
864 self.update_config(dict(fitspace=fitspace))
865 arrname = Linear_ArrayChoices.get(fitspace, 'norm')
866 self.update_fit_xspace(arrname)
868 self.skip_process = False
870 def read_form(self, dgroup=None):
871 "read form, return dict of values"
872 self.skiap_process = True
873 if dgroup is None:
874 dgroup = self.controller.get_group()
875 self.dgroup = dgroup
876 if dgroup is None:
877 opts = {'group': '', 'filename': ''}
878 else:
879 opts = {'group': dgroup.groupname, 'filename':dgroup.filename}
881 wids = self.wids
882 for attr in ('elo', 'ehi', 'max_ncomps'):
883 opts[attr] = wids[attr].GetValue()
885 opts['fitspace'] = wids['fitspace'].GetStringSelection()
887 for attr in ('all_combos', 'vary_e0', 'sum_to_one', 'show_fitrange'):
888 opts[attr] = wids[attr].GetValue()
890 for attr, wid in wids.items():
891 if attr.startswith('compchoice'):
892 opts[attr] = wid.GetStringSelection()
893 elif attr.startswith('comp'):
894 opts[attr] = wid.GetValue()
896 comps, cnames, wval, wmin, wmax = [], [], [], [], []
898 table_data = self.wids['table'].table.data
899 for _cname, _wval, _wmin, _wmax in table_data:
900 if _cname.strip() in ('', None) or len(_cname) < 1:
901 break
902 cnames.append(_cname)
903 comps.append(self.controller.file_groups[_cname])
904 wval.append("%.5f" % _wval)
905 wmin.append("%.5f" % _wmin)
906 wmax.append("%.5f" % _wmax)
908 opts['comp_names'] = cnames
909 opts['comps'] = ', '.join(comps)
910 opts['weights'] = ', '.join(wval)
911 opts['minvals'] = ', '.join(wmin)
912 opts['maxvals'] = ', '.join(wmax)
913 opts['func'] = 'lincombo_fit'
914 if opts['all_combos']:
915 opts['func'] = 'lincombo_fitall'
917 opts['arrayname'] = Linear_ArrayChoices.get(opts['fitspace'], 'norm')
918 self.skip_process = False
919 return opts
921 def onSaveConfigBtn(self, evt=None):
922 conf = self.get_config()
923 conf.update(self.read_form())
924 self.set_defaultconfig(conf)
926 def onUseSelected(self, event=None):
927 """ use selected groups as standards"""
928 self.skip_process = True
929 selected_groups = self.controller.filelist.GetCheckedStrings()
930 if len(selected_groups) == 0:
931 return
932 if len(selected_groups) >= MAX_COMPONENTS:
933 selected_groups = selected_groups[:MAX_COMPONENTS]
934 weight = 1.0/len(selected_groups)
936 grid_data = []
937 for grp in selected_groups:
938 grid_data.append([grp, weight, 0, 1])
940 self.wids['fit_group'].Enable()
941 self.wids['fit_selected'].Enable()
942 self.wids['table'].table.data = grid_data
943 self.wids['table'].table.View.Refresh()
944 self.skip_process = False
946 def do_fit(self, groupname, form, plot=True):
947 """run lincombo fit for a group"""
948 form['gname'] = groupname
949 dgroup = self.controller.get_group(groupname)
950 self.ensure_xas_processed(dgroup)
952 if len(groupname) == 0:
953 print("no group to fit?")
954 return
956 script = """# do LCF for {gname:s}
957lcf_result = {func:s}({gname:s}, [{comps:s}],
958 xmin={elo:.4f}, xmax={ehi:.4f},
959 arrayname='{arrayname:s}',
960 sum_to_one={sum_to_one}, vary_e0={vary_e0},
961 weights=[{weights:s}],
962 minvals=[{minvals:s}],
963 maxvals=[{maxvals:s}],
964 max_ncomps={max_ncomps:.0f})
965"""
966 if form['all_combos']:
967 script = "%s\n{gname:s}.lcf_result = lcf_result\n" % script
968 else:
969 script = "%s\n{gname:s}.lcf_result = [lcf_result]\n" % script
971 self.larch_eval(script.format(**form))
973 dgroup = self.controller.get_group(groupname)
974 self.show_subframe('lcf_result', LinComboResultFrame,
975 datagroup=dgroup, mainpanel=self)
977 self.subframes['lcf_result'].add_results(dgroup, form=form,
978 larch_eval=self.larch_eval, show=plot)
979 if plot:
980 self.plot(dgroup=dgroup, with_fit=True)
982 def onShowResults(self, event=None):
983 self.show_subframe('lcf_result', LinComboResultFrame, mainpanel=self)
985 def onFitOne(self, event=None):
986 """ handle process events"""
987 if self.skip_process:
988 return
990 self.skip_process = True
991 form = self.read_form()
992 self.update_config(form)
993 self.do_fit(form['group'], form)
994 self.skip_process = False
996 def onFitAll(self, event=None):
997 """ handle process events"""
998 if self.skip_process:
999 return
1000 self.skip_process = True
1001 form = self.read_form()
1002 groups = self.controller.filelist.GetCheckedStrings()
1003 for i, sel in enumerate(groups):
1004 gname = self.controller.file_groups[sel]
1005 self.do_fit(gname, form, plot=(i==len(groups)-1))
1006 self.skip_process = False
1008 def plot(self, dgroup=None, with_fit=False):
1009 if self.skip_plotting:
1010 return
1012 if dgroup is None:
1013 dgroup = self.controller.get_group()
1015 form = self.read_form(dgroup=dgroup)
1016 script = make_lcfplot(dgroup, form, with_fit=with_fit, nfit=0)
1017 self.larch_eval(script)
1018 self.controller.set_focus()