Coverage for larch/wxxas/feffit_panel.py: 0%

1676 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2024-10-16 21:04 +0000

1import time 

2import sys 

3import ast 

4import shutil 

5import string 

6import json 

7import math 

8from copy import deepcopy 

9from sys import exc_info 

10from string import printable 

11from functools import partial 

12from pathlib import Path 

13 

14import numpy as np 

15np.seterr(all='ignore') 

16 

17import wx 

18import wx.lib.scrolledpanel as scrolled 

19 

20import wx.dataview as dv 

21 

22from lmfit import Parameter 

23from lmfit.model import (save_modelresult, load_modelresult, 

24 save_model, load_model) 

25 

26import lmfit.models as lm_models 

27 

28from larch import Group, site_config 

29from larch.math import index_of 

30from larch.fitting import group2params, param 

31from larch.utils.jsonutils import encode4js, decode4js 

32from larch.inputText import is_complete 

33from larch.utils import fix_varname, fix_filename, gformat, mkdir, isValidName 

34from larch.io.export_modelresult import export_modelresult 

35from larch.xafs import feffit_report, feffpath 

36from larch.xafs.feffdat import FEFFDAT_VALUES 

37from larch.xafs.xafsutils import FT_WINDOWS 

38 

39from larch.wxlib import (ReportFrame, BitmapButton, FloatCtrl, FloatSpin, 

40 SetTip, GridPanel, get_icon, SimpleText, pack, 

41 Button, HLine, Choice, Check, MenuItem, GUIColors, 

42 CEN, RIGHT, LEFT, FRAMESTYLE, Font, FONTSIZE, 

43 COLORS, set_color, FONTSIZE_FW, FileSave, 

44 FileOpen, flatnotebook, EditableListBox, Popup, 

45 ExceptionPopup) 

46 

47from larch.wxlib.parameter import ParameterWidgets 

48from larch.wxlib.plotter import last_cursor_pos 

49from .taskpanel import TaskPanel 

50 

51from .config import (Feffit_KWChoices, Feffit_SpaceChoices, 

52 Feffit_PlotChoices, make_array_choice, 

53 PlotWindowChoices) 

54 

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

56 

57# PlotOne_Choices = [chik, chirmag, chirre, chirmr] 

58 

59Plot1_Choices = make_array_choice(['chi','chir_mag', 'chir_re', 'chir_mag+chir_re', 'chiq']) 

60Plot2_Choices = make_array_choice(['noplot', 'chi','chir_mag', 'chir_re', 'chir_mag+chir_re', 'chiq']) 

61 

62# Plot2_Choices = [noplot] + Plot1_Choices 

63 

64ScriptWcards = "Fit Models(*.lar)|*.lar|All files (*.*)|*.*" 

65 

66MIN_CORREL = 0.10 

67 

68COMMANDS = {} 

69COMMANDS['feffit_top'] = """##### FEFFIT Commands 

70## 

71## saved {ctime} 

72## to use from python, uncomment these import lines: 

73## 

74 

75#from larch.xafs import feffit, feffit_dataset, feffit_transform, feffit_report 

76#from larch.xafs import pre_edge, autobk, xftf, xftr, ff2chi, feffpath 

77#from larch.fitting import param_group, param 

78#from larch.io import read_ascii, read_athena, read_xdi, read_specfile 

79# 

80#### for interactive plotting from python (but not the Larch shell!) use: 

81#from larch.wxlib.xafsplots import plot_chik, plot_chir 

82#from wxmplot.interactive import get_wxapp 

83#wxapp = get_wxapp() # <- needed for plotting to work from python command-line 

84#### 

85#### 

86""" 

87 

88COMMANDS['data_source'] = """### you will need to add how the data chi(k) gets built: 

89### 

90## data group = {groupname} 

91## from source = {filename} 

92## some processing steps for this group (comment out as needed): 

93""" 

94 

95COMMANDS['xft'] = """# ffts on group {groupname:s} 

96xftf({groupname:s}, kmin={kmin:.3f}, kmax={kmax:.3f}, dk={dk:.3f}, window='{kwindow:s}', kweight={kweight:.3f}) 

97xftr({groupname:s}, rmin={rmin:.3f}, rmax={rmax:.3f}, dr={dr:.3f}, window='{rwindow:s}') 

98""" 

99 

100COMMANDS['feffit_params_init'] = """# create feffit Parameter Group to hold fit parameters 

101_feffit_params = param_group(reff=-1.0) 

102""" 

103 

104COMMANDS['feffit_trans'] = """# define Fourier transform and fitting space 

105_feffit_trans = feffit_transform(kmin={fit_kmin:.3f}, kmax={fit_kmax:.3f}, dk={fit_dk:.4f}, kw={fit_kwstring:s}, 

106 window='{fit_kwindow:s}', fitspace='{fit_space:s}', rmin={fit_rmin:.3f}, rmax={fit_rmax:.3f}) 

107""" 

108 

109 

110COMMANDS['feffit_dataset_init'] = """# create empty dataset 

111_feffit_dataset = feffit_dataset(transform=_feffit_trans) 

112""" 

113 

114 

115COMMANDS['paths_init'] = """# make sure dictionary for Feff Paths exists 

116try: 

117 npaths = len(_feffpaths.keys()) 

118except: 

119 _feffcache = {'paths':{}, 'runs':{}} # group of all paths, info about Feff runs 

120 _feffpaths = {} # dict of paths currently in use, copied from _feffcache.paths 

121#endtry 

122""" 

123 

124COMMANDS['paths_reset'] = """# clear existing paths 

125npaths = 0 

126_feffpaths = {} 

127#endtry 

128""" 

129 

130COMMANDS['cache_path'] = """ 

131_feffcache['paths']['{title:s}'] = feffpath('{fullpath:s}', 

132 label='{title:s}',feffrun='{feffrun:s}', degen=1) 

133""" 

134 

135COMMANDS['use_path'] = """ 

136_feffpaths['{title:s}'] = use_feffpath(_feffcache['paths'], '{title:s}', 

137 s02='{amp:s}', e0='{e0:s}', 

138 deltar='{delr:s}', sigma2='{sigma2:s}', 

139 third='{third:s}', ei='{ei:s}', use={use}) 

140""" 

141 

142COMMANDS['ff2chi'] = """# sum paths using a list of paths and a group of parameters 

143_feffit_dataset = feffit_dataset(data={groupname:s}, transform={trans:s}, 

144 refine_bkg={refine_bkg}, 

145 paths={paths:s}) 

146_feffit_dataset.model = ff2chi({paths:s}, paramgroup=_feffit_params) 

147""" 

148 

149COMMANDS['ff2chi_nodata'] = """# sum paths using a list of paths and a group of parameters 

150_feffit_dataset = feffit_dataset(transform={trans:s}, paths={paths:s}) 

151_feffit_dataset.model = ff2chi({paths:s}, paramgroup=_feffit_params) 

152""" 

153 

154COMMANDS['do_feffit'] = """# build feffit dataset, run feffit 

155_feffit_dataset = feffit_dataset(data={groupname:s}, transform={trans:s}, 

156 refine_bkg={refine_bkg}, 

157 paths={paths:s}) 

158_feffit_result = feffit({params}, _feffit_dataset) 

159if not hasattr({groupname:s}, 'feffit_history'): {groupname}.feffit_history = [] 

160{groupname:s}.feffit_history.insert(0, _feffit_result) 

161""" 

162 

163COMMANDS['path2chi'] = """# generate chi(k) and chi(R) for each path 

164for label, path in {paths_name:s}.items(): 

165 path.calc_chi_from_params({pargroup_name:s}) 

166 xftf(path, kmin={kmin:.3f}, kmax={kmax:.3f}, dk={dk:.3f}, 

167 window='{kwindow:s}', kweight={kweight:.3f}) 

168#endfor 

169""" 

170 

171def get_commands(textlines): 

172 """return list of complete larch command / python function calls 

173 from a list of text lines such as 'command history' 

174 """ 

175 out = [] 

176 work_lines = [] 

177 for line in textlines: 

178 work_lines.append(line) 

179 work = ' '.join(work_lines) 

180 if is_complete(work): 

181 out.append(work) 

182 work_lines.clear() 

183 out.extend(work_lines) # add any dangling text 

184 return out 

185 

186 

187class ParametersModel(dv.DataViewIndexListModel): 

188 def __init__(self, paramgroup, selected=None, pathkeys=None): 

189 dv.DataViewIndexListModel.__init__(self, 0) 

190 self.data = [] 

191 if selected is None: 

192 selected = [] 

193 self.selected = selected 

194 

195 if pathkeys is None: 

196 pathkeys = [] 

197 self.pathkeys = pathkeys 

198 

199 self.paramgroup = paramgroup 

200 self.read_data() 

201 

202 def set_data(self, paramgroup, selected=None, pathkeys=None): 

203 self.paramgroup = paramgroup 

204 if selected is not None: 

205 self.selected = selected 

206 if pathkeys is not None: 

207 self.pathkeys = pathkeys 

208 self.read_data() 

209 

210 def read_data(self): 

211 self.data = [] 

212 if self.paramgroup is None: 

213 self.data.append(['param name', False, 'vary', '0.0']) 

214 else: 

215 for pname, par in group2params(self.paramgroup).items(): 

216 if any([pname.endswith('_%s' % phash) for phash in self.pathkeys]): 

217 continue 

218 ptype = 'vary' 

219 if not par.vary: 

220 pytype = 'fixed' 

221 if getattr(par, 'skip', None) not in (False, None): 

222 ptype = 'skip' 

223 par.skip = ptype == 'skip' 

224 try: 

225 value = str(par.value) 

226 except: 

227 value = 'INVALID ' 

228 if par.expr is not None: 

229 ptype = 'constraint' 

230 value = "%s := %s" % (value, par.expr) 

231 sel = pname in self.selected 

232 self.data.append([pname, sel, ptype, value]) 

233 self.Reset(len(self.data)) 

234 

235 def select_all(self, value=True): 

236 self.selected = [] 

237 for irow, row in enumerate(self.data): 

238 self.SetValueByRow(value, irow, 1) 

239 if value: 

240 self.selected.append(row[0]) 

241 

242 def select_none(self): 

243 self.select_all(value=False) 

244 

245 def GetColumnType(self, col): 

246 return "bool" if col == 2 else "string" 

247 

248 def GetValueByRow(self, row, col): 

249 return self.data[row][col] 

250 

251 def SetValueByRow(self, value, row, col): 

252 self.data[row][col] = value 

253 return True 

254 

255 def GetColumnCount(self): 

256 return len(self.data[0]) 

257 

258 def GetCount(self): 

259 return len(self.data) 

260 

261 def GetAttrByRow(self, row, col, attr): 

262 """set row/col attributes (color, etc)""" 

263 ptype = self.data[row][2] 

264 if ptype == 'vary': 

265 attr.SetColour('#000000') 

266 elif ptype == 'fixed': 

267 attr.SetColour('#AA2020') 

268 elif ptype == 'skip': 

269 attr.SetColour('#50AA50') 

270 else: 

271 attr.SetColour('#2010BB') 

272 return True 

273 

274class EditParamsFrame(wx.Frame): 

275 """ edit parameters""" 

276 def __init__(self, parent=None, feffit_panel=None, 

277 paramgroup=None, selected=None): 

278 wx.Frame.__init__(self, None, -1, 

279 'Edit Feffit Parameters', 

280 style=FRAMESTYLE, size=(550, 325)) 

281 

282 self.parent = parent 

283 self.feffit_panel = feffit_panel 

284 self.paramgroup = paramgroup 

285 

286 spanel = scrolled.ScrolledPanel(self, size=(500, 275)) 

287 spanel.SetBackgroundColour('#EEEEEE') 

288 

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

290 

291 self.dvc = dv.DataViewCtrl(spanel, style=DVSTYLE) 

292 self.dvc.SetFont(self.font_fixedwidth) 

293 self.SetMinSize((500, 250)) 

294 

295 self.model = ParametersModel(paramgroup, selected) 

296 self.dvc.AssociateModel(self.model) 

297 

298 sizer = wx.BoxSizer(wx.VERTICAL) 

299 sizer.Add(self.dvc, 1, LEFT|wx.ALL|wx.GROW) 

300 pack(spanel, sizer) 

301 

302 spanel.SetupScrolling() 

303 

304 toppan = GridPanel(self, ncols=4, pad=1, itemstyle=LEFT) 

305 

306 bkws = dict(size=(200, -1)) 

307 toppan.Add(Button(toppan, "Select All", action=self.onSelAll, size=(175, -1))) 

308 toppan.Add(Button(toppan, "Select None", action=self.onSelNone, size=(175, -1))) 

309 toppan.Add(Button(toppan, "Select Unused Variables", action=self.onSelUnused, size=(200, -1))) 

310 toppan.Add(Button(toppan, "Remove Selected", action=self.onRemove, size=(175,-1)), newrow=True) 

311 toppan.Add(Button(toppan, "'Skip' Selected", action=self.onSkip, size=(175, -1))) 

312 toppan.Add(Button(toppan, "Force Refresh", action=self.onRefresh, size=(200, -1))) 

313 npan = wx.Panel(toppan) 

314 nsiz = wx.BoxSizer(wx.HORIZONTAL) 

315 

316 self.par_name = wx.TextCtrl(npan, -1, value='par_name', size=(125, -1), 

317 style=wx.TE_PROCESS_ENTER) 

318 self.par_expr = wx.TextCtrl(npan, -1, value='<expression or value>', size=(250, -1), 

319 style=wx.TE_PROCESS_ENTER) 

320 nsiz.Add(SimpleText(npan, "Add Parameter:"), 0) 

321 nsiz.Add(self.par_name, 0) 

322 nsiz.Add(self.par_expr, 1, wx.GROW|wx.ALL) 

323 nsiz.Add(Button(npan, label='Add', action=self.onAddParam), 0) 

324 pack(npan, nsiz) 

325 

326 toppan.Add(npan, dcol=4, newrow=True) 

327 toppan.Add(HLine(toppan, size=(500, 2)), dcol=5, newrow=True) 

328 toppan.pack() 

329 

330 mainsizer = wx.BoxSizer(wx.VERTICAL) 

331 mainsizer.Add(toppan, 0, wx.GROW|wx.ALL, 1) 

332 mainsizer.Add(spanel, 1, wx.GROW|wx.ALL, 1) 

333 pack(self, mainsizer) 

334 

335 columns = [('Parameter', 150, 'text'), 

336 ('Select', 75, 'bool'), 

337 ('Type', 75, 'text'), 

338 ('Value', 200, 'text')] 

339 

340 for icol, dat in enumerate(columns): 

341 label, width, dtype = dat 

342 method = self.dvc.AppendTextColumn 

343 mode = dv.DATAVIEW_CELL_EDITABLE 

344 if dtype == 'bool': 

345 method = self.dvc.AppendToggleColumn 

346 mode = dv.DATAVIEW_CELL_ACTIVATABLE 

347 method(label, icol, width=width, mode=mode) 

348 c = self.dvc.Columns[icol] 

349 c.Alignment = c.Renderer.Alignment = wx.ALIGN_LEFT 

350 c.SetSortable(False) 

351 

352 self.dvc.EnsureVisible(self.model.GetItem(0)) 

353 self.Bind(wx.EVT_CLOSE, self.onClose) 

354 

355 self.Show() 

356 self.Raise() 

357 wx.CallAfter(self.onSelUnused) 

358 

359 def onSelAll(self, event=None): 

360 self.model.select_all() 

361 self.model.read_data() 

362 

363 def onSelNone(self, event=None): 

364 self.model.select_none() 

365 self.model.read_data() 

366 

367 def onSelUnused(self, event=None): 

368 curr_syms = self.feffit_panel.get_used_params() 

369 unused = [] 

370 for pname, par in group2params(self.paramgroup).items(): 

371 if pname not in curr_syms: # and par.vary: 

372 unused.append(pname) 

373 self.model.set_data(self.paramgroup, selected=unused, 

374 pathkeys=self.feffit_panel.get_pathkeys()) 

375 

376 def onRemove(self, event=None): 

377 out = [] 

378 for pname, sel, ptype, val in self.model.data: 

379 if sel: 

380 out.append(pname) 

381 nout = len(out) 

382 

383 msg = f"Remove {nout:d} Parameters? \n This is not easy to undo!" 

384 dlg = wx.MessageDialog(self, msg, 'Warning', wx.YES | wx.NO ) 

385 if (wx.ID_YES == dlg.ShowModal()): 

386 for pname, sel, ptype, val in self.model.data: 

387 if sel: 

388 out.append(pname) 

389 if hasattr(self.paramgroup, pname): 

390 delattr(self.paramgroup, pname) 

391 

392 self.model.set_data(self.paramgroup, selected=None, 

393 pathkeys=self.feffit_panel.get_pathkeys()) 

394 self.model.read_data() 

395 self.feffit_panel.get_pathpage('parameters').Rebuild() 

396 dlg.Destroy() 

397 

398 def onSkip(self, event=None): 

399 for pname, sel, ptype, val in self.model.data: 

400 if sel: 

401 par = getattr(self.paramgroup, pname, None) 

402 if par is not None: 

403 par.skip = True 

404 self.model.read_data() 

405 self.feffit_panel.get_pathpage('parameters').Rebuild() 

406 

407 

408 def onAddParam(self, event=None): 

409 par_name = self.par_name.GetValue() 

410 par_expr = self.par_expr.GetValue() 

411 

412 try: 

413 val = float(par_expr) 

414 ptype = 'vary' 

415 except: 

416 val = par_expr 

417 ptype = 'expr' 

418 

419 if ptype == 'vary': 

420 cmd = f"_feffit_params.{par_name} = param({val}, vary=True)" 

421 else: 

422 cmd = f"_feffit_params.{par_name} = param(expr='{val}')" 

423 

424 self.feffit_panel.larch_eval(cmd) 

425 self.onRefresh() 

426 

427 def onRefresh(self, event=None): 

428 self.paramgroup = self.feffit_panel.get_paramgroup() 

429 self.model.set_data(self.paramgroup, 

430 pathkeys=self.feffit_panel.get_pathkeys()) 

431 self.model.read_data() 

432 self.feffit_panel.get_pathpage('parameters').Rebuild() 

433 

434 def onClose(self, event=None): 

435 self.Destroy() 

436 

437 

438class FeffitParamsPanel(wx.Panel): 

439 def __init__(self, parent=None, feffit_panel=None, **kws): 

440 wx.Panel.__init__(self, parent, -1, size=(550, 250)) 

441 self.feffit_panel = feffit_panel 

442 self.parwids = {} 

443 self.SetFont(Font(FONTSIZE)) 

444 spanel = scrolled.ScrolledPanel(self) 

445 spanel.SetSize((250, 250)) 

446 spanel.SetMinSize((50, 50)) 

447 panel = self.panel = GridPanel(spanel, ncols=8, nrows=30, pad=1, itemstyle=LEFT) 

448 panel.SetFont(Font(FONTSIZE)) 

449 

450 def SLabel(label, size=(80, -1), **kws): 

451 return SimpleText(panel, label, size=size, style=wx.ALIGN_LEFT, **kws) 

452 

453 panel.Add(SLabel("Feffit Parameters ", colour='#0000AA', size=(200, -1)), dcol=2) 

454 panel.Add(Button(panel, 'Edit Parameters', action=self.onEditParams), dcol=2) 

455 panel.Add(Button(panel, 'Force Refresh', action=self.Rebuild), dcol=3) 

456 

457 panel.Add(SLabel("Parameter "), style=wx.ALIGN_LEFT, newrow=True) 

458 panel.AddMany((SLabel(" Value"), SLabel(" Type"), SLabel(' Bounds'), 

459 SLabel(" Min", size=(60, -1)), 

460 SLabel(" Max", size=(60, -1)), 

461 SLabel(" Expression"))) 

462 

463 self.update() 

464 panel.pack() 

465 ssizer = wx.BoxSizer(wx.VERTICAL) 

466 ssizer.Add(panel, 1, wx.GROW|wx.ALL, 2) 

467 pack(spanel, ssizer) 

468 

469 spanel.SetupScrolling() 

470 mainsizer = wx.BoxSizer(wx.VERTICAL) 

471 mainsizer.Add(spanel, 1, wx.GROW|wx.ALL, 2) 

472 pack(self, mainsizer) 

473 

474 def Rebuild(self, event=None): 

475 for pname, parwid in self.parwids.items(): 

476 for x in parwid.widgets: 

477 x.Destroy() 

478 self.panel.irow = 1 

479 self.parwids = {} 

480 self.update() 

481 

482 def set_init_values(self, params): 

483 for pname, par in params.items(): 

484 if pname in self.parwids and par.vary: 

485 stderr = getattr(par, 'stderr', 0.001) 

486 try: 

487 prec = max(1, min(8, round(2-math.log10(stderr)))) 

488 except: 

489 prec = 5 

490 self.parwids[pname].value.SetValue(("%%.%.df" % prec) % par.value) 

491 

492 def update(self): 

493 pargroup = self.feffit_panel.get_paramgroup() 

494 hashkeys = self.feffit_panel.get_pathkeys() 

495 params = group2params(pargroup) 

496 for pname, par in params.items(): 

497 if any([pname.endswith('_%s' % phash) for phash in hashkeys]): 

498 continue 

499 if pname not in self.parwids and not hasattr(par, '_is_pathparam'): 

500 pwids = ParameterWidgets(self.panel, par, name_size=120, 

501 expr_size=200, float_size=85, 

502 with_skip=True, 

503 widgets=('name', 'value', 

504 'minval', 'maxval', 

505 'vary', 'expr')) 

506 

507 self.parwids[pname] = pwids 

508 self.panel.Add(pwids.name, newrow=True) 

509 self.panel.AddMany((pwids.value, pwids.vary, pwids.bounds, 

510 pwids.minval, pwids.maxval, pwids.expr)) 

511 self.panel.pack() 

512 

513 pwids = self.parwids[pname] 

514 varstr = 'vary' if par.vary else 'fix' 

515 if par.expr is not None: 

516 varstr = 'constrain' 

517 pwids.expr.SetValue(par.expr) 

518 if getattr(par, 'skip', None) not in (False, None): 

519 varstr = 'skip' 

520 pwids.vary.SetStringSelection(varstr) 

521 if varstr != 'skip': 

522 pwids.value.SetValue(par.value) 

523 pwids.minval.SetValue(par.min) 

524 pwids.maxval.SetValue(par.max) 

525 pwids.onVaryChoice() 

526 self.panel.Update() 

527 

528 def onEditParams(self, event=None): 

529 pargroup = self.feffit_panel.get_paramgroup() 

530 self.feffit_panel.show_subframe('edit_params', EditParamsFrame, 

531 paramgroup=pargroup, 

532 feffit_panel=self.feffit_panel) 

533 

534 def RemoveParams(self, event=None, name=None): 

535 if name is None: 

536 return 

537 pargroup = self.feffit_panel.get_paramgroup() 

538 

539 if hasattr(pargroup, name): 

540 delattr(pargroup, name) 

541 if name in self.parwids: 

542 pwids = self.parwids.pop(name) 

543 pwids.name.Destroy() 

544 pwids.value.Destroy() 

545 pwids.vary.Destroy() 

546 pwids.bounds.Destroy() 

547 pwids.minval.Destroy() 

548 pwids.maxval.Destroy() 

549 pwids.expr.Destroy() 

550 pwids.remover.Destroy() 

551 

552 def generate_params(self, event=None): 

553 s = [] 

554 s.append(COMMANDS['feffit_params_init']) 

555 for name, pwids in self.parwids.items(): 

556 param = pwids.param 

557 args = [f'{param.value}'] 

558 minval = pwids.minval.GetValue() 

559 if np.isfinite(minval): 

560 args.append(f'min={minval}') 

561 maxval = pwids.maxval.GetValue() 

562 if np.isfinite(maxval): 

563 args.append(f'max={maxval}') 

564 

565 varstr = pwids.vary.GetStringSelection() 

566 if varstr == 'skip': 

567 args.append('skip=True, vary=False') 

568 elif param.expr is not None and varstr == 'constrain': 

569 args.append(f"expr='{param.expr}'") 

570 elif varstr == 'vary': 

571 args.append(f'vary=True') 

572 else: 

573 args.append(f'vary=False') 

574 args = ', '.join(args) 

575 cmd = f'_feffit_params.{name} = param({args})' 

576 s.append(cmd) 

577 return s 

578 

579 

580class FeffPathPanel(wx.Panel): 

581 """Feff Path """ 

582 def __init__(self, parent, feffit_panel, filename, title, user_label, 

583 geomstr, absorber, shell, reff, nleg, degen, 

584 par_amp, par_e0, par_delr, par_sigma2, par_third, par_ei): 

585 

586 self.parent = parent 

587 self.title = title 

588 self.user_label = fix_varname(f'{title:s}') 

589 self.feffit_panel = feffit_panel 

590 self.editing_enabled = False 

591 

592 wx.Panel.__init__(self, parent, -1, size=(550, 250)) 

593 self.SetFont(Font(FONTSIZE)) 

594 panel = GridPanel(self, ncols=4, nrows=4, pad=2, itemstyle=LEFT) 

595 

596 self.fullpath = filename 

597 pfile = Path(filename).absolute() 

598 feffdat_file = pfile.name 

599 dirname = pfile.parent.name 

600 

601 self.user_label = user_label 

602 

603 self.nleg = nleg 

604 self.reff = reff 

605 self.geomstr = geomstr 

606 # self.geometry = geometry 

607 

608 def SLabel(label, size=(80, -1), **kws): 

609 return SimpleText(panel, label, size=size, style=LEFT, **kws) 

610 

611 self.wids = wids = {} 

612 for name, expr in (('label', user_label), 

613 ('amp', par_amp), 

614 ('e0', par_e0), 

615 ('delr', par_delr), 

616 ('sigma2', par_sigma2), 

617 ('third', par_third), 

618 ('ei', par_ei)): 

619 self.wids[name] = wx.TextCtrl(panel, -1, size=(250, -1), 

620 value=expr, style=wx.TE_PROCESS_ENTER) 

621 wids[name+'_val'] = SimpleText(panel, '', size=(150, -1), style=LEFT) 

622 

623 wids['use'] = Check(panel, default=True, label='Use in Fit?', size=(100, -1)) 

624 wids['del'] = Button(panel, 'Remove This Path', size=(150, -1), 

625 action=self.onRemovePath) 

626 wids['plot_feffdat'] = Button(panel, 'Plot F(k)', size=(150, -1), 

627 action=self.onPlotFeffDat) 

628 

629 scatt = {2: 'Single', 3: 'Double', 4: 'Triple', 

630 5: 'Quadruple'}.get(nleg, f'{nleg-1:d}-atom') 

631 scatt = scatt + ' Scattering' 

632 

633 

634 title1 = f'{dirname:s}: {feffdat_file:s} {absorber:s} {shell:s} edge' 

635 title2 = f'Reff={reff:.4f}, Degen={degen:.1f}, {scatt:s}: {geomstr:s}' 

636 

637 panel.Add(SLabel(title1, size=(375, -1), colour='#0000AA'), 

638 dcol=2, style=wx.ALIGN_LEFT, newrow=True) 

639 panel.Add(wids['use']) 

640 panel.Add(wids['del']) 

641 panel.Add(SLabel(title2, size=(425, -1)), 

642 dcol=3, style=wx.ALIGN_LEFT, newrow=True) 

643 panel.Add(wids['plot_feffdat']) 

644 

645 panel.AddMany((SLabel('Label'), wids['label'], wids['label_val']), newrow=True) 

646 panel.AddMany((SLabel('Amplitude'), wids['amp'], wids['amp_val']), newrow=True) 

647 panel.AddMany((SLabel('E0 '), wids['e0'], wids['e0_val']), newrow=True) 

648 panel.AddMany((SLabel('Delta R'), wids['delr'], wids['delr_val']), newrow=True) 

649 panel.AddMany((SLabel('sigma2'), wids['sigma2'], wids['sigma2_val']),newrow=True) 

650 panel.AddMany((SLabel('third'), wids['third'], wids['third_val']), newrow=True) 

651 panel.AddMany((SLabel('Eimag'), wids['ei'], wids['ei_val']), newrow=True) 

652 panel.pack() 

653 sizer= wx.BoxSizer(wx.VERTICAL) 

654 sizer.Add(panel, 1, LEFT|wx.GROW|wx.ALL, 2) 

655 pack(self, sizer) 

656 

657 

658 def enable_editing(self): 

659 for name in ('label', 'amp', 'e0', 'delr', 'sigma2', 'third', 'ei'): 

660 self.wids[name].Bind(wx.EVT_TEXT_ENTER, partial(self.onExpression, name=name)) 

661 self.wids[name].Bind(wx.EVT_KILL_FOCUS, partial(self.onExpression, name=name)) 

662 self.editing_enabled = True 

663 self.wids['label'].SetValue(self.user_label) 

664 

665 def set_userlabel(self, label): 

666 self.wids['label'].SetValue(label) 

667 

668 def get_expressions(self): 

669 out = {'use': self.wids['use'].IsChecked()} 

670 for key in ('label', 'amp', 'e0', 'delr', 'sigma2', 'third', 'ei'): 

671 val = self.wids[key].GetValue().strip() 

672 if len(val) == 0: val = '0' 

673 out[key] = val 

674 return out 

675 

676 def onExpression(self, event=None, name=None): 

677 if name is None: 

678 return 

679 expr = self.wids[name].GetValue() 

680 if name == 'label': 

681 time.sleep(0.001) 

682 return 

683 

684 expr = self.wids[name].GetValue().strip() 

685 if len(expr) < 1: 

686 return 

687 opts= dict(value=1.e-3, minval=None, maxval=None) 

688 if name == 'sigma2': 

689 opts['minval'] = 0 

690 opts['maxval'] = 1 

691 opts['value'] = np.sqrt(self.reff)/200.0 

692 elif name == 'delr': 

693 opts['minval'] = -0.75 

694 opts['maxval'] = 0.75 

695 elif name == 'amp': 

696 opts['value'] = 1 

697 result = self.feffit_panel.update_params_for_expr(expr, **opts) 

698 if result: 

699 pargroup = self.feffit_panel.get_paramgroup() 

700 _eval = pargroup.__params__._asteval 

701 try: 

702 value = _eval.eval(expr, show_errors=False, raise_errors=False) 

703 if value is not None: 

704 value = gformat(value, 11) 

705 self.wids[name + '_val'].SetLabel(f'= {value}') 

706 except: 

707 result = False 

708 

709 if result: 

710 bgcol, fgcol = 'white', 'black' 

711 else: 

712 bgcol, fgcol = '#AAAA4488', '#AA0000' 

713 self.wids[name].SetForegroundColour(fgcol) 

714 self.wids[name].SetBackgroundColour(bgcol) 

715 self.wids[name].SetOwnBackgroundColour(bgcol) 

716 if event is not None: 

717 event.Skip() 

718 

719 

720 def onPlotFeffDat(self, event=None): 

721 cmd = f"plot_feffdat(_feffpaths['{self.title}'], title='Feff data for path {self.title}')" 

722 self.feffit_panel.larch_eval(cmd) 

723 

724 def onRemovePath(self, event=None): 

725 msg = f"Delete Path {self.title:s}?" 

726 dlg = wx.MessageDialog(self, msg, 'Warning', wx.YES | wx.NO ) 

727 if (wx.ID_YES == dlg.ShowModal()): 

728 self.feffit_panel.paths_data.pop(self.title) 

729 self.feffit_panel.model_needs_build = True 

730 path_nb = self.feffit_panel.paths_nb 

731 for i in range(path_nb.GetPageCount()): 

732 if self.title == path_nb.GetPageText(i).strip(): 

733 path_nb.DeletePage(i) 

734 self.feffit_panel.skip_unused_params() 

735 dlg.Destroy() 

736 

737 def update_values(self): 

738 pargroup = self.feffit_panel.get_paramgroup() 

739 _eval = pargroup.__params__._asteval 

740 for par in ('amp', 'e0', 'delr', 'sigma2', 'third', 'ei'): 

741 expr = self.wids[par].GetValue().strip() 

742 if len(expr) > 0: 

743 try: 

744 value = _eval.eval(expr, show_errors=False, raise_errors=False) 

745 if value is not None: 

746 value = gformat(value, 10) 

747 self.wids[par + '_val'].SetLabel(f'= {value}') 

748 except: 

749 self.feffit_panel.update_params_for_expr(expr) 

750 

751 

752class FeffitPanel(TaskPanel): 

753 def __init__(self, parent=None, controller=None, **kws): 

754 self.resetting = False 

755 self.model_needs_rebuild = False 

756 self.params_need_update = False 

757 self.rmin_warned = False 

758 TaskPanel.__init__(self, parent, controller, panel='feffit', **kws) 

759 self.paths_data = {} 

760 self.config_saved = self.get_defaultconfig() 

761 self.dgroup = None 

762 

763 def onPanelExposed(self, **kws): 

764 # called when notebook is selected 

765 dgroup = self.controller.get_group() 

766 try: 

767 pargroup = self.get_paramgroup() 

768 self.params_panel.update() 

769 fname = self.controller.filelist.GetStringSelection() 

770 gname = self.controller.file_groups[fname] 

771 dgroup = self.controller.get_group(gname) 

772 if not hasattr(dgroup, 'chi'): 

773 self.parent.process_exafs(dgroup) 

774 self.fill_form(dgroup) 

775 except: 

776 pass # print(" Cannot Fill feffit panel from group ") 

777 if dgroup is not self.dgroup: 

778 # setting up feffit for this group 

779 self.dgroup = dgroup 

780 try: 

781 has_fit_hist = len(self.dgroup.feffit_history) > 0 

782 except: 

783 has_fit_hist = getattr(self.larch.symtable, '_feffit_dataset', None) is not None 

784 

785 if has_fit_hist: 

786 self.wids['show_results'].Enable() 

787 

788 

789 feffpaths = getattr(self.larch.symtable, '_feffpaths', None) 

790 if feffpaths is not None: 

791 self.reset_paths() 

792 

793 self.params_panel.update() 

794 self.skip_unused_params() 

795 self.params_need_update = False 

796 

797 def build_display(self): 

798 self.paths_nb = flatnotebook(self, {}, on_change=self.onPathsNBChanged, 

799 with_dropdown=True) 

800 

801 self.params_panel = FeffitParamsPanel(parent=self.paths_nb, 

802 feffit_panel=self) 

803 self.paths_nb.AddPage(self.params_panel, ' Parameters ', True) 

804 pan = self.panel # = GridPanel(self, ncols=4, nrows=4, pad=2, itemstyle=LEFT) 

805 

806 self.wids = wids = {} 

807 

808 fsopts = dict(digits=2, increment=0.1, with_pin=True) 

809 

810 fit_kmin = self.add_floatspin('fit_kmin', value=2, **fsopts) 

811 fit_kmax = self.add_floatspin('fit_kmax', value=17, **fsopts) 

812 fit_dk = self.add_floatspin('fit_dk', value=4, **fsopts) 

813 fit_rmax = self.add_floatspin('fit_rmax', value=5, **fsopts) 

814 fit_rmin = self.add_floatspin('fit_rmin', value=1, action=self.onRmin, **fsopts) 

815 

816 wids['fit_kwstring'] = Choice(pan, size=(120, -1), 

817 choices=list(Feffit_KWChoices.keys())) 

818 wids['fit_kwstring'].SetSelection(1) 

819 

820 wids['fit_kwindow'] = Choice(pan, choices=list(FT_WINDOWS), size=(150, -1)) 

821 

822 wids['fit_space'] = Choice(pan, choices=list(Feffit_SpaceChoices.keys()), 

823 size=(150, -1)) 

824 

825 wids['plot_kw'] = Choice(pan, size=(80, -1), 

826 choices=['0', '1', '2', '3', '4'], default=2) 

827 

828 wids['plot1_op'] = Choice(pan, choices=list(Plot1_Choices.keys()), 

829 action=self.onPlot, size=(150, -1)) 

830 wids['plot1_op'].SetSelection(1) 

831 

832 wids['plot1_voff'] = FloatSpin(pan, value=0, digits=2, increment=0.25, 

833 size=(100, -1), action=self.onPlot) 

834 

835 wids['plot1_paths'] = Check(pan, default=False, label='Plot Each Path', 

836 action=self.onPlot) 

837 wids['plot1_ftwins'] = Check(pan, default=False, label='Plot FT Windows', 

838 action=self.onPlot) 

839 

840 

841 wids['plot2_win'] = Choice(pan, choices=PlotWindowChoices, 

842 action=self.onPlot, size=(55, -1)) 

843 wids['plot2_win'].SetStringSelection('2') 

844 wids['plot2_win'].SetToolTip('Plot Window for Second Plot') 

845 

846 wids['plot2_op'] = Choice(pan, choices=list(Plot2_Choices.keys()), 

847 action=self.onPlot, size=(150, -1)) 

848 

849 

850 wids['plot2_voff'] = FloatSpin(pan, value=0, digits=2, increment=0.25, 

851 size=(100, -1), action=self.onPlot) 

852 

853 wids['plot2_paths'] = Check(pan, default=False, label='Plot Each Path', 

854 action=self.onPlot) 

855 wids['plot2_ftwins'] = Check(pan, default=False, label='Plot FT Windows', 

856 action=self.onPlot) 

857 wids['plot_current'] = Button(pan,'Plot Current Model', 

858 action=self.onPlot, size=(175, -1)) 

859 

860 wids['refine_bkg'] = Check(pan, default=False, 

861 label='Refine Background during Fit?') 

862 wids['do_fit'] = Button(pan, 'Fit Data to Model', 

863 action=self.onFitModel, size=(175, -1)) 

864 wids['show_results'] = Button(pan, 'Show Fit Results', 

865 action=self.onShowResults, size=(175, -1)) 

866 wids['show_results'].Disable() 

867 

868 def add_text(text, dcol=1, newrow=True): 

869 pan.Add(SimpleText(pan, text), dcol=dcol, newrow=newrow) 

870 

871 pan.Add(SimpleText(pan, 'Feff Fitting', 

872 size=(150, -1), **self.titleopts), style=LEFT, dcol=1) 

873 pan.Add(SimpleText(pan, 'Use Feff->Browse Feff Calculations to Add Paths', 

874 size=(425, -1)), style=LEFT, dcol=4) 

875 

876 add_text('Fitting Space: ') 

877 pan.Add(wids['fit_space']) 

878 

879 add_text('k weightings: ', newrow=False) 

880 pan.Add(wids['fit_kwstring'], dcol=3) 

881 

882 add_text('k min: ') 

883 pan.Add(fit_kmin) 

884 add_text(' k max: ', newrow=False) 

885 pan.Add(fit_kmax) 

886 

887 add_text('k Window: ') 

888 pan.Add(wids['fit_kwindow']) 

889 add_text('dk: ', newrow=False) 

890 pan.Add(fit_dk) 

891 

892 add_text('R min: ') 

893 pan.Add(fit_rmin) 

894 add_text('R max: ', newrow=False) 

895 pan.Add(fit_rmax) 

896 add_text(' ', newrow=True) 

897 pan.Add(wids['refine_bkg'], dcol=3) 

898 

899 pan.Add(HLine(pan, size=(600, 2)), dcol=6, newrow=True) 

900 

901 pan.Add(wids['plot_current'], dcol=1, newrow=True) 

902 pan.Add(wids['plot1_op'], dcol=1) 

903 add_text('k-weight:' , newrow=False) 

904 pan.Add(wids['plot_kw'], dcol=1) 

905 

906 pan.Add(wids['plot1_ftwins'], newrow=True) 

907 pan.Add(wids['plot1_paths']) 

908 add_text('Vertical Offset' , newrow=False) 

909 pan.Add(wids['plot1_voff']) 

910 

911 

912 add_text('Add Second Plot: ') 

913 

914 pan.Add(wids['plot2_op'], dcol=1) 

915 add_text('Plot Window:' , newrow=False) 

916 pan.Add(wids['plot2_win']) 

917 

918 pan.Add(wids['plot2_ftwins'], newrow=True) 

919 pan.Add(wids['plot2_paths']) 

920 add_text('Vertical Offset' , newrow=False) 

921 pan.Add(wids['plot2_voff']) 

922 

923 

924 pan.Add(wids['do_fit'], dcol=1, newrow=True) 

925 pan.Add(wids['show_results']) 

926 pan.Add((5, 5), newrow=True) 

927 

928 pan.Add(HLine(pan, size=(600, 2)), dcol=6, newrow=True) 

929 pan.pack() 

930 

931 sizer = wx.BoxSizer(wx.VERTICAL) 

932 sizer.Add(pan, 0, LEFT, 3) 

933 sizer.Add((3, 3), 0, LEFT, 3) 

934 sizer.Add(SimpleText(self, ' Parameters and Paths'), 0, LEFT, 2) 

935 sizer.Add((3, 3), 0, LEFT, 3) 

936 sizer.Add(self.paths_nb, 1, LEFT|wx.GROW, 5) 

937 pack(self, sizer) 

938 

939 def onRmin(self, event=None, **kws): 

940 dgroup = self.controller.get_group() 

941 if dgroup is None: 

942 return 

943 rbkg = getattr(dgroup, 'rbkg', None) 

944 callargs = getattr(dgroup, 'callargs', None) 

945 if rbkg is None and callargs is not None: 

946 autobk_args = getattr(callargs, 'autobk', {'rbkg': 1.0}) 

947 rbkg = autobk_args.get('rbkg', None) 

948 if rbkg is None: rbkg = 0.0 

949 rmin = self.wids['fit_rmin'].GetValue() 

950 if rmin > (rbkg-0.025): 

951 self.rmin_warned = False 

952 

953 if rmin < (rbkg-0.025) and not self.rmin_warned: 

954 self.rmin_warned = True 

955 Popup(self, 

956 f"""Rmin={rmin:.3f} Ang is below Rbkg={rbkg:.3f} Ang. 

957 

958 This should be done with caution.""", 

959 "Warning: Rmin < Rbkg", 

960 style=wx.ICON_WARNING|wx.OK_DEFAULT) 

961 

962 

963 def onPathsNBChanged(self, event=None): 

964 updater = getattr(self.paths_nb.GetCurrentPage(), 'update_values', None) 

965 if self.params_need_update and not self.resetting: 

966 self.params_panel.update() 

967 self.skip_unused_params() 

968 self.params_need_update = False 

969 if callable(updater) and not self.resetting: 

970 updater() 

971 

972 def get_config(self, dgroup=None): 

973 """get and set processing configuration for a group""" 

974 if dgroup is None: 

975 dgroup = self.controller.get_group() 

976 if dgroup is None: 

977 conf = None 

978 if not hasattr(dgroup, 'chi'): 

979 self.parent.process_exafs(dgroup) 

980 

981 dconf = self.get_defaultconfig() 

982 

983 if dgroup is None: 

984 return dconf 

985 if not hasattr(dgroup, 'config'): 

986 dgroup.config = Group() 

987 

988 conf = getattr(dgroup.config, self.configname, dconf) 

989 for k, v in dconf.items(): 

990 if k not in conf: 

991 conf[k] = v 

992 

993 econf = getattr(dgroup.config, 'exafs', {}) 

994 for key in ('fit_kmin', 'fit_kmax', 'fit_dk', 

995 'fit_rmin', 'fit_rmax', 'fit_dr', 

996 'fit_kwindow', 'fit_rwindow'): 

997 val = conf.get(key, -1) 

998 if val in (None, -1, 'Auto'): 

999 alt = key.replace('fit', 'fft') 

1000 if alt in econf: 

1001 conf[key] = econf[alt] 

1002 setattr(dgroup.config, self.configname, conf) 

1003 self.config_saved = conf 

1004 return conf 

1005 

1006 def process(self, dgroup=None, **kws): 

1007 # print("Feffit Panel Process ", dgroup, time.ctime()) 

1008 if dgroup is None: 

1009 dgroup = self.controller.get_group() 

1010 

1011 conf = self.get_config(dgroup=dgroup) 

1012 conf.update(kws) 

1013 

1014 if self.params_need_update: 

1015 self.params_panel.update() 

1016 self.skip_unused_params() 

1017 self.params_need_update = False 

1018 

1019 opts = self.read_form(dgroup=dgroup) 

1020 if dgroup is not None: 

1021 self.dgroup = dgroup 

1022 for attr in ('fit_kmin', 'fit_kmax', 'fit_dk', 'fit_rmin', 

1023 'fit_rmax', 'fit_kwindow', 'fit_rwindow', 

1024 'fit_dr', 'fit_kwstring', 'fit_space', 

1025 'fit_plot', 'plot1_paths'): 

1026 

1027 conf[attr] = opts.get(attr, None) 

1028 

1029 if not hasattr(dgroup, 'config'): 

1030 dgroup.config = Group() 

1031 setattr(dgroup.config, self.configname, conf) 

1032 

1033 orig_dgroup = self.controller.get_group() 

1034 if orig_dgroup is not None: 

1035 setattr(orig_dgroup.config, self.configname, conf) 

1036 # print("dgroup " , conf) 

1037 

1038 try: 

1039 tchi = getattr(dgroup, 'chi', np.zeros(10)) 

1040 has_data = 1.e-12 < (tchi**2).sum() 

1041 except: 

1042 has_data = False 

1043 cmds = [COMMANDS['feffit_trans'].format(**conf)] 

1044 _feffit_dataset = getattr(self.larch.symtable, '_feffit_dataset', None) 

1045 if _feffit_dataset is None: 

1046 cmds.append(COMMANDS['feffit_dataset_init']) 

1047 if has_data: 

1048 cmds.append(f"_feffit_dataset.set_datagroup({dgroup.groupname})") 

1049 cmds.append(f"_feffit_dataset.refine_bkg = {opts['refine_bkg']}") 

1050 self.larch.eval('\n'.join(cmds)) 

1051 return opts 

1052 

1053 def fill_form(self, dat): 

1054 dgroup = self.controller.get_group() 

1055 conf = self.get_config(dat) 

1056 

1057 for attr in ('fit_kmin', 'fit_kmax', 'fit_rmin', 'fit_rmax', 'fit_dk'): 

1058 self.wids[attr].SetValue(conf[attr]) 

1059 

1060 self.wids['fit_kwindow'].SetStringSelection(conf['fit_kwindow']) 

1061 

1062 fit_space = conf.get('fit_space', 'r') 

1063 

1064 for key, val in Feffit_SpaceChoices.items(): 

1065 if fit_space in (key, val): 

1066 self.wids['fit_space'].SetStringSelection(key) 

1067 

1068 for key, val in Feffit_KWChoices.items(): 

1069 if conf['fit_kwstring'] == val: 

1070 self.wids['fit_kwstring'].SetStringSelection(key) 

1071 

1072 def read_form(self, dgroup=None): 

1073 "read form, returning dict of values" 

1074 

1075 if dgroup is None: 

1076 try: 

1077 fname = self.controller.filelist.GetStringSelection() 

1078 gname = self.controller.file_groups[fname] 

1079 dgroup = self.controller.get_group() 

1080 except: 

1081 gname = fname = dgroup = None 

1082 else: 

1083 

1084 gname = dgroup.groupname 

1085 fname = dgroup.filename 

1086 

1087 form_opts = {'datagroup': dgroup, 'groupname': gname, 'filename': fname} 

1088 wids = self.wids 

1089 

1090 for attr in ('fit_kmin', 'fit_kmax', 'fit_rmin', 'fit_rmax', 'fit_dk'): 

1091 form_opts[attr] = wids[attr].GetValue() 

1092 form_opts['fit_kwstring'] = Feffit_KWChoices[wids['fit_kwstring'].GetStringSelection()] 

1093 if len(form_opts['fit_kwstring']) == 1: 

1094 d = form_opts['fit_kwstring'] 

1095 else: 

1096 d = form_opts['fit_kwstring'].replace('[', '').strip(',').split()[0] 

1097 try: 

1098 form_opts['fit_kweight'] = int(d) 

1099 except: 

1100 form_opts['fit_kweight'] = 2 

1101 

1102 

1103 form_opts['refine_bkg'] = wids['refine_bkg'].IsChecked() 

1104 fitspace_string = wids['fit_space'].GetStringSelection() 

1105 form_opts['fit_space'] = Feffit_SpaceChoices[fitspace_string] 

1106 

1107 form_opts['fit_kwindow'] = wids['fit_kwindow'].GetStringSelection() 

1108 form_opts['plot_kw'] = int(wids['plot_kw'].GetStringSelection()) 

1109 form_opts['plot1_ftwins'] = wids['plot1_ftwins'].IsChecked() 

1110 form_opts['plot1_paths'] = wids['plot1_paths'].IsChecked() 

1111 form_opts['plot1_op'] = Plot1_Choices[wids['plot1_op'].GetStringSelection()] 

1112 form_opts['plot1_voff'] = wids['plot1_voff'].GetValue() 

1113 

1114 form_opts['plot2_op'] = Plot2_Choices[wids['plot2_op'].GetStringSelection()] 

1115 form_opts['plot2_ftwins'] = wids['plot2_ftwins'].IsChecked() 

1116 form_opts['plot2_paths'] = wids['plot2_paths'].IsChecked() 

1117 form_opts['plot2_voff'] = wids['plot2_voff'].GetValue() 

1118 form_opts['plot2_win'] = int(wids['plot2_win'].GetStringSelection()) 

1119 return form_opts 

1120 

1121 

1122 def fill_model_params(self, prefix, params): 

1123 comp = self.fit_components[prefix] 

1124 parwids = comp.parwids 

1125 for pname, par in params.items(): 

1126 pname = prefix + pname 

1127 if pname in parwids: 

1128 wids = parwids[pname] 

1129 if wids.minval is not None: 

1130 wids.minval.SetValue(par.min) 

1131 if wids.maxval is not None: 

1132 wids.maxval.SetValue(par.max) 

1133 wids.value.SetValue(par.value) 

1134 varstr = 'vary' if par.vary else 'fix' 

1135 if par.expr is not None: 

1136 varstr = 'constrain' 

1137 if wids.vary is not None: 

1138 wids.vary.SetStringSelection(varstr) 

1139 

1140 def onPlot(self, evt=None, dataset_name='_feffit_dataset', 

1141 pargroup_name='_feffit_params', title=None, build_fitmodel=True, 

1142 topwin=None, **kws): 

1143 

1144 dataset = getattr(self.larch.symtable, dataset_name, None) 

1145 if dataset is None: 

1146 dgroup = self.controller.get_group() 

1147 else: 

1148 if dataset.has_data: 

1149 dgroup = dataset.data 

1150 else: 

1151 dgroup = self.controller.get_group() 

1152 # print("no data? dgroup ", dataset.data, dgroup) 

1153 if dgroup is not None: 

1154 self.larch.eval(f"{dataset_name}.set_datagroup({dgroup.groupname})") 

1155 dataset = getattr(self.larch.symtable, dataset_name, None) 

1156 dgroup = dataset.data 

1157 # print("now data now dgroup ", dataset, getattr(dataset, 'data', None), dgroup) 

1158 

1159 opts = self.process(dgroup) 

1160 opts.update(**kws) 

1161 

1162 if build_fitmodel: 

1163 self.build_fitmodel(dgroup, opts=opts) 

1164 

1165 model_name = dataset_name + '.model' 

1166 paths_name = dataset_name + '.paths' 

1167 paths = self.larch.eval(paths_name) 

1168 

1169 data_name = dataset_name + '.data' 

1170 refine_bkg = getattr(dataset, 'refine_bkg', 

1171 opts.get('refine_bkg', False)) 

1172 

1173 if refine_bkg and hasattr(dataset, 'data_rebkg'): 

1174 data_name = dataset_name + '.data_rebkg' 

1175 

1176 exafs_conf = self.parent.get_nbpage('exafs')[1].read_form() 

1177 opts['plot_rmax'] = exafs_conf['plot_rmax'] 

1178 groupname = getattr(dgroup, 'groupname', 'unknown') 

1179 cmds = ["#### plot ", 

1180 f"# build arrays for plotting: refine bkg? {refine_bkg}, {groupname} / {dataset_name}"] 

1181 

1182 kweight = opts['plot_kw'] 

1183 

1184 ftargs = dict(kmin=opts['fit_kmin'], kmax=opts['fit_kmax'], dk=opts['fit_dk'], 

1185 kwindow=opts['fit_kwindow'], kweight=opts['plot_kw'], 

1186 rmin=opts['fit_rmin'], rmax=opts['fit_rmax'], 

1187 dr=opts.get('fit_dr', 0.1), rwindow='hanning') 

1188 

1189 if model_name is not None: 

1190 cmds.append(COMMANDS['xft'].format(groupname=model_name, **ftargs)) 

1191 if data_name is not None: 

1192 cmds.append(COMMANDS['xft'].format(groupname=data_name, **ftargs)) 

1193 

1194 if opts['plot1_paths'] or opts['plot2_paths']: 

1195 cmds.append(COMMANDS['path2chi'].format(paths_name=paths_name, 

1196 pargroup_name=pargroup_name, 

1197 **ftargs)) 

1198 

1199 self.larch_eval('\n'.join(cmds)) 

1200 self.plot_feffit_result(dataset_name, topwin=topwin, ftargs=ftargs, **opts) 

1201 

1202 def plot_feffit_result(self, dataset_name, topwin=None, ftargs=None, **kws): 

1203 

1204 if isValidName(dataset_name): 

1205 dataset = getattr(self.larch.symtable, dataset_name, None) 

1206 else: 

1207 dataset = self.larch.eval(dataset_name) 

1208 

1209 if dataset is None: 

1210 dgroup = self.controller.get_group() 

1211 else: 

1212 dgroup = dataset.data 

1213 data_name = dataset_name + '.data' 

1214 model_name = dataset_name + '.model' 

1215 has_data = getattr(dataset, 'has_data', True) 

1216 

1217 #print("plot_feffit_result/ dgroup, dataset: ", dataset_name, dgroup, dataset, has_data) 

1218 

1219 opts = self.process(dgroup) 

1220 opts.update(**kws) 

1221 title = fname = opts['filename'] 

1222 if title is None: 

1223 title = 'Feff Sum' 

1224 if "'" in title: 

1225 title = title.replace("'", "\\'") 

1226 

1227 needs_qspace = False 

1228 plot1 = opts['plot1_op'] 

1229 plot2 = opts['plot2_op'] 

1230 plot_rmax = opts['plot_rmax'] 

1231 kweight = opts['plot_kw'] 

1232 if ftargs is None: 

1233 ftargs = dict(kmin=opts['fit_kmin'], kmax=opts['fit_kmax'], dk=opts['fit_dk'], 

1234 kwindow=opts['fit_kwindow'], kweight=opts['plot_kw'], 

1235 rmin=opts['fit_rmin'], rmax=opts['fit_rmax'], 

1236 dr=opts.get('fit_dr', 0.1), rwindow='hanning') 

1237 

1238 cmds = [] 

1239 for i, plot in enumerate((plot1, plot2)): 

1240 if plot in Plot2_Choices: 

1241 plot = Plot2_Choices[plot] 

1242 plotwin = 1 

1243 if i == 1: 

1244 if plot in ('noplot', '<no plot>'): 

1245 continue 

1246 else: 

1247 plotwin = int(opts.get('plot2_win', '2')) 

1248 pcmd = 'plot_chir' 

1249 pextra = f', win={plotwin:d}' 

1250 if plot == 'chi': 

1251 pcmd = 'plot_chik' 

1252 pextra += f', kweight={kweight:d}' 

1253 elif plot == 'chir_mag': 

1254 pcmd = 'plot_chir' 

1255 pextra += f', rmax={plot_rmax}' 

1256 elif plot == 'chir_re': 

1257 pextra += f', show_mag=False, show_real=True, rmax={plot_rmax}' 

1258 elif plot == 'chir_mag+chir_re': 

1259 pextra += f', show_mag=True, show_real=True, rmax={plot_rmax}' 

1260 elif plot == 'chiq': 

1261 pcmd = 'plot_chiq' 

1262 pextra += f', show_chik=False' 

1263 needs_qspace = True 

1264 else: 

1265 # print(" do not know how to plot ", plot) 

1266 continue 

1267 with_win = opts[f'plot{i+1}_ftwins'] 

1268 newplot = f', show_window={with_win}, new=True' 

1269 overplot = f', show_window=False, new=False' 

1270 extra = newplot 

1271 if dgroup is not None: 

1272 if has_data: 

1273 cmds.append(f"{pcmd}({data_name:s}, label='data'{pextra}, title='{title}'{extra})") 

1274 extra = overplot 

1275 if dataset.model is not None: 

1276 cmds.append(f"{pcmd}({model_name:s}, label='model'{pextra}{extra})") 

1277 extra = overplot 

1278 elif dataset.model is not None: 

1279 cmds.append(f"{pcmd}({model_name:s}, label='Path sum'{pextra}, title='sum of paths'{extra})") 

1280 extra = overplot 

1281 if opts[f'plot{i+1}_paths']: 

1282 paths_name = dataset_name + '.paths' 

1283 try: 

1284 paths = self._plain_larch_eval(paths_name) 

1285 except: 

1286 paths = {} 

1287 voff = opts[f'plot{i+1}_voff'] 

1288 for i, label in enumerate(paths.keys()): 

1289 if paths[label].use: 

1290 objname = f"{paths_name}['{label:s}']" 

1291 if needs_qspace: 

1292 xpath = paths.get(label) 

1293 if not hasattr(xpath, 'chiq_re'): 

1294 cmds.append(COMMANDS['xft'].format(groupname=objname, **ftargs)) 

1295 

1296 cmds.append(f"{pcmd}({objname}, label='{label:s}'{pextra}, offset={(i+1)*voff}{overplot})") 

1297 

1298 self.larch_eval('\n'.join(cmds)) 

1299 if topwin is not None: 

1300 self.controller.set_focus(topwin=topwin) 

1301 

1302 

1303 def reset_paths(self, event=None): 

1304 "reset paths from _feffpaths" 

1305 self.resetting = True 

1306 def get_pagenames(): 

1307 allpages = [] 

1308 for i in range(self.paths_nb.GetPageCount()): 

1309 allpages.append(self.paths_nb.GetPage(i).__class__.__name__) 

1310 return allpages 

1311 

1312 allpages = get_pagenames() 

1313 t0 = time.time() 

1314 

1315 while 'FeffPathPanel' in allpages: 

1316 for i in range(self.paths_nb.GetPageCount()): 

1317 nbpage = self.paths_nb.GetPage(i) 

1318 if isinstance(nbpage, FeffPathPanel): 

1319 key = self.paths_nb.GetPageText(i) 

1320 self.paths_nb.DeletePage(i) 

1321 allpages = get_pagenames() 

1322 

1323 time.sleep(0.02) 

1324 feffpaths = deepcopy(getattr(self.larch.symtable, '_feffpaths', {})) 

1325 self.paths_data = {} 

1326 for path in feffpaths.values(): 

1327 self.add_path(path.filename, feffpath=path, resize=False) 

1328 

1329 self.get_pathpage('parameters').Rebuild() 

1330 self.resetting = False 

1331 self.params_need_update = True 

1332 

1333 

1334 def add_path(self, filename, pathinfo=None, feffpath=None, resize=True): 

1335 """ add new path to cache """ 

1336 

1337 if pathinfo is None and feffpath is None: 

1338 raise ValueError("add_path needs a Feff Path or Path information") 

1339 self.params_need_update = True 

1340 pfile = Path(filename).absolute() 

1341 fname = pfile.name 

1342 feffrun = pfile.parent.name 

1343 

1344 feffcache = getattr(self.larch.symtable, '_feffcache', None) 

1345 if feffcache is None: 

1346 self.larch_eval(COMMANDS['paths_init']) 

1347 feffcache = getattr(self.larch.symtable, '_feffcache', None) 

1348 if feffcache is None: 

1349 raise ValueError("cannot get feff cache ") 

1350 

1351 geomstre = None 

1352 if pathinfo is not None: 

1353 absorber = pathinfo.absorber 

1354 shell = pathinfo.shell 

1355 reff = float(pathinfo.reff) 

1356 nleg = int(pathinfo.nleg) 

1357 degen = float(pathinfo.degen) 

1358 if hasattr(pathinfo, 'atoms'): 

1359 geom = pathinfo.atoms 

1360 geomstr = pathinfo.geom # '[Fe] > O > [Fe]' 

1361 par_amp = par_e0 = par_delr = par_sigma2 = par_third = par_ei = '' 

1362 

1363 if feffpath is not None: 

1364 absorber = feffpath.absorber 

1365 shell = feffpath.shell 

1366 reff = feffpath.reff 

1367 nleg = feffpath.nleg 

1368 degen = float(feffpath.degen) 

1369 geomstr = [] 

1370 for gdat in feffpath.geom: # ('Fe', 26, 0, 55.845, x, y, z) 

1371 w = gdat[0] 

1372 if gdat[2] == 0: # absorber 

1373 w = '[%s]' % w 

1374 geomstr.append(w) 

1375 geomstr.append(geomstr[0]) 

1376 geomstr = ' > '.join(geomstr) 

1377 par_amp = feffpath.s02 

1378 par_e0 = feffpath.e0 

1379 par_delr = feffpath.deltar 

1380 par_sigma2 = feffpath.sigma2 

1381 par_third = feffpath.third 

1382 par_ei = feffpath.ei 

1383 

1384 try: 

1385 atoms = [s.strip() for s in geomstr.split('>')] 

1386 atoms.pop() 

1387 except: 

1388 title = "Cannot interpret Feff Path data" 

1389 message = [f"Cannot interpret Feff path {filename}"] 

1390 ExceptionPopup(self, title, message) 

1391 

1392 title = '_'.join(atoms) + "%d" % (round(100*reff)) 

1393 for c in ',.[](){}<>+=-?/\\&%$#@!|:;"\'': 

1394 title = title.replace(c, '') 

1395 if title in self.paths_data: 

1396 btitle = title 

1397 i = -1 

1398 while title in self.paths_data: 

1399 i += 1 

1400 title = btitle + '_%s' % string.ascii_lowercase[i] 

1401 

1402 user_label = fix_varname(title) 

1403 self.paths_data[title] = filename 

1404 

1405 ptitle = title 

1406 if ptitle.startswith(absorber): 

1407 ptitle = ptitle[len(absorber):] 

1408 if ptitle.startswith('_'): 

1409 ptitle = ptitle[1:] 

1410 

1411 # set default Path parameters if not supplied already 

1412 if len(par_amp) < 1: 

1413 par_amp = f'{degen:.1f} * s02' 

1414 if len(par_e0) < 1: 

1415 par_e0 = 'e0' 

1416 if len(par_delr) < 1: 

1417 par_delr = f'delr_{ptitle}' 

1418 if len(par_sigma2) < 1: 

1419 par_sigma2 = f'sigma2_{ptitle}' 

1420 

1421 pathpanel = FeffPathPanel(self.paths_nb, self, filename, title, 

1422 user_label, geomstr, absorber, shell, 

1423 reff, nleg, degen, par_amp, par_e0, 

1424 par_delr, par_sigma2, par_third, par_ei) 

1425 

1426 self.paths_nb.AddPage(pathpanel, f' {title:s} ', True) 

1427 

1428 for pname in ('amp', 'e0', 'delr', 'sigma2', 'third', 'ei'): 

1429 pathpanel.onExpression(name=pname) 

1430 

1431 pathpanel.enable_editing() 

1432 

1433 pdat = {'title': title, 'fullpath': filename, 

1434 'feffrun': feffrun, 'use':True} 

1435 pdat.update(pathpanel.get_expressions()) 

1436 

1437 if title not in feffcache['paths']: 

1438 if Path(filename).exists(): 

1439 self.larch_eval(COMMANDS['cache_path'].format(**pdat)) 

1440 else: 

1441 print(f"cannot file Feff data file '{filename}'") 

1442 

1443 self.larch_eval(COMMANDS['use_path'].format(**pdat)) 

1444 if resize: 

1445 sx,sy = self.GetSize() 

1446 self.SetSize((sx, sy+1)) 

1447 self.SetSize((sx, sy)) 

1448 ipage, pagepanel = self.parent.get_nbpage('feffit') 

1449 self.parent.nb.SetSelection(ipage) 

1450 self.parent.Raise() 

1451 

1452 def get_pathkeys(self): 

1453 _feffpaths = getattr(self.larch.symtable, '_feffpaths', {}) 

1454 return [p.hashkey for p in _feffpaths.values()] 

1455 

1456 def get_paramgroup(self): 

1457 pgroup = getattr(self.larch.symtable, '_feffit_params', None) 

1458 if pgroup is None: 

1459 self.larch_eval(COMMANDS['feffit_params_init']) 

1460 pgroup = getattr(self.larch.symtable, '_feffit_params', None) 

1461 if not hasattr(self.larch.symtable, '_feffpaths'): 

1462 self.larch_eval(COMMANDS['paths_init']) 

1463 return pgroup 

1464 

1465 def update_params_for_expr(self, expr=None, value=1.e-3, 

1466 minval=None, maxval=None): 

1467 if expr is None: 

1468 return 

1469 pargroup = self.get_paramgroup() 

1470 symtable = pargroup.__params__._asteval.symtable 

1471 extras= '' 

1472 if minval is not None: 

1473 extras = f', min={minval}' 

1474 if maxval is not None: 

1475 extras = f'{extras}, max={maxval}' 

1476 

1477 try: 

1478 for node in ast.walk(ast.parse(expr)): 

1479 if isinstance(node, ast.Name): 

1480 sym = node.id 

1481 if sym not in symtable and sym not in FEFFDAT_VALUES: 

1482 s = f"_feffit_params.{sym:s} = param({value:.4f}, name='{sym:s}', vary=True{extras:s})" 

1483 self.larch_eval(s) 

1484 result = True 

1485 except: 

1486 result = False 

1487 

1488 self.params_need_update = True 

1489 return result 

1490 

1491 def onLoadFitResult(self, event=None): 

1492 rfile = FileOpen(self, "Load Saved Feffit Model", 

1493 wildcard=ModelWcards) 

1494 if rfile is None: 

1495 return 

1496 

1497 def get_xranges(self, x): 

1498 if self.dgroup is None: 

1499 self.dgroup = self.controller.get_group() 

1500 self.process(self.dgroup) 

1501 opts = self.read_form(self.dgroup) 

1502 dgroup = self.controller.get_group() 

1503 en_eps = min(np.diff(dgroup.energy)) / 5. 

1504 

1505 i1 = index_of(x, opts['emin'] + en_eps) 

1506 i2 = index_of(x, opts['emax'] + en_eps) + 1 

1507 return i1, i2 

1508 

1509 def get_pathpage(self, name): 

1510 "get nb page for a Path by name" 

1511 name = name.lower().strip() 

1512 for i in range(self.paths_nb.GetPageCount()): 

1513 text = self.paths_nb.GetPageText(i).strip().lower() 

1514 if name in text: 

1515 return self.paths_nb.GetPage(i) 

1516 

1517 def build_fitmodel(self, groupname=None, opts=None): 

1518 """ use fit components to build model""" 

1519 paths = [] 

1520 cmds = ["### set up feffit "] 

1521 pargroup = self.get_paramgroup() 

1522 if self.dgroup is None: 

1523 self.dgroup = self.controller.get_group() 

1524 

1525 # self.params_panel.update() 

1526 cmds.extend(self.params_panel.generate_params()) 

1527 

1528 if opts is None: 

1529 opts = self.process(self.dgroup) 

1530 

1531 cmds.append(COMMANDS['feffit_trans'].format(**opts)) 

1532 

1533 path_pages = {} 

1534 for i in range(self.paths_nb.GetPageCount()): 

1535 text = self.paths_nb.GetPageText(i).strip() 

1536 path_pages[text] = self.paths_nb.GetPage(i) 

1537 

1538 _feffpaths = getattr(self.larch.symtable, '_feffpaths', None) 

1539 if _feffpaths is None: 

1540 cmds.append(COMMANDS['paths_init']) 

1541 else: 

1542 cmds.append(COMMANDS['paths_reset']) 

1543 

1544 _feffit_dataset = getattr(self.larch.symtable, '_feffit_dataset', None) 

1545 if _feffit_dataset is None: 

1546 cmds.append(COMMANDS['feffit_dataset_init']) 

1547 

1548 paths_list = [] 

1549 opts['paths'] = [] 

1550 for title, pathdata in self.paths_data.items(): 

1551 if title not in path_pages: 

1552 continue 

1553 pdat = {'title': title, 'fullpath': pathdata[0], 

1554 'feffrun': pathdata[1], 'use':True} 

1555 pdat.update(path_pages[title].get_expressions()) 

1556 

1557 #if pdat['use']: 

1558 cmds.append(COMMANDS['use_path'].format(**pdat)) 

1559 paths_list.append(f"_feffpaths['{title:s}']") 

1560 opts['paths'].append(pdat) 

1561 

1562 paths_string = '[%s]' % (', '.join(paths_list)) 

1563 

1564 gname = opts.get('groupname', None) 

1565 if gname is None: 

1566 cmds.append(COMMANDS['ff2chi_nodata'].format(paths=paths_string, 

1567 trans='_feffit_trans') 

1568 ) 

1569 else: 

1570 cmds.append(COMMANDS['ff2chi'].format(paths=paths_string, 

1571 trans='_feffit_trans', 

1572 groupname=gname, 

1573 refine_bkg=opts['refine_bkg']) 

1574 ) 

1575 cmds.append('# end of build model') 

1576 self.larch_eval("\n".join(cmds)) 

1577 return opts 

1578 

1579 

1580 def get_used_params(self): 

1581 used_syms = [] 

1582 path_pages = {} 

1583 

1584 for i in range(self.paths_nb.GetPageCount()): 

1585 text = self.paths_nb.GetPageText(i).strip() 

1586 path_pages[text] = self.paths_nb.GetPage(i) 

1587 for title in self.paths_data: 

1588 if title not in path_pages: 

1589 continue 

1590 exprs = path_pages[title].get_expressions() 

1591 if exprs['use']: 

1592 for ename, expr in exprs.items(): 

1593 if ename in ('label', 'use'): 

1594 continue 

1595 for node in ast.walk(ast.parse(expr)): 

1596 if isinstance(node, ast.Name): 

1597 sym = node.id 

1598 if sym not in used_syms: 

1599 used_syms.append(sym) 

1600 return used_syms 

1601 

1602 

1603 def skip_unused_params(self): 

1604 # find unused symbols, set to "skip" 

1605 curr_syms = self.get_used_params() 

1606 pargroup = self.get_paramgroup() 

1607 parpanel = self.params_panel 

1608 for pname, par in group2params(pargroup).items(): 

1609 if pname in parpanel.parwids: 

1610 ppar = parpanel.parwids[pname] 

1611 _skip = False 

1612 _str = ppar.vary.GetStringSelection() 

1613 if pname not in curr_syms: 

1614 _skip = True 

1615 _str = 'skip' 

1616 elif (pname in curr_syms and getattr(ppar, 'skip', False)): 

1617 _str = 'vary' 

1618 ppar.skip = ppar.param.skip = par.skip = _skip 

1619 ppar.vary.SetStringSelection(_str) 

1620 ppar.onVaryChoice() 

1621 parpanel.update() 

1622 

1623 def onFitModel(self, event=None, dgroup=None): 

1624 session_history = self.get_session_history() 

1625 nstart = len(session_history) 

1626 

1627 script = [COMMANDS['feffit_top'].format(ctime=time.ctime())] 

1628 

1629 if dgroup is None: 

1630 dgroup = self.controller.get_group() 

1631 opts = self.build_fitmodel(dgroup) 

1632 

1633 # dgroup = opts['datagroup'] 

1634 fopts = dict(groupname=opts['groupname'], 

1635 refine_bkg=bool(opts['refine_bkg']), 

1636 trans='_feffit_trans', 

1637 paths='_feffpaths', 

1638 params='_feffit_params') 

1639 

1640 groupname = opts['groupname'] 

1641 filename = opts['filename'] 

1642 if dgroup is None: 

1643 dgroup = opts['datagroup'] 

1644 

1645 script.append("###\n### DATA \n###\n") 

1646 script.append(COMMANDS['data_source'].format(groupname=groupname, filename=filename)) 

1647 seen_cmds = [] 

1648 cmdhist = [] 

1649 sesshist = get_commands(session_history) 

1650 for cmd in reversed(sesshist): 

1651 if groupname in cmd or filename in cmd or 'athena' in cmd or 'session' in cmd: 

1652 scmd = cmd.strip() 

1653 if (scmd.startswith('#') or scmd.startswith('plot_') 

1654 or 'feffit_dataset(' in scmd or 'feffit_transform(' in scmd): 

1655 continue 

1656 fcn_name, args = scmd.split('(', 1) if '(' in scmd else ('', scmd) 

1657 if fcn_name in ('autobk', 'pre_edge', 'xftf', 'xftr'): 

1658 if fcn_name in seen_cmds: 

1659 continue 

1660 else: 

1661 seen_cmds.append(fcn_name) 

1662 cmdhist.append(f"# {cmd}") 

1663 

1664 script.extend(list(reversed(cmdhist))) 

1665 

1666 script.append("### end of data reading and preparation\n###\n###") 

1667 script.append("## read Feff Paths into '_feffpaths'. You will need to either") 

1668 script.append("## read feff.dat from disk files with `feffpath()` or use Paths") 

1669 script.append("## cached from a session file into `feffcache`") 

1670 script.append("#_feffcache = {'runs': {}, 'paths':{}}") 

1671 script.append("#_feffpaths = {}") 

1672 for path in opts['paths']: 

1673 lab, fname, run = path['title'], path['fullpath'], path['feffrun'] 

1674 amp, e0, delr, sigma2, third, ei = path['amp'], path['e0'], path['delr'], path['sigma2'], path['third'], path['ei'] 

1675 script.append(f"""## Path '{lab}' : ############ 

1676#_feffcache['paths']['{lab}'] = feffpath('{fname}', label='{lab}', feffrun='{run}', degen=1) 

1677#_feffpaths['{lab}'] = use_feffpath(_feffcache['paths'], '{lab}', 

1678# s02='{amp:s}', e0='{e0:s}', deltar='{delr:s}', 

1679# sigma2='{sigma2:s}', third='{third:s}', ei='{ei:s}')""") 

1680 

1681 script.append("###\n###\n###") 

1682 self.larch_eval(COMMANDS['do_feffit'].format(**fopts)) 

1683 

1684 self.wids['show_results'].Enable() 

1685 self.onPlot(dataset_name='_feffit_dataset', 

1686 pargroup_name='_feffit_result.paramgroup', 

1687 build_fitmodel=False) 

1688 

1689 

1690 script.extend(self.get_session_history()[nstart:]) 

1691 script.extend(["print(feffit_report(_feffit_result))", 

1692 "#end of autosaved feffit script" , ""]) 

1693 

1694 if not hasattr(dgroup, 'feffit_history'): 

1695 dgroup.feffit_history = [] 

1696 

1697 

1698 label = now = time.strftime("%b-%d %H:%M") 

1699 if len(dgroup.feffit_history) > 0: 

1700 dgroup.feffit_history[0].commands = script 

1701 dgroup.feffit_history[0].timestamp = time.strftime("%Y-%b-%d %H:%M") 

1702 dgroup.feffit_history[0].label = label 

1703 

1704 fitlabels = [fhist.label for fhist in dgroup.feffit_history[1:]] 

1705 if label in fitlabels: 

1706 count = 1 

1707 while label in fitlabels: 

1708 label = f'{now:s}_{printable[count]:s}' 

1709 count +=1 

1710 dgroup.feffit_history[0].label = label 

1711 

1712 sname = self.autosave_script('\n'.join(script)) 

1713 self.write_message("wrote feffit script to '%s'" % sname) 

1714 

1715 self.show_subframe('feffit_result', FeffitResultFrame, 

1716 datagroup=opts['datagroup'], feffit_panel=self) 

1717 self.subframes['feffit_result'].add_results(dgroup, form=opts) 

1718 

1719 def onShowResults(self, event=None): 

1720 self.show_subframe('feffit_result', FeffitResultFrame, 

1721 feffit_panel=self) 

1722 

1723 def update_start_values(self, params): 

1724 """fill parameters with best fit values""" 

1725 self.params_panel.set_init_values(params) 

1726 for i in range(self.paths_nb.GetPageCount()): 

1727 if 'parameters' in self.paths_nb.GetPageText(i).strip().lower(): 

1728 self.paths_nb.SetSelection(i) 

1729 

1730 def autosave_script(self, text, fname='feffit_script.lar'): 

1731 """autosave model result to user larch folder""" 

1732 confdir = self.controller.larix_folder 

1733 if fname is None: 

1734 fname = 'feffit_script.lar' 

1735 fullpath = Path(confdir, fname) 

1736 fullname = fullpath.as_posix() 

1737 if fullpath.exists(): 

1738 shutil.copy(fullname, Path(confdir, 'feffit_script_BAK.lar').as_posix()) 

1739 with open(fullname, 'w', encoding=sys.getdefaultencoding()) as fh: 

1740 fh.write(text) 

1741 return fullname 

1742 

1743 

1744############### 

1745 

1746class FeffitResultFrame(wx.Frame): 

1747 def __init__(self, parent=None, feffit_panel=None, datagroup=None, **kws): 

1748 wx.Frame.__init__(self, None, -1, title='Feffit Results', 

1749 style=FRAMESTYLE, size=(1000, 700), **kws) 

1750 

1751 self.outforms = {'chik': 'chi(k), no k-weight', 

1752 'chikw': 'chi(k), k-weighted', 

1753 'chir_mag': '|chi(R)|', 

1754 'chir_re': 'Real[chi(R)]', 

1755 'chiq': 'Filtered \u03c7(k)' 

1756 } 

1757 

1758 self.feffit_panel = feffit_panel 

1759 self.datagroup = datagroup 

1760 self.feffit_history = getattr(datagroup, 'fit_history', []) 

1761 self.parent = parent 

1762 self.report_frame = None 

1763 self.datasets = {} 

1764 self.form = {} 

1765 self.larch_eval = feffit_panel.larch_eval 

1766 self.nfit = 0 

1767 self.createMenus() 

1768 self.build() 

1769 

1770 if datagroup is None: 

1771 symtab = self.feffit_panel.larch.symtable 

1772 xasgroups = getattr(symtab, '_xasgroups', None) 

1773 if xasgroups is not None: 

1774 for dname, dgroup in xasgroups.items(): 

1775 dgroup = getattr(symtab, dgroup, None) 

1776 hist = getattr(dgroup, 'feffit_history', None) 

1777 if hist is not None: 

1778 self.add_results(dgroup, show=True) 

1779 

1780 

1781 def createMenus(self): 

1782 self.menubar = wx.MenuBar() 

1783 fmenu = wx.Menu() 

1784 m = {} 

1785 for key, desc in self.outforms.items(): 

1786 MenuItem(self, fmenu, 

1787 f"Save Fit: {desc}", 

1788 f"Save data, model, path arrays as {desc}", 

1789 partial(self.onSaveFit, form=key)) 

1790 

1791 fmenu.AppendSeparator() 

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

1793 self.SetMenuBar(self.menubar) 

1794 

1795 def build(self): 

1796 sizer = wx.GridBagSizer(2, 2) 

1797 sizer.SetVGap(2) 

1798 sizer.SetHGap(2) 

1799 

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

1801 splitter.SetMinimumPaneSize(200) 

1802 

1803 self.filelist = EditableListBox(splitter, self.ShowDataSet, 

1804 size=(250, -1)) 

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

1806 

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

1808 

1809 panel = scrolled.ScrolledPanel(splitter) 

1810 

1811 panel.SetMinSize((725, 575)) 

1812 panel.SetSize((850, 575)) 

1813 

1814 # title row 

1815 self.wids = wids = {} 

1816 title = SimpleText(panel, 'Feffit Results', font=Font(FONTSIZE+2), 

1817 colour=COLORS['title'], style=LEFT) 

1818 

1819 wids['data_title'] = SimpleText(panel, '< > ', font=Font(FONTSIZE+2), 

1820 minsize=(350, -1), 

1821 colour=COLORS['title'], style=LEFT) 

1822 

1823 wids['plot1_op'] = Choice(panel, choices=list(Plot1_Choices.keys()), 

1824 action=self.onPlot, size=(125, -1)) 

1825 wids['plot1_op'].SetSelection(1) 

1826 wids['plot2_op'] = Choice(panel, choices=list(Plot2_Choices.keys()), 

1827 action=self.onPlot, size=(125, -1)) 

1828 

1829 wids['plot2_win'] = Choice(panel, choices=PlotWindowChoices, 

1830 action=self.onPlot, size=(60, -1)) 

1831 wids['plot2_win'].SetStringSelection('2') 

1832 

1833 wids['plot_kw'] = Choice(panel, size=(80, -1), 

1834 choices=['0', '1', '2', '3', '4'], default=2) 

1835 

1836 wids['plot1_paths'] = Check(panel, default=False, label='Plot Each Path', 

1837 action=self.onPlot) 

1838 wids['plot1_ftwins'] = Check(panel, default=False, label='Plot FT Windows', 

1839 action=self.onPlot) 

1840 

1841 wids['plot1_voff'] = FloatSpin(panel, value=0, digits=2, increment=0.25, 

1842 action=self.onPlot, size=(100, -1)) 

1843 wids['plot2_paths'] = Check(panel, default=False, label='Plot Each Path', 

1844 action=self.onPlot) 

1845 wids['plot2_ftwins'] = Check(panel, default=False, label='Plot FT Windows', 

1846 action=self.onPlot) 

1847 

1848 wids['plot2_voff'] = FloatSpin(panel, value=0, digits=2, increment=0.25, 

1849 action=self.onPlot, size=(100, -1)) 

1850 

1851 wids['plot_current'] = Button(panel,'Plot Current Model', 

1852 action=self.onPlot, size=(175, -1)) 

1853 

1854 wids['show_pathpars'] = Button(panel,'Show Path Parameters', 

1855 action=self.onShowPathParams, size=(175, -1)) 

1856 wids['show_script'] = Button(panel,'Show Fit Script', 

1857 action=self.onShowScript, size=(150, -1)) 

1858 

1859 lpanel = wx.Panel(panel) 

1860 wids['fit_label'] = wx.TextCtrl(lpanel, -1, ' ', size=(175, -1)) 

1861 wids['set_label'] = Button(lpanel, 'Update Label', size=(150, -1), 

1862 action=self.onUpdateLabel) 

1863 wids['del_fit'] = Button(lpanel, 'Remove from Fit History', size=(200, -1), 

1864 action=self.onRemoveFromHistory) 

1865 

1866 lsizer = wx.BoxSizer(wx.HORIZONTAL) 

1867 lsizer.Add(wids['fit_label'], 0, 2) 

1868 lsizer.Add(wids['set_label'], 0, 2) 

1869 lsizer.Add(wids['del_fit'], 0, 2) 

1870 pack(lpanel, lsizer) 

1871 

1872 irow = 0 

1873 sizer.Add(title, (irow, 0), (1, 1), LEFT) 

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

1875 

1876 irow += 1 

1877 sizer.Add(wids['plot_current'], (irow, 0), (1, 1), LEFT) 

1878 sizer.Add(wids['plot1_op'], (irow, 1), (1, 1), LEFT) 

1879 sizer.Add(SimpleText(panel, 'k-weight'), (irow, 2), (1, 1), LEFT) 

1880 sizer.Add(wids['plot_kw'], (irow, 3), (1, 1), LEFT) 

1881 irow += 1 

1882 sizer.Add(wids['plot1_ftwins'], (irow, 0), (1, 1), LEFT) 

1883 sizer.Add(wids['plot1_paths'], (irow, 1), (1, 1), LEFT) 

1884 sizer.Add(SimpleText(panel, 'Vertical Offest:'), (irow, 2), (1, 1), LEFT) 

1885 sizer.Add(wids['plot1_voff'], (irow, 3), (1, 1), LEFT) 

1886 

1887 irow += 1 

1888 sizer.Add(SimpleText(panel, 'Add Second Plot:', style=LEFT), (irow, 0), (1, 1), LEFT) 

1889 sizer.Add(wids['plot2_op'], (irow, 1), (1, 1), LEFT) 

1890 sizer.Add(SimpleText(panel, 'Plot Window:', style=LEFT), (irow, 2), (1, 1), LEFT) 

1891 sizer.Add(wids['plot2_win'], (irow, 3), (1, 1), LEFT) 

1892 irow += 1 

1893 sizer.Add(wids['plot2_ftwins'], (irow, 0), (1, 1), LEFT) 

1894 sizer.Add(wids['plot2_paths'], (irow, 1), (1, 1), LEFT) 

1895 sizer.Add(SimpleText(panel, 'Vertical Offest:'), (irow, 2), (1, 1), LEFT) 

1896 sizer.Add(wids['plot2_voff'], (irow, 3), (1, 1), LEFT) 

1897 

1898 irow += 1 

1899 sizer.Add(wids['show_pathpars'], (irow, 0), (1, 1), LEFT) 

1900 sizer.Add(wids['show_script'], (irow, 1), (1, 1), LEFT) 

1901 irow += 1 

1902 sizer.Add(HLine(panel, size=(650, 3)), (irow, 0), (1, 5), LEFT) 

1903 

1904 irow += 1 

1905 sizer.Add(SimpleText(panel, 'Fit Label:', style=LEFT), (irow, 0), (1, 1), LEFT) 

1906 sizer.Add(lpanel, (irow, 1), (1, 4), LEFT) 

1907 

1908 irow += 1 

1909 title = SimpleText(panel, '[[Fit Statistics]]', font=Font(FONTSIZE+2), 

1910 colour=COLORS['title'], style=LEFT) 

1911 subtitle = SimpleText(panel, ' (most recent fit is at the top)', 

1912 font=Font(FONTSIZE+1), style=LEFT) 

1913 

1914 sizer.Add(title, (irow, 0), (1, 1), LEFT) 

1915 sizer.Add(subtitle, (irow, 1), (1, 3), LEFT) 

1916 

1917 sview = self.wids['stats'] = dv.DataViewListCtrl(panel, style=DVSTYLE) 

1918 sview.SetFont(self.font_fixedwidth) 

1919 sview.Bind(dv.EVT_DATAVIEW_SELECTION_CHANGED, self.onSelectFit) 

1920 

1921 xw = (40, 135, 80, 80, 92, 92, 122, 100) 

1922 

1923 sview.AppendTextColumn(' # ', width=xw[0]) 

1924 sview.AppendTextColumn('Label', width=xw[1]) 

1925 sview.AppendTextColumn('Npaths', width=xw[2]) 

1926 sview.AppendTextColumn('Nvary', width=xw[3]) 

1927 sview.AppendTextColumn('Nidp', width=xw[4]) 

1928 sview.AppendTextColumn('\u03c7\u00B2', width=xw[5]) 

1929 sview.AppendTextColumn('reduced \u03c7\u00B2', width=xw[6]) 

1930 sview.AppendTextColumn('R Factor', width=xw[7]) 

1931 

1932 for col in range(sview.ColumnCount): 

1933 this = sview.Columns[col] 

1934 this.Sortable = True 

1935 this.Alignment = wx.ALIGN_RIGHT if col > 1 else wx.ALIGN_LEFT 

1936 this.Renderer.Alignment = this.Alignment 

1937 

1938 sview.SetMinSize((750, 150)) 

1939 

1940 irow += 1 

1941 sizer.Add(sview, (irow, 0), (1, 5), LEFT) 

1942 

1943 irow += 1 

1944 title = SimpleText(panel, '[[Variables]]', font=Font(FONTSIZE+2), 

1945 colour=COLORS['title'], style=LEFT) 

1946 sizer.Add(title, (irow, 0), (1, 1), LEFT) 

1947 

1948 self.wids['copy_params'] = Button(panel, 'Update Model with these values', 

1949 size=(225, -1), action=self.onCopyParams) 

1950 

1951 sizer.Add(self.wids['copy_params'], (irow, 1), (1, 3), LEFT) 

1952 

1953 pview = self.wids['params'] = dv.DataViewListCtrl(panel, style=DVSTYLE) 

1954 pview.SetFont(self.font_fixedwidth) 

1955 self.wids['paramsdata'] = [] 

1956 xw = (180, 140, 150, 250) 

1957 

1958 pview.AppendTextColumn('Parameter', width=xw[0]) 

1959 pview.AppendTextColumn('Best Value', width=xw[1]) 

1960 pview.AppendTextColumn('1-\u03c3 Uncertainty', width=xw[2]) 

1961 pview.AppendTextColumn('Info ', width=xw[3]) 

1962 

1963 for col in range(4): 

1964 this = pview.Columns[col] 

1965 this.Sortable = False 

1966 this.Alignment = wx.ALIGN_RIGHT if col in (1, 2) else wx.ALIGN_LEFT 

1967 this.Renderer.Alignment = this.Alignment 

1968 

1969 pview.SetMinSize((750, 200)) 

1970 pview.Bind(dv.EVT_DATAVIEW_SELECTION_CHANGED, self.onSelectParameter) 

1971 

1972 irow += 1 

1973 sizer.Add(pview, (irow, 0), (1, 5), LEFT) 

1974 

1975 irow += 1 

1976 title = SimpleText(panel, '[[Correlations]]', font=Font(FONTSIZE+2), 

1977 colour=COLORS['title'], style=LEFT) 

1978 

1979 ppanel = wx.Panel(panel) 

1980 ppanel.SetMinSize((450, 20)) 

1981 self.wids['all_correl'] = Button(ppanel, 'Show All', 

1982 size=(100, -1), action=self.onAllCorrel) 

1983 

1984 self.wids['min_correl'] = FloatSpin(ppanel, value=MIN_CORREL, 

1985 min_val=0, size=(100, -1), 

1986 digits=3, increment=0.1) 

1987 

1988 psizer = wx.BoxSizer(wx.HORIZONTAL) 

1989 psizer.Add(SimpleText(ppanel, 'minimum correlation: '), 0, 2) 

1990 psizer.Add(self.wids['min_correl'], 0, 2) 

1991 psizer.Add(self.wids['all_correl'], 0, 2) 

1992 pack(ppanel, psizer) 

1993 

1994 sizer.Add(title, (irow, 0), (1, 1), LEFT) 

1995 sizer.Add(ppanel, (irow, 1), (1, 4), LEFT) 

1996 

1997 cview = self.wids['correl'] = dv.DataViewListCtrl(panel, style=DVSTYLE) 

1998 cview.SetFont(self.font_fixedwidth) 

1999 

2000 cview.AppendTextColumn('Parameter 1', width=150) 

2001 cview.AppendTextColumn('Parameter 2', width=150) 

2002 cview.AppendTextColumn('Correlation', width=150) 

2003 

2004 for col in (0, 1, 2): 

2005 this = cview.Columns[col] 

2006 this.Sortable = False 

2007 align = wx.ALIGN_LEFT 

2008 if col == 2: 

2009 align = wx.ALIGN_RIGHT 

2010 this.Alignment = this.Renderer.Alignment = align 

2011 cview.SetMinSize((550, 150)) 

2012 

2013 irow += 1 

2014 sizer.Add(cview, (irow, 0), (1, 5), LEFT) 

2015 

2016 pack(panel, sizer) 

2017 panel.SetupScrolling() 

2018 

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

2020 

2021 mainsizer = wx.BoxSizer(wx.VERTICAL) 

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

2023 

2024 pack(self, mainsizer) 

2025 self.Show() 

2026 self.Raise() 

2027 

2028 def show_report(self, text, title='Text', default_filename='out.txt', 

2029 wildcard=None): 

2030 if wildcard is None: 

2031 wildcard='Text Files (*.txt)|*.txt' 

2032 try: 

2033 self.report_frame.set_text(text) 

2034 self.report_frame.SetTitle(title) 

2035 self.report_frame.default_filename = default_filename 

2036 self.report_frame.wildcard = wildcard 

2037 except: 

2038 self.report_frame = ReportFrame(parent=self.parent, 

2039 text=text, title=title, 

2040 default_filename=default_filename, 

2041 wildcard=wildcard) 

2042 

2043 def onShowPathParams(self, event=None): 

2044 result = self.get_fitresult() 

2045 if result is None: 

2046 return 

2047 text = f'# Feffit Report for {self.datagroup.filename} fit "{result.label}"\n' 

2048 text = text + feffit_report(result) 

2049 title = f'Report for {self.datagroup.filename} fit "{result.label}"' 

2050 fname = fix_filename(f'{self.datagroup.filename}_{result.label}.txt') 

2051 self.show_report(text, title=title, default_filename=fname) 

2052 

2053 def onShowScript(self, event=None): 

2054 result = self.get_fitresult() 

2055 if result is None: 

2056 return 

2057 text = [f'# Feffit Script for {self.datagroup.filename} fit "{result.label}"'] 

2058 text.extend(result.commands) 

2059 text = '\n'.join(text) 

2060 title = f'Script for {self.datagroup.filename} fit "{result.label}"' 

2061 fname = fix_filename(f'{self.datagroup.filename}_{result.label}.lar') 

2062 self.show_report(text, title=title, default_filename=fname, 

2063 wildcard='Larch/Python Script (*.lar)|*.lar') 

2064 

2065 def onUpdateLabel(self, event=None): 

2066 result = self.get_fitresult() 

2067 if result is None: 

2068 return 

2069 item = self.wids['stats'].GetSelectedRow() 

2070 result.label = self.wids['fit_label'].GetValue() 

2071 self.show_results() 

2072 

2073 def onRemoveFromHistory(self, event=None): 

2074 result = self.get_fitresult() 

2075 if result is None: 

2076 return 

2077 if wx.ID_YES != Popup(self, 

2078 f"Remove fit '{result.label}' from history?\nThis cannot be undone.", 

2079 "Remove fit?", style=wx.YES_NO): 

2080 return 

2081 self.datagroup.feffit_history.pop(self.nfit) 

2082 self.nfit = 0 

2083 self.show_results() 

2084 

2085 def onPlot(self, event=None): 

2086 result = self.get_fitresult() 

2087 if result is None: 

2088 return 

2089 dset = result.datasets[0] 

2090 dgroup = dset.data 

2091 if not hasattr(dset.data, 'rwin'): 

2092 dset._residual(result.params) 

2093 dset.save_outputs() 

2094 trans = dset.transform 

2095 dset.prepare_fit(result.params) 

2096 dset._residual(result.params) 

2097 

2098 opts = self.feffit_panel.read_form(dgroup=dgroup) 

2099 opts = {'build_fitmodel': False} 

2100 

2101 for key, meth in (('plot1_ftwins', 'IsChecked'), 

2102 ('plot2_ftwins', 'IsChecked'), 

2103 ('plot1_paths', 'IsChecked'), 

2104 ('plot2_paths', 'IsChecked'), 

2105 ('plot1_op', 'GetStringSelection'), 

2106 ('plot2_op', 'GetStringSelection'), 

2107 ('plot1_voff', 'GetValue'), 

2108 ('plot2_voff', 'GetValue'), 

2109 ('plot_kw', 'GetStringSelection'), 

2110 ('plot2_win', 'GetStringSelection'), 

2111 ): 

2112 opts[key] = getattr(self.wids[key], meth)() 

2113 

2114 opts['plot1_op'] = Plot1_Choices[opts['plot1_op']] 

2115 opts['plot2_op'] = Plot2_Choices[opts['plot2_op']] 

2116 opts['plot2_win'] = int(opts['plot2_win']) 

2117 opts['plot_kw'] = int(opts['plot_kw']) 

2118 

2119 

2120 result_name = f'{self.datagroup.groupname}.feffit_history[{self.nfit}]' 

2121 opts['label'] = f'{result_name}.label' 

2122 opts['filename'] = self.datagroup.filename 

2123 opts['pargroup_name'] = f'{result_name}.paramgroup' 

2124 opts['title'] = f'{self.datagroup.filename}: {result.label}' 

2125 

2126 for attr in ('kmin', 'kmax', 'dk', 'rmin', 'rmax', 'fitspace'): 

2127 opts[attr] = getattr(trans, attr) 

2128 opts['fit_kwstring'] = "%s" % getattr(trans, 'kweight') 

2129 opts['kwindow'] = getattr(trans, 'window') 

2130 opts['topwin'] = self 

2131 

2132 exafs_conf = self.feffit_panel.parent.get_nbpage('exafs')[1].read_form() 

2133 opts['plot_rmax'] = exafs_conf['plot_rmax'] 

2134 self.feffit_panel.plot_feffit_result(f'{result_name}.datasets[0]', **opts) 

2135 

2136 

2137 def onSaveFitCommand(self, event=None): 

2138 wildcard = 'Larch/Python Script (*.lar)|*.lar|All files (*.*)|*.*' 

2139 result = self.get_fitresult() 

2140 if result is None: 

2141 return 

2142 fname = fix_filename(f'{self.datagroup.filename}_{result.label:s}.lar') 

2143 

2144 path = FileSave(self, message='Save text to file', 

2145 wildcard=wildcard, default_file=fname) 

2146 if path is not None: 

2147 text = '\n'.join(result.commands) 

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

2149 fh.write(text) 

2150 fh.write('') 

2151 

2152 

2153 def onSaveFit(self, evt=None, form='chikw'): 

2154 "Save arrays to text file" 

2155 result = self.get_fitresult() 

2156 if result is None: 

2157 return 

2158 

2159 fname = fix_filename(f'{self.datagroup.filename}_{result.label:s}_{form}') 

2160 fname = fname.replace('.', '_') 

2161 fname = fname + '.txt' 

2162 

2163 wildcard = 'Text Files (*.txt)|*.txt|All files (*.*)|*.*' 

2164 savefile = FileSave(self, 'Save Fit Model (%s)' % form, 

2165 default_file=fname, 

2166 wildcard=wildcard) 

2167 if savefile is None: 

2168 return 

2169 

2170 text = feffit_report(result) 

2171 desc = self.outforms[form] 

2172 buff = [f'# Results for {self.datagroup.filename} "{result.label}": {desc}'] 

2173 

2174 for line in text.split('\n'): 

2175 buff.append('# %s' % line) 

2176 buff.append('## ') 

2177 buff.append('#' + '---'*25) 

2178 

2179 ds0 = result.datasets[0] 

2180 

2181 xname = 'k' if form.startswith('chik') else 'r' 

2182 yname = 'chi' if form.startswith('chik') else form 

2183 yname = 'chiq_re' if form.startswith('chiq') else form 

2184 kw = 0 

2185 if form == 'chikw': 

2186 kw = ds0.transform.kweight 

2187 

2188 xarr = getattr(ds0.data, xname) 

2189 nx = len(xarr) 

2190 ydata = getattr(ds0.data, yname)[:nx] * xarr**kw 

2191 ymodel = getattr(ds0.model, yname)[:nx] * xarr**kw 

2192 out = [xarr, ydata, ymodel] 

2193 

2194 array_names = [xname, 'expdata', 'model'] 

2195 for pname, pgroup in ds0.paths.items(): 

2196 array_names.append(f'feffpath_{pname}') 

2197 out.append(getattr(pgroup, yname)[:nx] * xarr**kw) 

2198 

2199 col_labels = [] 

2200 for a in array_names: 

2201 if len(a) < 13: 

2202 a = (a + ' '*13)[:13] 

2203 col_labels.append(a) 

2204 

2205 buff.append('# ' + ' '.join(col_labels)) 

2206 

2207 for i in range(nx): 

2208 words = [gformat(x[i], 12) for x in out] 

2209 buff.append(' '.join(words)) 

2210 buff.append('') 

2211 

2212 

2213 with open(savefile, 'w', encoding=sys.getdefaultencoding()) as fh: 

2214 fh.write('\n'.join(buff)) 

2215 

2216 def get_fitresult(self, nfit=None): 

2217 if nfit is None: 

2218 nfit = self.nfit 

2219 self.feffit_history = getattr(self.datagroup, 'feffit_history', []) 

2220 self.nfit = max(0, nfit) 

2221 n_hist = len(self.feffit_history) 

2222 if n_hist == 0: 

2223 return None 

2224 if self.nfit > n_hist: 

2225 self.nfit = 0 

2226 return self.feffit_history[self.nfit] 

2227 

2228 

2229 def onSelectFit(self, evt=None): 

2230 if self.wids['stats'] is None: 

2231 return 

2232 item = self.wids['stats'].GetSelectedRow() 

2233 if item > -1: 

2234 self.show_fitresult(nfit=item) 

2235 

2236 def onSelectParameter(self, evt=None): 

2237 if self.wids['params'] is None: 

2238 return 

2239 if not self.wids['params'].HasSelection(): 

2240 return 

2241 item = self.wids['params'].GetSelectedRow() 

2242 pname = self.wids['paramsdata'][item] 

2243 

2244 cormin= self.wids['min_correl'].GetValue() 

2245 self.wids['correl'].DeleteAllItems() 

2246 

2247 result = self.get_fitresult() 

2248 if result is None: 

2249 return 

2250 this = result.params[pname] 

2251 if this.correl is not None: 

2252 sort_correl = sorted(this.correl.items(), key=lambda it: abs(it[1])) 

2253 for name, corval in reversed(sort_correl): 

2254 if abs(corval) > cormin: 

2255 self.wids['correl'].AppendItem((pname, name, "% .4f" % corval)) 

2256 

2257 def onAllCorrel(self, evt=None): 

2258 result = self.get_fitresult() 

2259 if result is None: 

2260 return 

2261 params = result.params 

2262 parnames = list(params.keys()) 

2263 

2264 cormin= self.wids['min_correl'].GetValue() 

2265 correls = {} 

2266 for i, name in enumerate(parnames): 

2267 par = params[name] 

2268 if not par.vary: 

2269 continue 

2270 if hasattr(par, 'correl') and par.correl is not None: 

2271 for name2 in parnames[i+1:]: 

2272 if (name != name2 and name2 in par.correl and 

2273 abs(par.correl[name2]) > cormin): 

2274 correls["%s$$%s" % (name, name2)] = par.correl[name2] 

2275 

2276 sort_correl = sorted(correls.items(), key=lambda it: abs(it[1])) 

2277 sort_correl.reverse() 

2278 

2279 self.wids['correl'].DeleteAllItems() 

2280 

2281 for namepair, corval in sort_correl: 

2282 name1, name2 = namepair.split('$$') 

2283 self.wids['correl'].AppendItem((name1, name2, "% .4f" % corval)) 

2284 

2285 def onCopyParams(self, evt=None): 

2286 result = self.get_fitresult() 

2287 if result is None: 

2288 return 

2289 self.feffit_panel.update_start_values(result.params) 

2290 

2291 def ShowDataSet(self, evt=None): 

2292 dataset = evt.GetString() 

2293 group = self.datasets.get(evt.GetString(), None) 

2294 if group is not None: 

2295 self.show_results(datagroup=group) 

2296 

2297 def add_results(self, dgroup, form=None, larch_eval=None, show=True): 

2298 name = dgroup.filename 

2299 if name not in self.filelist.GetItems(): 

2300 self.filelist.Append(name) 

2301 self.datasets[name] = dgroup 

2302 if show: 

2303 self.show_results(datagroup=dgroup, form=form, larch_eval=larch_eval) 

2304 

2305 def show_results(self, datagroup=None, form=None, larch_eval=None): 

2306 if datagroup is not None: 

2307 self.datagroup = datagroup 

2308 if larch_eval is not None: 

2309 self.larch_eval = larch_eval 

2310 

2311 datagroup = self.datagroup 

2312 self.feffit_history = getattr(self.datagroup, 'feffit_history', []) 

2313 

2314 cur = self.get_fitresult() 

2315 if cur is None: 

2316 return 

2317 wids = self.wids 

2318 wids['stats'].DeleteAllItems() 

2319 for i, res in enumerate(self.feffit_history): 

2320 args = ["%d" % (i+1), res.label, "%.d" % (len(res.datasets[0].paths))] 

2321 for attr in ('nvarys', 'n_independent', 'chi_square', 

2322 'chi2_reduced', 'rfactor'): 

2323 val = getattr(res, attr) 

2324 if isinstance(val, int): 

2325 val = '%d' % val 

2326 elif attr == 'n_independent': 

2327 val = "%.2f" % val 

2328 else: 

2329 val = "%.4f" % val 

2330 # val = gformat(val, 9) 

2331 args.append(val) 

2332 wids['stats'].AppendItem(tuple(args)) 

2333 wids['data_title'].SetLabel(self.datagroup.filename) 

2334 self.show_fitresult(nfit=0) 

2335 

2336 

2337 def show_fitresult(self, nfit=0, datagroup=None): 

2338 if datagroup is not None: 

2339 self.datagroup = datagroup 

2340 

2341 result = self.get_fitresult(nfit=nfit) 

2342 if result is None: 

2343 return 

2344 

2345 path_hashkeys = [] 

2346 for ds in result.datasets: 

2347 path_hashkeys.extend([p.hashkey for p in ds.paths.values()]) 

2348 

2349 wids = self.wids 

2350 wids['fit_label'].SetValue(result.label) 

2351 wids['data_title'].SetLabel(self.datagroup.filename) 

2352 wids['params'].DeleteAllItems() 

2353 wids['paramsdata'] = [] 

2354 for param in reversed(result.params.values()): 

2355 pname = param.name 

2356 if any([pname.endswith('_%s' % phash) for phash in path_hashkeys]): 

2357 continue 

2358 if getattr(param, 'skip', None) not in (False, None): 

2359 continue 

2360 

2361 try: 

2362 val = gformat(param.value, 10) 

2363 except (TypeError, ValueError): 

2364 val = ' ??? ' 

2365 serr = ' N/A ' 

2366 if param.stderr is not None: 

2367 serr = gformat(param.stderr, 10) 

2368 extra = ' ' 

2369 if param.expr is not None: 

2370 extra = '= %s ' % param.expr 

2371 elif not param.vary: 

2372 extra = '(fixed)' 

2373 elif param.init_value is not None: 

2374 extra = '(init=%s)' % gformat(param.init_value, 10) 

2375 

2376 wids['params'].AppendItem((pname, val, serr, extra)) 

2377 wids['paramsdata'].append(pname) 

2378 self.Refresh()