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

1#!/usr/bin/env python 

2""" 

3Linear Combination panel 

4""" 

5import os 

6import sys 

7import time 

8 

9import wx 

10import wx.lib.scrolledpanel as scrolled 

11import wx.dataview as dv 

12import numpy as np 

13 

14from functools import partial 

15 

16import lmfit 

17from lmfit.printfuncs import fit_report 

18 

19from larch import Group 

20from larch.math import index_of 

21from larch.xafs import etok, ktoe 

22 

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) 

29 

30from .taskpanel import TaskPanel 

31from .config import ARRAYS, Linear_ArrayChoices, PlotWindowChoices 

32from larch.io import write_ascii 

33from larch.utils import gformat 

34 

35np.seterr(all='ignore') 

36 

37# plot options: 

38Plot_Choices = ['Data + Sum', 'Data + Sum + Components'] 

39 

40DVSTYLE = dv.DV_SINGLE|dv.DV_VERT_RULES|dv.DV_ROW_LINES 

41 

42MAX_COMPONENTS = 12 

43 

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) 

50 

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 

58 

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})"""] 

61 

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' 

70 

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) 

74 

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})"""] 

77 

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)) 

90 

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})") 

96 

97 script = "\n".join(cmds) 

98 return script.format(**form) 

99 

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() 

113 

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) 

123 

124 def createMenus(self): 

125 self.menubar = wx.MenuBar() 

126 fmenu = wx.Menu() 

127 m = {} 

128 

129 MenuItem(self, fmenu, "Export current fit as group", 

130 "Export current fit to a new group in the main panel", self.onExportGroupFit) 

131 

132 MenuItem(self, fmenu, "Save Fit And Components for Current Group", 

133 "Save Fit and Components to Data File for Current Group", self.onSaveGroupFit) 

134 

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) 

137 

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) 

140 

141 fmenu.AppendSeparator() 

142 MenuItem(self, fmenu, "Save Statistics Report for All Fitted Groups", 

143 "Save Statistics for All Fitted Groups", self.onSaveAllStats) 

144 

145 self.menubar.Append(fmenu, "&File") 

146 self.SetMenuBar(self.menubar) 

147 

148 def build(self): 

149 sizer = wx.GridBagSizer(3, 3) 

150 sizer.SetVGap(3) 

151 sizer.SetHGap(3) 

152 

153 splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE) 

154 splitter.SetMinimumPaneSize(200) 

155 

156 dl = self.filelist = EditableListBox(splitter, self.ShowDataSet, 

157 size=(250, -1)) 

158 set_color(self.filelist, 'list_fg', bg='list_bg') 

159 

160 

161 panel = scrolled.ScrolledPanel(splitter) 

162 

163 self.SetMinSize((650, 600)) 

164 

165 self.font_fixedwidth = wx.Font(FONTSIZE_FW, wx.MODERN, wx.NORMAL, wx.NORMAL) 

166 

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) 

172 

173 wids['plot_win'] = Choice(panel, choices=PlotWindowChoices, 

174 action=self.onPlotOne, size=(60, -1)) 

175 wids['plot_win'].SetStringSelection('1') 

176 

177 wids['plot_wtitle'] = SimpleText(panel, 'Plot Window: ') 

178 wids['plot_ntitle'] = SimpleText(panel, 'N fits to plot: ') 

179 

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') 

183 

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: ') 

190 

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) 

194 

195 irow = 0 

196 sizer.Add(wids['data_title'], (irow, 0), (1, 3), LEFT) 

197 

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) 

201 

202 

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) 

208 

209 

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 

223 

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) 

227 

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) 

230 

231 

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) 

238 

239 

240 irow += 7 

241 sizer.Add(HLine(panel, size=(675, 3)), (irow, 0), (1, 4), LEFT) 

242 

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) 

253 

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 

261 

262 sview.SetMinSize((700, 175)) 

263 

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) 

268 

269 irow += 1 

270 sizer.Add(sview, (irow, 0), (1, 4), LEFT) 

271 

272 irow += 1 

273 sizer.Add(HLine(panel, size=(675, 3)), (irow, 0), (1, 4), LEFT) 

274 

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) 

280 

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)) 

286 

287 irow += 1 

288 sizer.Add(ppan, (irow, 0), (1, 4), LEFT) 

289 

290 irow += 1 

291 sizer.Add(HLine(panel, size=(675, 3)), (irow, 0), (1, 4), LEFT) 

292 

293 pack(panel, sizer) 

294 panel.SetupScrolling() 

295 

296 splitter.SplitVertically(self.filelist, panel, 1) 

297 

298 mainsizer = wx.BoxSizer(wx.VERTICAL) 

299 mainsizer.Add(splitter, 1, wx.GROW|wx.ALL, 5) 

300 

301 pack(self, mainsizer) 

302 # self.SetSize((725, 750)) 

303 self.Show() 

304 self.Raise() 

305 

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) 

311 

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) 

319 

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 

327 

328 form = self.form 

329 if form is None: 

330 form = self.mainpanel.read_form() 

331 datagroup = self.datagroup 

332 

333 wids = self.wids 

334 wids['data_title'].SetLabel('Linear Combination Result: %s ' % self.datagroup.filename) 

335 wids['show_fitrange'].SetValue(form['show_fitrange']) 

336 

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')) 

344 

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)) 

358 

359 wpan = self.wids['weightspanel'] 

360 wpan.DestroyChildren() 

361 

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) 

367 

368 for i, cname in enumerate(form['comp_names']): 

369 wview.AppendTextColumn(cname, width=100) 

370 wview.AppendTextColumn('Total', width=100) 

371 

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 

379 

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)) 

388 

389 os = wx.BoxSizer(wx.VERTICAL) 

390 os.Add(wview, 1, wx.GROW|wx.ALL) 

391 pack(wpan, os) 

392 

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() 

402 

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) 

408 

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) 

414 

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)) 

422 

423 wids['params'].DeleteAllItems() 

424 

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)) 

430 

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) 

438 

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 

446 

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' 

452 

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) 

456 

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})"""] 

459 

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)) 

467 

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)") 

471 

472 script = "\n".join(cmds) 

473 self.larch_eval(script.format(**form)) 

474 self.parent.controller.set_focus(topwin=self) 

475 

476 def onExportGroupFit(self, evt=None): 

477 "Export current fit to a new group in the main panel" 

478 

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) 

484 

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") 

497 

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()) 

503 

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 

510 

511 form = self.form 

512 label = [' energy ', 

513 ' data ', 

514 ' best_fit '] 

515 result = dgroup.lcf_result[nfit] 

516 

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)) 

525 

526 report = fit_report(result.result).split('\n') 

527 header.extend(report) 

528 

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) 

533 

534 label = ' '.join(label) 

535 write_ascii(path, header=header, label=label, *out) 

536 

537 

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 (*.*)|*.*' 

546 

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 

552 

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)] 

559 

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) 

568 

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) 

580 

581 out = np.array(out).transpose() 

582 write_ascii(path, header=header, label=label, *out) 

583 

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) 

590 

591 deffile = "%s_LinearFits%i.dat" % (dgroup.filename, nresults) 

592 wcards = 'Data Files (*.dat)|*.dat|All files (*.*)|*.*' 

593 

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'])] 

604 

605 label = [' energy ', ' data '] 

606 label.extend([' fit_%2.2d ' % i for i in range(nresults)]) 

607 label = ' '.join(label) 

608 

609 out = [results[0].xdata, results[0].ydata] 

610 for i, res in enumerate(results): 

611 out.append(results[i].yfit) 

612 

613 write_ascii(path, header=header, label=label, *out) 

614 

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 

624 

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'])] 

628 

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) 

639 

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('') 

655 

656 with open(path, 'w', encoding=sys.getdefaultencoding()) as fh: 

657 fh.write('\n'.join(out)) 

658 

659class LinearComboPanel(TaskPanel): 

660 """Liear Combination Panel""" 

661 def __init__(self, parent, controller, **kws): 

662 TaskPanel.__init__(self, parent, controller, panel='lincombo', **kws) 

663 

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) 

674 

675 def build_display(self): 

676 panel = self.panel 

677 wids = self.wids 

678 self.skip_process = True 

679 

680 wids['fitspace'] = Choice(panel, choices=list(Linear_ArrayChoices.keys()), 

681 action=self.onFitSpace, size=(175, -1)) 

682 wids['fitspace'].SetSelection(0) 

683 

684 add_text = self.add_text 

685 

686 opts = dict(digits=2, increment=1.0, relative_e0=False) 

687 defaults = self.get_defaultconfig() 

688 

689 self.make_fit_xspace_widgets(elo=defaults['elo_rel'], ehi=defaults['ehi_rel']) 

690 

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) 

695 

696 wids['fit_group'].Disable() 

697 wids['fit_selected'].Disable() 

698 

699 wids['show_results'] = Button(panel, 'Show Fit Results', 

700 action=self.onShowResults, size=(150, -1)) 

701 wids['show_results'].Disable() 

702 

703 wids['add_selected'] = Button(panel, 'Use Selected Groups as Components', 

704 size=(300, -1), action=self.onUseSelected) 

705 

706 opts = dict(default=True, size=(75, -1), action=self.onPlotOne) 

707 

708 wids['show_fitrange'] = Check(panel, label='show?', **opts) 

709 

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) 

716 

717 panel.Add(SimpleText(panel, 'Linear Combination Analysis', 

718 size=(350, -1), **self.titleopts), style=LEFT, dcol=4) 

719 

720 add_text('Array to Fit: ', newrow=True) 

721 panel.Add(wids['fitspace'], dcol=3) 

722 panel.Add(wids['show_results']) 

723 

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']) 

729 

730 panel.Add(HLine(panel, size=(625, 3)), dcol=5, newrow=True) 

731 

732 add_text('Build Model : ') 

733 panel.Add(wids['add_selected'], dcol=4) 

734 

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] 

739 

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) 

745 

746 wids['table'].SetMinSize((700, 250)) 

747 wids['table'].SetFont(self.font_fixedwidth) 

748 panel.Add(wids['table'], newrow=True, dcol=6) 

749 

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) 

761 

762 panel.Add(HLine(panel, size=(625, 3)), dcol=5, newrow=True) 

763 # panel.Add(wids['saveconf'], dcol=4, newrow=True) 

764 panel.pack() 

765 

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 

771 

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 ") 

782 

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) 

793 

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]) 

801 

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 

807 

808 

809 def onFitSpace(self, evt=None): 

810 fitspace = self.wids['fitspace'].GetStringSelection() 

811 self.update_config(dict(fitspace=fitspace)) 

812 

813 arrname = Linear_ArrayChoices.get(fitspace, 'norm') 

814 self.update_fit_xspace(arrname) 

815 self.plot() 

816 

817 def onComponent(self, evt=None, comp=None): 

818 if comp is None or evt is None: 

819 return 

820 

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) 

829 

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) 

834 

835 weight = 1.0 / len(comps) 

836 

837 for n, cname in comps: 

838 self.wids["compval_%2.2d" % n].SetValue(weight) 

839 

840 

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() 

847 

848 self.skip_process = True 

849 wids = self.wids 

850 

851 for attr in ('all_combos', 'sum_to_one', 'show_fitrange'): 

852 wids[attr].SetValue(opts.get(attr, True)) 

853 

854 for attr in ('elo', 'ehi', ): 

855 val = opts.get(attr, None) 

856 if val is not None: 

857 wids[attr].SetValue(val) 

858 

859 for attr in ('fitspace', ): 

860 if attr in opts: 

861 wids[attr].SetStringSelection(opts[attr]) 

862 

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) 

867 

868 self.skip_process = False 

869 

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} 

880 

881 wids = self.wids 

882 for attr in ('elo', 'ehi', 'max_ncomps'): 

883 opts[attr] = wids[attr].GetValue() 

884 

885 opts['fitspace'] = wids['fitspace'].GetStringSelection() 

886 

887 for attr in ('all_combos', 'vary_e0', 'sum_to_one', 'show_fitrange'): 

888 opts[attr] = wids[attr].GetValue() 

889 

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() 

895 

896 comps, cnames, wval, wmin, wmax = [], [], [], [], [] 

897 

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) 

907 

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' 

916 

917 opts['arrayname'] = Linear_ArrayChoices.get(opts['fitspace'], 'norm') 

918 self.skip_process = False 

919 return opts 

920 

921 def onSaveConfigBtn(self, evt=None): 

922 conf = self.get_config() 

923 conf.update(self.read_form()) 

924 self.set_defaultconfig(conf) 

925 

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) 

935 

936 grid_data = [] 

937 for grp in selected_groups: 

938 grid_data.append([grp, weight, 0, 1]) 

939 

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 

945 

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) 

951 

952 if len(groupname) == 0: 

953 print("no group to fit?") 

954 return 

955 

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 

970 

971 self.larch_eval(script.format(**form)) 

972 

973 dgroup = self.controller.get_group(groupname) 

974 self.show_subframe('lcf_result', LinComboResultFrame, 

975 datagroup=dgroup, mainpanel=self) 

976 

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) 

981 

982 def onShowResults(self, event=None): 

983 self.show_subframe('lcf_result', LinComboResultFrame, mainpanel=self) 

984 

985 def onFitOne(self, event=None): 

986 """ handle process events""" 

987 if self.skip_process: 

988 return 

989 

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 

995 

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 

1007 

1008 def plot(self, dgroup=None, with_fit=False): 

1009 if self.skip_plotting: 

1010 return 

1011 

1012 if dgroup is None: 

1013 dgroup = self.controller.get_group() 

1014 

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()