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

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

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

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 _larch = self.parent.controller.larch 

536 write_ascii(path, header=header, label=label, _larch=_larch, *out) 

537 

538 

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

547 

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 

553 

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

560 

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) 

569 

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) 

581 

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

583 _larch = self.parent.controller.larch 

584 write_ascii(path, header=header, label=label, _larch=_larch, *out) 

585 

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) 

592 

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

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

595 

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

606 

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

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

609 label = ' '.join(label) 

610 

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

612 for i, res in enumerate(results): 

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

614 

615 _larch = self.parent.controller.larch 

616 write_ascii(path, header=header, label=label, _larch=_larch, *out) 

617 

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 

627 

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

631 

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) 

642 

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

658 

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

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

661 

662class LinearComboPanel(TaskPanel): 

663 """Liear Combination Panel""" 

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

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

666 

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) 

677 

678 def build_display(self): 

679 panel = self.panel 

680 wids = self.wids 

681 self.skip_process = True 

682 

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

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

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

686 

687 add_text = self.add_text 

688 

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

690 defaults = self.get_defaultconfig() 

691 

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

693 

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) 

698 

699 wids['fit_group'].Disable() 

700 wids['fit_selected'].Disable() 

701 

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

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

704 wids['show_results'].Disable() 

705 

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

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

708 

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

710 

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

712 

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) 

719 

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

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

722 

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

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

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

726 

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

732 

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

734 

735 add_text('Build Model : ') 

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

737 

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] 

742 

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) 

748 

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

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

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

752 

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) 

764 

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

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

767 panel.pack() 

768 

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 

774 

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

785 

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) 

796 

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

804 

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 

810 

811 

812 def onFitSpace(self, evt=None): 

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

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

815 

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

817 self.update_fit_xspace(arrname) 

818 self.plot() 

819 

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

821 if comp is None or evt is None: 

822 return 

823 

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) 

832 

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) 

837 

838 weight = 1.0 / len(comps) 

839 

840 for n, cname in comps: 

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

842 

843 

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

850 

851 self.skip_process = True 

852 wids = self.wids 

853 

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

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

856 

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

858 val = opts.get(attr, None) 

859 if val is not None: 

860 wids[attr].SetValue(val) 

861 

862 for attr in ('fitspace', ): 

863 if attr in opts: 

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

865 

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) 

870 

871 self.skip_process = False 

872 

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} 

883 

884 wids = self.wids 

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

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

887 

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

889 

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

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

892 

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

898 

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

900 

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) 

910 

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' 

919 

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

921 self.skip_process = False 

922 return opts 

923 

924 def onSaveConfigBtn(self, evt=None): 

925 conf = self.get_config() 

926 conf.update(self.read_form()) 

927 self.set_defaultconfig(conf) 

928 

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) 

938 

939 grid_data = [] 

940 for grp in selected_groups: 

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

942 

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 

948 

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) 

954 

955 if len(groupname) == 0: 

956 print("no group to fit?") 

957 return 

958 

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 

973 

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

975 

976 dgroup = self.controller.get_group(groupname) 

977 self.show_subframe('lcf_result', LinComboResultFrame, 

978 datagroup=dgroup, mainpanel=self) 

979 

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) 

984 

985 def onShowResults(self, event=None): 

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

987 

988 def onFitOne(self, event=None): 

989 """ handle process events""" 

990 if self.skip_process: 

991 return 

992 

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 

998 

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 

1010 

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

1012 if self.skip_plotting: 

1013 return 

1014 

1015 if dgroup is None: 

1016 dgroup = self.controller.get_group() 

1017 

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