Coverage for larch/wxlib/structure2feff_browser.py: 13%

359 statements  

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

1#!/usr/bin/env python 

2""" 

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

4""" 

5 

6import os 

7import sys 

8from pathlib import Path 

9import numpy as np 

10np.seterr(all='ignore') 

11 

12import wx 

13import wx.lib.scrolledpanel as scrolled 

14import wx.lib.agw.flatnotebook as fnb 

15 

16from xraydb.chemparser import chemparse 

17from xraydb import atomic_number 

18 

19import larch 

20from larch.xafs import feff8l, feff6l 

21from larch.utils import unixpath, mkdir, read_textfile 

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

23from larch.site_config import user_larchdir 

24 

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

26 FloatCtrl, SetTip, get_icon, SimpleText, pack, 

27 Button, Popup, HLine, FileSave, FileOpen, Choice, 

28 Check, MenuItem, CEN, LEFT, FRAMESTYLE, 

29 Font, FONTSIZE, flatnotebook, LarchUpdaterDialog, 

30 PeriodicTablePanel, FeffResultsPanel, LarchWxApp, 

31 ExceptionPopup, set_color) 

32 

33 

34from larch.xrd import structure2feff 

35 

36LEFT = wx.ALIGN_LEFT 

37CEN |= wx.ALL 

38FNB_STYLE = fnb.FNB_NO_X_BUTTON|fnb.FNB_SMART_TABS 

39FNB_STYLE |= fnb.FNB_NO_NAV_BUTTONS|fnb.FNB_NODRAG 

40 

41MAINSIZE = (850, 750) 

42 

43class Structure2FeffFrame(wx.Frame): 

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

45 

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

47 """ 

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

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

50 

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

52 

53 self.larch = _larch 

54 if _larch is None: 

55 self.larch = larch.Interpreter() 

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

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

58 self.path_importer = path_importer 

59 self.subframes = {} 

60 self.current_structure = None 

61 self.SetTitle(title) 

62 self.SetSize(MAINSIZE) 

63 self.SetFont(Font(FONTSIZE)) 

64 self.createMainPanel() 

65 self.createMenus() 

66 

67 self.feff_folder = Path(user_larchdir, 'feff').as_posix() 

68 mkdir(self.feff_folder) 

69 

70 self.runs_list = [] 

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

72 full = Path(self.feff_folder, fname).absolute() 

73 if full.is_dir(): 

74 self.runs_list.append(full.name) 

75 

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

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

78 statusbar_fields = [" ", ""] 

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

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

81 self.Show() 

82 

83 def createMainPanel(self): 

84 display0 = wx.Display(0) 

85 client_area = display0.ClientArea 

86 xmin, ymin, xmax, ymax = client_area 

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

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

89 self.SetPosition((xpos, ypos)) 

90 

91 # main panel with scrolled panel 

92 scrolledpanel = scrolled.ScrolledPanel(self) 

93 panel = wx.Panel(scrolledpanel) 

94 sizer = wx.GridBagSizer(2,2) 

95 

96 wids = self.wids = {} 

97 

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

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

100 

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

102 action=self.onRunFeff) 

103 wids['run_feff'].Disable() 

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

105 action=self.onGetFeff) 

106 

107 

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

109 action=self.onCentralAtom) 

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

111 'M5', 'M4'], 

112 size=(80, -1), 

113 action=self.onGetFeff) 

114 

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

116 size=(80, -1), 

117 action=self.onGetFeff) 

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

119 size=(80, -1), 

120 action=self.onGetFeff) 

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

122 increment=0.1, max_val=10, 

123 action=self.onGetFeff) 

124 wids['central_atom'].Disable() 

125 wids['edge'].Disable() 

126 wids['cluster_size'].Disable() 

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

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

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

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

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

132 

133 ir = 1 

134 

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

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

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

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

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

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

141 

142 ir += 1 

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

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

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

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

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

148 

149 ir += 1 

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

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

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

153 

154 pack(panel, sizer) 

155 

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

157 

158 self.feffresults = FeffResultsPanel(scrolledpanel, 

159 path_importer=self.path_importer, 

160 _larch=self.larch) 

161 

162 structure_panel = wx.Panel(scrolledpanel) 

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

164 style=wx.TE_MULTILINE|wx.TE_READONLY, 

165 size=(300, 350)) 

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

167 structure_sizer = wx.BoxSizer(wx.VERTICAL) 

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

169 pack(structure_panel, structure_sizer) 

170 

171 feff_panel = wx.Panel(scrolledpanel) 

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

173 value='<Feff Input Text>', 

174 style=wx.TE_MULTILINE, 

175 size=(300, 350)) 

176 wids['feff_text'].CanCopy() 

177 

178 feff_panel.onPanelExposed = self.onGetFeff 

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

180 feff_sizer = wx.BoxSizer(wx.VERTICAL) 

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

182 pack(feff_panel, feff_sizer) 

183 

184 feffout_panel = wx.Panel(scrolledpanel) 

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

186 value='<Feff Output>', 

187 style=wx.TE_MULTILINE, 

188 size=(300, 350)) 

189 wids['feffout_text'].CanCopy() 

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

191 feffout_sizer = wx.BoxSizer(wx.VERTICAL) 

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

193 pack(feffout_panel, feffout_sizer) 

194 

195 self.nbpages = [] 

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

197 ('Feff Input Text', feff_panel), 

198 ('Feff Output Text', feffout_panel), 

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

200 ): 

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

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

203 self.nb.SetSelection(0) 

204 

205 r_sizer = wx.BoxSizer(wx.VERTICAL) 

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

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

208 pack(scrolledpanel, r_sizer) 

209 scrolledpanel.SetupScrolling() 

210 

211 def get_nbpage(self, name): 

212 "get nb page by name" 

213 name = name.lower() 

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

215 label, page = dat 

216 if name in label.lower(): 

217 return i, page 

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

219 

220 def onCentralAtom(self, event=None): 

221 structure = self.current_structure 

222 if structure is None: 

223 return 

224 catom = event.GetString() 

225 try: 

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

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

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

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

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

231 except: 

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

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

234 message = [] 

235 ExceptionPopup(self, title, message) 

236 

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

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

239 self.onGetFeff() 

240 

241 def onGetFeff(self, event=None): 

242 structure = self.current_structure 

243 if structure is None: 

244 return 

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

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

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

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

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

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

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

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

253 

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

255 cluster_size=csize, 

256 absorber_site=asite, 

257 version8=version8, 

258 with_h=with_h, 

259 fmt=structure['fmt']) 

260 

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

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

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

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

265 self.nb.SetSelection(i) 

266 

267 def onRunFeff(self, event=None): 

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

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

270 return 

271 

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

273 structure = self.current_structure 

274 structure_fname = None 

275 

276 if structure is not None: 

277 structure_fname = structure['fname'] 

278 

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

280 

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

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

283 self.runs_list.append(fname) 

284 folder = Path(self.feff_folder, fname).absolute() 

285 mkdir(folder) 

286 

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

288 self.nb.SetSelection(ix) 

289 

290 self.folder = folder.as_posix() 

291 out = self.wids['feffout_text'] 

292 out.Clear() 

293 out.SetInsertionPoint(0) 

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

295 out.SetInsertionPoint(out.GetLastPosition()) 

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

297 out.SetInsertionPoint(out.GetLastPosition()) 

298 

299 fname = Path(folder, 'feff.inp').absolute() 

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

301 fh.write(strict_ascii(fefftext)) 

302 

303 if structure_fname is not None: 

304 cname = Path(folder, structure_fname).absolute() 

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

306 fh.write(strict_ascii(structure_text)) 

307 

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

309 

310 def run_feff(self, folder, version8=True): 

311 folder = Path(folder).absolute() 

312 dname = folder.name 

313 prog, cmd = feff8l, 'feff8l' 

314 if not version8: 

315 prog, cmd = feff6l, 'feff6l' 

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

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

318 

319 prog(folder=folder.as_posix(), message_writer=self.feff_output) 

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

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

322 this_feffrun = self.larch.symtable._sys._feffruns[f'{dname}'] 

323 self.feffresults.set_feffresult(this_feffrun) 

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

325 self.nb.SetSelection(ix) 

326 

327 # clean up unused, intermediate Feff files 

328 for fname in os.listdir(folder): 

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

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

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

332 os.unlink(Path(folder, fname).absolute()) 

333 

334 def feff_output(self, text): 

335 out = self.wids['feffout_text'] 

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

337 self.nb.SetSelection(ix) 

338 pos0 = out.GetLastPosition() 

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

340 text = '%s\n' % text 

341 out.WriteText(text) 

342 out.SetInsertionPoint(out.GetLastPosition()) 

343 out.Update() 

344 out.Refresh() 

345 

346 def onExportFeff(self, event=None): 

347 if self.current_structure is None: 

348 return 

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

350 if len(fefftext) < 20: 

351 return 

352 cc = self.current_structure 

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

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

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

356 wildcard=wildcard, 

357 default_file=fname) 

358 if path is not None: 

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

360 fh.write(fefftext) 

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

362 

363 def onExportStructure(self, event=None): 

364 if self.current_structure is None: 

365 return 

366 

367 cc = self.current_structure 

368 fname = cc["fname"] 

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

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

371 wildcard=wildcard, 

372 default_file=fname) 

373 

374 if path is not None: 

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

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

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

378 

379 def onImportStructure(self, event=None): 

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

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

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

383 

384 if path is not None: 

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

386 fname = Path(path).name 

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

388 structure_text = f.read() 

389 

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

391 

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

393 

394 # use pytmatgen to get formula 

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

396 

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

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

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

400 

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

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

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

404 

405 

406 

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

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

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

410 

411 # sites 

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

413 try: 

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

415 except: 

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

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

418 f"Sites: {sites}"] 

419 ExceptionPopup(self, title, message) 

420 

421 

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

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

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

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

426 self.nb.SetSelection(i) 

427 

428 def onImportFeff(self, event=None): 

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

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

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

432 if path is not None: 

433 fefftext = None 

434 fname = Path(path).name.replace('.inp', '_run') 

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

436 fefftext = read_textfile(path) 

437 if fefftext is not None: 

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

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

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

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

442 self.nb.SetSelection(i) 

443 

444 def onFeffFolder(self, eventa=None): 

445 "prompt for Feff Folder" 

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

447 style=wx.DD_DEFAULT_STYLE|wx.DD_CHANGE_DIR) 

448 

449 dlg.SetPath(self.feff_folder) 

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

451 return None 

452 self.feff_folder = Path(dlg.GetPath()).absolute().as_posix() 

453 mkdir(self.feff_folder) 

454 

455 def onNBChanged(self, event=None): 

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

457 if callable(callback): 

458 callback() 

459 

460 def onSelAll(self, event=None): 

461 self.controller.filelist.select_all() 

462 

463 def onSelNone(self, event=None): 

464 self.controller.filelist.select_none() 

465 

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

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

468 self.statusbar.SetStatusText(msg, panel) 

469 

470 def createMenus(self): 

471 self.menubar = wx.MenuBar() 

472 fmenu = wx.Menu() 

473 group_menu = wx.Menu() 

474 data_menu = wx.Menu() 

475 ppeak_menu = wx.Menu() 

476 m = {} 

477 

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

479 "Open Structure File", self.onImportStructure) 

480 

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

482 "Save Structure File", self.onExportStructure) 

483 

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

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

486 

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

488 "Save Feff6 File", self.onExportFeff) 

489 

490 fmenu.AppendSeparator() 

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

492 "Select Main Folder for running Feff", 

493 self.onFeffFolder) 

494 fmenu.AppendSeparator() 

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

496 

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

498 

499 self.SetMenuBar(self.menubar) 

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

501 

502 def onClose(self, event=None): 

503 self.Destroy() 

504 

505 

506class Structure2FeffViewer(LarchWxApp): 

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

508 self.filename = filename 

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

510 

511 def createApp(self): 

512 frame = Structure2FeffFrame(filename=self.filename, 

513 version_info=self.version_info) 

514 self.SetTopWindow(frame) 

515 return True 

516 

517def structure_viewer(**kws): 

518 Structure2FeffViewer(**kws) 

519 

520if __name__ == '__main__': 

521 Structure2FeffViewer().MainLoop()