Coverage for /Users/Newville/Codes/xraylarch/larch/wxlib/structure2feff_browser.py: 13%

359 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-11-09 10:08 -0600

1#!/usr/bin/env python 

2""" 

3Read Structure input file, Make Feff input file, and run Feff 

4""" 

5 

6import os 

7import sys 

8import numpy as np 

9np.seterr(all='ignore') 

10 

11import wx 

12import wx.lib.scrolledpanel as scrolled 

13import wx.lib.agw.flatnotebook as fnb 

14 

15from xraydb.chemparser import chemparse 

16from xraydb import atomic_number 

17 

18import larch 

19from larch.xafs import feff8l, feff6l 

20from larch.utils import unixpath, mkdir, read_textfile 

21from larch.utils.strutils import fix_filename, unique_name, strict_ascii 

22from larch.site_config import user_larchdir 

23 

24from larch.wxlib import (LarchFrame, FloatSpin, EditableListBox, 

25 FloatCtrl, SetTip, get_icon, SimpleText, pack, 

26 Button, Popup, HLine, FileSave, FileOpen, Choice, 

27 Check, MenuItem, CEN, LEFT, FRAMESTYLE, 

28 Font, FONTSIZE, flatnotebook, LarchUpdaterDialog, 

29 PeriodicTablePanel, FeffResultsPanel, LarchWxApp, 

30 ExceptionPopup, set_color) 

31 

32 

33from larch.xrd import structure2feff 

34 

35LEFT = wx.ALIGN_LEFT 

36CEN |= wx.ALL 

37FNB_STYLE = fnb.FNB_NO_X_BUTTON|fnb.FNB_SMART_TABS 

38FNB_STYLE |= fnb.FNB_NO_NAV_BUTTONS|fnb.FNB_NODRAG 

39 

40MAINSIZE = (850, 750) 

41 

42class Structure2FeffFrame(wx.Frame): 

43 _about = """Larch structure browser for generating and running Feff. 

44 

45 Ryuichi Shimogawa <ryuichi.shimogawa@stonybrook.edu> 

46 """ 

47 def __init__(self, parent=None, _larch=None, path_importer=None, filename=None, **kws): 

48 wx.Frame.__init__(self, parent, -1, size=MAINSIZE, style=FRAMESTYLE) 

49 

50 title = "Larch FEFF Input Generator and FEFF Runner" 

51 

52 self.larch = _larch 

53 if _larch is None: 

54 self.larch = larch.Interpreter() 

55 self.larch.eval("# started Structure browser\n") 

56 self.larch.eval("if not hasattr('_sys', '_feffruns'): _sys._feffruns = {}") 

57 self.path_importer = path_importer 

58 self.subframes = {} 

59 self.current_structure = None 

60 self.SetTitle(title) 

61 self.SetSize(MAINSIZE) 

62 self.SetFont(Font(FONTSIZE)) 

63 self.createMainPanel() 

64 self.createMenus() 

65 

66 self.feff_folder = unixpath(os.path.join(user_larchdir, 'feff')) 

67 mkdir(self.feff_folder) 

68 

69 self.runs_list = [] 

70 for fname in os.listdir(self.feff_folder): 

71 full = os.path.join(self.feff_folder, fname) 

72 if os.path.isdir(full): 

73 self.runs_list.append(fname) 

74 

75 self.statusbar = self.CreateStatusBar(2, style=wx.STB_DEFAULT_STYLE) 

76 self.statusbar.SetStatusWidths([-3, -1]) 

77 statusbar_fields = [" ", ""] 

78 for i in range(len(statusbar_fields)): 

79 self.statusbar.SetStatusText(statusbar_fields[i], i) 

80 self.Show() 

81 

82 def createMainPanel(self): 

83 display0 = wx.Display(0) 

84 client_area = display0.ClientArea 

85 xmin, ymin, xmax, ymax = client_area 

86 xpos = int((xmax-xmin)*0.07) + xmin 

87 ypos = int((ymax-ymin)*0.09) + ymin 

88 self.SetPosition((xpos, ypos)) 

89 

90 # main panel with scrolled panel 

91 scrolledpanel = scrolled.ScrolledPanel(self) 

92 panel = wx.Panel(scrolledpanel) 

93 sizer = wx.GridBagSizer(2,2) 

94 

95 wids = self.wids = {} 

96 

97 folderlab = SimpleText(panel, ' Feff Folder: ') 

98 wids['run_folder'] = wx.TextCtrl(panel, value='calc1', size=(250, -1)) 

99 

100 wids['run_feff'] = Button(panel, ' Run Feff ', 

101 action=self.onRunFeff) 

102 wids['run_feff'].Disable() 

103 wids['without_h'] = Check(panel, default=True, label='Remove H atoms', 

104 action=self.onGetFeff) 

105 

106 

107 wids['central_atom'] = Choice(panel, choices=['<empty>'], size=(80, -1), 

108 action=self.onCentralAtom) 

109 wids['edge'] = Choice(panel, choices=['K', 'L3', 'L2', 'L1', 

110 'M5', 'M4'], 

111 size=(80, -1), 

112 action=self.onGetFeff) 

113 

114 wids['feffvers'] = Choice(panel, choices=['6', '8'], default=1, 

115 size=(80, -1), 

116 action=self.onGetFeff) 

117 wids['site'] = Choice(panel, choices=['1', '2', '3', '4'], 

118 size=(80, -1), 

119 action=self.onGetFeff) 

120 wids['cluster_size'] = FloatSpin(panel, value=7.0, digits=2, 

121 increment=0.1, max_val=10, 

122 action=self.onGetFeff) 

123 wids['central_atom'].Disable() 

124 wids['edge'].Disable() 

125 wids['cluster_size'].Disable() 

126 catomlab = SimpleText(panel, ' Absorbing Atom: ') 

127 sitelab = SimpleText(panel, ' Crystal Site: ') 

128 edgelab = SimpleText(panel, ' Edge: ') 

129 csizelab = SimpleText(panel, ' Cluster Size (\u212B): ') 

130 fverslab = SimpleText(panel, ' Feff Version:') 

131 

132 ir = 1 

133 

134 sizer.Add(catomlab, (ir, 0), (1, 1), LEFT, 3) 

135 sizer.Add(wids['central_atom'], (ir, 1), (1, 1), LEFT, 3) 

136 sizer.Add(sitelab, (ir, 2), (1, 1), LEFT, 3) 

137 sizer.Add(wids['site'], (ir, 3), (1, 1), LEFT, 3) 

138 sizer.Add(edgelab, (ir, 4), (1, 1), LEFT, 3) 

139 sizer.Add(wids['edge'], (ir, 5), (1, 1), LEFT, 3) 

140 

141 ir += 1 

142 sizer.Add(csizelab, (ir, 0), (1, 1), LEFT, 3) 

143 sizer.Add(wids['cluster_size'], (ir, 1), (1, 1), LEFT, 3) 

144 sizer.Add(fverslab, (ir, 2), (1, 1), LEFT, 3) 

145 sizer.Add(wids['feffvers'], (ir, 3), (1, 1), LEFT, 3) 

146 sizer.Add(wids['without_h'], (ir, 4), (1, 2), LEFT, 3) 

147 

148 ir += 1 

149 sizer.Add(folderlab, (ir, 0), (1, 1), LEFT, 3) 

150 sizer.Add(wids['run_folder'], (ir, 1), (1, 4), LEFT, 3) 

151 sizer.Add(wids['run_feff'], (ir, 5), (1, 1), LEFT, 3) 

152 

153 pack(panel, sizer) 

154 

155 self.nb = flatnotebook(scrolledpanel, {}, on_change=self.onNBChanged) 

156 

157 self.feffresults = FeffResultsPanel(scrolledpanel, 

158 path_importer=self.path_importer, 

159 _larch=self.larch) 

160 

161 structure_panel = wx.Panel(scrolledpanel) 

162 wids['structure_text'] = wx.TextCtrl(structure_panel, value='<STRUCTURE TEXT>', 

163 style=wx.TE_MULTILINE|wx.TE_READONLY, 

164 size=(300, 350)) 

165 wids['structure_text'].SetFont(Font(FONTSIZE+1)) 

166 structure_sizer = wx.BoxSizer(wx.VERTICAL) 

167 structure_sizer.Add(wids['structure_text'], 1, LEFT|wx.GROW, 1) 

168 pack(structure_panel, structure_sizer) 

169 

170 feff_panel = wx.Panel(scrolledpanel) 

171 wids['feff_text'] = wx.TextCtrl(feff_panel, 

172 value='<Feff Input Text>', 

173 style=wx.TE_MULTILINE, 

174 size=(300, 350)) 

175 wids['feff_text'].CanCopy() 

176 

177 feff_panel.onPanelExposed = self.onGetFeff 

178 wids['feff_text'].SetFont(Font(FONTSIZE+1)) 

179 feff_sizer = wx.BoxSizer(wx.VERTICAL) 

180 feff_sizer.Add(wids['feff_text'], 1, LEFT|wx.GROW, 1) 

181 pack(feff_panel, feff_sizer) 

182 

183 feffout_panel = wx.Panel(scrolledpanel) 

184 wids['feffout_text'] = wx.TextCtrl(feffout_panel, 

185 value='<Feff Output>', 

186 style=wx.TE_MULTILINE, 

187 size=(300, 350)) 

188 wids['feffout_text'].CanCopy() 

189 wids['feffout_text'].SetFont(Font(FONTSIZE+1)) 

190 feffout_sizer = wx.BoxSizer(wx.VERTICAL) 

191 feffout_sizer.Add(wids['feffout_text'], 1, LEFT|wx.GROW, 1) 

192 pack(feffout_panel, feffout_sizer) 

193 

194 self.nbpages = [] 

195 for label, page in (('Structure Text', structure_panel), 

196 ('Feff Input Text', feff_panel), 

197 ('Feff Output Text', feffout_panel), 

198 ('Feff Results', self.feffresults), 

199 ): 

200 self.nb.AddPage(page, label, True) 

201 self.nbpages.append((label, page)) 

202 self.nb.SetSelection(0) 

203 

204 r_sizer = wx.BoxSizer(wx.VERTICAL) 

205 r_sizer.Add(panel, 0, LEFT|wx.GROW|wx.ALL) 

206 r_sizer.Add(self.nb, 1, LEFT|wx.GROW, 2) 

207 pack(scrolledpanel, r_sizer) 

208 scrolledpanel.SetupScrolling() 

209 

210 def get_nbpage(self, name): 

211 "get nb page by name" 

212 name = name.lower() 

213 for i, dat in enumerate(self.nbpages): 

214 label, page = dat 

215 if name in label.lower(): 

216 return i, page 

217 return (0, self.npbages[0][1]) 

218 

219 def onCentralAtom(self, event=None): 

220 structure = self.current_structure 

221 if structure is None: 

222 return 

223 catom = event.GetString() 

224 try: 

225 sites = structure2feff.structure_sites(structure['structure_text'], absorber=catom, fmt=structure['fmt']) 

226 sites = ['%d' % (i+1) for i in range(len(sites))] 

227 self.wids['site'].Clear() 

228 self.wids['site'].AppendItems(sites) 

229 self.wids['site'].Select(0) 

230 except: 

231 self.write_message(f"could not get sites for central atom '{catom}'") 

232 title = f"Could not get sites for central atom '{catom}'" 

233 message = [] 

234 ExceptionPopup(self, title, message) 

235 

236 edge_val = 'K' if atomic_number(catom) < 60 else 'L3' 

237 self.wids['edge'].SetStringSelection(edge_val) 

238 self.onGetFeff() 

239 

240 def onGetFeff(self, event=None): 

241 structure = self.current_structure 

242 if structure is None: 

243 return 

244 edge = self.wids['edge'].GetStringSelection() 

245 version8 = '8' == self.wids['feffvers'].GetStringSelection() 

246 catom = self.wids['central_atom'].GetStringSelection() 

247 asite = int(self.wids['site'].GetStringSelection()) 

248 csize = self.wids['cluster_size'].GetValue() 

249 with_h = not self.wids['without_h'].IsChecked() 

250 folder = f'{catom:s}{asite:d}_{edge:s}' 

251 folder = unique_name(fix_filename(folder), self.runs_list) 

252 

253 fefftext = structure2feff.structure2feffinp(structure['structure_text'], catom, edge=edge, 

254 cluster_size=csize, 

255 absorber_site=asite, 

256 version8=version8, 

257 with_h=with_h, 

258 fmt=structure['fmt']) 

259 

260 self.wids['run_folder'].SetValue(folder) 

261 self.wids['feff_text'].SetValue(fefftext) 

262 self.wids['run_feff'].Enable() 

263 i, p = self.get_nbpage('Feff Input') 

264 self.nb.SetSelection(i) 

265 

266 def onRunFeff(self, event=None): 

267 fefftext = self.wids['feff_text'].GetValue() 

268 if len(fefftext) < 100 or 'ATOMS' not in fefftext: 

269 return 

270 

271 structure_text = self.wids['structure_text'].GetValue() 

272 structure = self.current_structure 

273 structure_fname = None 

274 

275 if structure is not None: 

276 structure_fname = structure['fname'] 

277 

278 version8 = '8' == self.wids['feffvers'].GetStringSelection() 

279 

280 fname = self.wids['run_folder'].GetValue() 

281 fname = unique_name(fix_filename(fname), self.runs_list) 

282 self.runs_list.append(fname) 

283 folder = unixpath(os.path.join(self.feff_folder, fname)) 

284 mkdir(folder) 

285 

286 ix, p = self.get_nbpage('Feff Output') 

287 self.nb.SetSelection(ix) 

288 

289 self.folder = folder 

290 out = self.wids['feffout_text'] 

291 out.Clear() 

292 out.SetInsertionPoint(0) 

293 out.WriteText(f'########\n###\n# Run Feff in folder: {folder:s}\n') 

294 out.SetInsertionPoint(out.GetLastPosition()) 

295 out.WriteText('###\n########\n') 

296 out.SetInsertionPoint(out.GetLastPosition()) 

297 

298 fname = unixpath(os.path.join(folder, 'feff.inp')) 

299 with open(fname, 'w', encoding=sys.getdefaultencoding()) as fh: 

300 fh.write(strict_ascii(fefftext)) 

301 

302 if structure_fname is not None: 

303 cname = unixpath(os.path.join(folder, structure_fname)) 

304 with open(cname, 'w', encoding=sys.getdefaultencoding()) as fh: 

305 fh.write(strict_ascii(structure_text)) 

306 

307 wx.CallAfter(self.run_feff, folder, version8=version8) 

308 

309 def run_feff(self, folder=None, version8=True): 

310 _, dname = os.path.split(folder) 

311 prog, cmd = feff8l, 'feff8l' 

312 if not version8: 

313 prog, cmd = feff6l, 'feff6l' 

314 command = f"{cmd:s}(folder='{folder:s}')" 

315 self.larch.eval(f"## running Feff as:\n# {command:s}\n##\n") 

316 

317 prog(folder=folder, message_writer=self.feff_output) 

318 self.larch.eval("## gathering results:\n") 

319 self.larch.eval(f"_sys._feffruns['{dname:s}'] = get_feff_pathinfo('{folder:s}')") 

320 this_feffrun = self.larch.symtable._sys._feffruns[f'{dname:s}'] 

321 self.feffresults.set_feffresult(this_feffrun) 

322 ix, p = self.get_nbpage('Feff Results') 

323 self.nb.SetSelection(ix) 

324 

325 # clean up unused, intermediate Feff files 

326 for fname in os.listdir(folder): 

327 if (fname.endswith('.json') or fname.endswith('.pad') or 

328 fname.endswith('.bin') or fname.startswith('log') or 

329 fname in ('chi.dat', 'xmu.dat', 'misc.dat')): 

330 os.unlink(unixpath(os.path.join(folder, fname))) 

331 

332 def feff_output(self, text): 

333 out = self.wids['feffout_text'] 

334 ix, p = self.get_nbpage('Feff Output') 

335 self.nb.SetSelection(ix) 

336 pos0 = out.GetLastPosition() 

337 if not text.endswith('\n'): 

338 text = '%s\n' % text 

339 out.WriteText(text) 

340 out.SetInsertionPoint(out.GetLastPosition()) 

341 out.Update() 

342 out.Refresh() 

343 

344 def onExportFeff(self, event=None): 

345 if self.current_structure is None: 

346 return 

347 fefftext = self.wids['feff_text'].GetValue() 

348 if len(fefftext) < 20: 

349 return 

350 cc = self.current_structure 

351 fname = f'{cc["fname"]}_feff.inp' 

352 wildcard = 'Feff Inut files (*.inp)|*.inp|All files (*.*)|*.*' 

353 path = FileSave(self, message='Save Feff File', 

354 wildcard=wildcard, 

355 default_file=fname) 

356 if path is not None: 

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

358 fh.write(fefftext) 

359 self.write_message("Wrote Feff file %s" % path, 0) 

360 

361 def onExportStructure(self, event=None): 

362 if self.current_structure is None: 

363 return 

364 

365 cc = self.current_structure 

366 fname = cc["fname"] 

367 wildcard = f'Sturcture files (*.{cc["fmt"]})|*.{cc["fmt"]}|All files (*.*)|*.*' 

368 path = FileSave(self, message='Save Structure File', 

369 wildcard=wildcard, 

370 default_file=fname) 

371 

372 if path is not None: 

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

374 fh.write(cc['structure_text']) 

375 self.write_message("Wrote structure file %s" % path, 0) 

376 

377 def onImportStructure(self, event=None): 

378 wildcard = 'Strucuture files (*.cif/*.postcar/*.contcar/*.chgcar/*locpot/*.cssr)|*.cif;*.postcar;*.contcar;*.chgcar;*locpot;*.cssr|Molecule files (*.xyz/*.gjf/*.g03/*.g09/*.com/*.inp)|*.xyz;*.gjf;*.g03;*.g09;*.com;*.inp|All other files readable with Openbabel (*.*)|*.*' 

379 path = FileOpen(self, message='Open Structure File', 

380 wildcard=wildcard, default_file='My.cif') 

381 

382 if path is not None: 

383 

384 fmt = path.split('.')[-1] 

385 fname = os.path.basename(path) 

386 

387 with open(path, 'r', encoding=sys.getdefaultencoding()) as f: 

388 structure_text = f.read() 

389 

390 

391 self.current_structure = structure2feff.parse_structure(structure_text=structure_text, fmt=fmt, fname=fname) 

392 

393 self.wids['structure_text'].SetValue(self.current_structure['structure_text']) 

394 

395 # use pytmatgen to get formula 

396 elems = chemparse(self.current_structure['formula'].replace(' ', '')) 

397 

398 self.wids['central_atom'].Enable() 

399 self.wids['edge'].Enable() 

400 self.wids['cluster_size'].Enable() 

401 

402 self.wids['central_atom'].Clear() 

403 self.wids['central_atom'].AppendItems(list(elems.keys())) 

404 self.wids['central_atom'].Select(0) 

405 

406 

407 

408 el0 = list(elems.keys())[0] 

409 edge_val = 'K' if atomic_number(el0) < 60 else 'L3' 

410 self.wids['edge'].SetStringSelection(edge_val) 

411 

412 # sites 

413 sites = structure2feff.structure_sites(self.current_structure['structure_text'], fmt=self.current_structure["fmt"], absorber=el0) 

414 try: 

415 sites = ['%d' % (i+1) for i in range(len(sites))] 

416 except: 

417 title = "Could not make sense of atomic sites" 

418 message = [f"Elements: {list(elems.keys())}", 

419 f"Sites: {sites}"] 

420 ExceptionPopup(self, title, message) 

421 

422 

423 self.wids['site'].Clear() 

424 self.wids['site'].AppendItems(sites) 

425 self.wids['site'].Select(0) 

426 i, p = self.get_nbpage('Structure Text') 

427 self.nb.SetSelection(i) 

428 

429 def onImportFeff(self, event=None): 

430 wildcard = 'Feff input files (*.inp)|*.inp|All files (*.*)|*.*' 

431 path = FileOpen(self, message='Open Feff Input File', 

432 wildcard=wildcard, default_file='feff.inp') 

433 if path is not None: 

434 fefftext = None 

435 _, fname = os.path.split(path) 

436 fname = fname.replace('.inp', '_run') 

437 fname = unique_name(fix_filename(fname), self.runs_list) 

438 fefftext = read_textfile(path) 

439 if fefftext is not None: 

440 self.wids['feff_text'].SetValue(fefftext) 

441 self.wids['run_folder'].SetValue(fname) 

442 self.wids['run_feff'].Enable() 

443 i, p = self.get_nbpage('Feff Input') 

444 self.nb.SetSelection(i) 

445 

446 def onFeffFolder(self, eventa=None): 

447 "prompt for Feff Folder" 

448 dlg = wx.DirDialog(self, 'Select Main Folder for Feff Calculations', 

449 style=wx.DD_DEFAULT_STYLE|wx.DD_CHANGE_DIR) 

450 

451 dlg.SetPath(self.feff_folder) 

452 if dlg.ShowModal() == wx.ID_CANCEL: 

453 return None 

454 self.feff_folder = os.path.abspath(dlg.GetPath()) 

455 mkdir(self.feff_folder) 

456 

457 def onNBChanged(self, event=None): 

458 callback = getattr(self.nb.GetCurrentPage(), 'onPanelExposed', None) 

459 if callable(callback): 

460 callback() 

461 

462 def onSelAll(self, event=None): 

463 self.controller.filelist.select_all() 

464 

465 def onSelNone(self, event=None): 

466 self.controller.filelist.select_none() 

467 

468 def write_message(self, msg, panel=0): 

469 """write a message to the Status Bar""" 

470 self.statusbar.SetStatusText(msg, panel) 

471 

472 def createMenus(self): 

473 self.menubar = wx.MenuBar() 

474 fmenu = wx.Menu() 

475 group_menu = wx.Menu() 

476 data_menu = wx.Menu() 

477 ppeak_menu = wx.Menu() 

478 m = {} 

479 

480 MenuItem(self, fmenu, "&Open Structure File\tCtrl+O", 

481 "Open Structure File", self.onImportStructure) 

482 

483 MenuItem(self, fmenu, "&Save Structure File\tCtrl+S", 

484 "Save Structure File", self.onExportStructure) 

485 

486 MenuItem(self, fmenu, "Open Feff Input File", 

487 "Open Feff input File", self.onImportFeff) 

488 

489 MenuItem(self, fmenu, "Save &Feff Inp File\tCtrl+F", 

490 "Save Feff6 File", self.onExportFeff) 

491 

492 fmenu.AppendSeparator() 

493 MenuItem(self, fmenu, "Select Main Feff Folder", 

494 "Select Main Folder for running Feff", 

495 self.onFeffFolder) 

496 fmenu.AppendSeparator() 

497 MenuItem(self, fmenu, "Quit", "Exit", self.onClose) 

498 

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

500 

501 self.SetMenuBar(self.menubar) 

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

503 

504 def onClose(self, event=None): 

505 self.Destroy() 

506 

507 

508class Structure2FeffViewer(LarchWxApp): 

509 def __init__(self, filename=None, version_info=None, **kws): 

510 self.filename = filename 

511 LarchWxApp.__init__(self, version_info=version_info, **kws) 

512 

513 def createApp(self): 

514 frame = Structure2FeffFrame(filename=self.filename, 

515 version_info=self.version_info) 

516 self.SetTopWindow(frame) 

517 return True 

518 

519def structure_viewer(**kws): 

520 Structure2FeffViewer(**kws) 

521 

522if __name__ == '__main__': 

523 Structure2FeffViewer().MainLoop()