1import abc 2import os 3import wx 4import wx.lib.dialogs as wxdg 5import abipy.gui.awx as awx 6import abipy.gui.electronswx as ewx 7 8from monty.os.path import which 9from abipy.core.mixins import NcDumper 10from abipy.iotools.visualizer import Visualizer 11from abipy.electrons.ebands import ElectronBandsPlotter, ElectronDosPlotter 12from abipy.gui.structure import StructureConverterFrame 13from abipy.gui.converter import ConverterFrame 14from abipy.gui.wxncview import NcViewerFrame 15from abipy.dfpt.phonons import PhononBandsPlotter, PhononDosPlotter 16from abipy.abilab import abiopen 17from abipy.iotools import ETSF_Reader 18 19 20class Has_Structure(metaclass=abc.ABCMeta): 21 """ 22 Mixin class that provides a menu and callbacks 23 for analyzing the crystalline structure. 24 """ 25 @abc.abstractproperty 26 def structure(self): 27 """Structure object.""" 28 29 def CreateStructureMenu(self): 30 """Creates the structure menu.""" 31 # Structure Menu ID's 32 self.ID_STRUCT_CONVERT = wx.NewId() 33 self.ID_STRUCT_VISUALIZE = wx.NewId() 34 self.ID_STRUCT_SHOWBZ = wx.NewId() 35 36 menu = wx.Menu() 37 menu.Append(self.ID_STRUCT_CONVERT, "Convert", "Convert structure data to cif, POSCAR ...") 38 self.Bind(wx.EVT_MENU, self.OnStructureConvert, id=self.ID_STRUCT_CONVERT) 39 40 menu.Append(self.ID_STRUCT_SHOWBZ, "Show BZ", "Visualize the first Brillouin zone with matplotlib.") 41 self.Bind(wx.EVT_MENU, self.OnStructureShowBz, id=self.ID_STRUCT_SHOWBZ) 42 43 # Make sub-menu with the list of supported visualizers. 44 visu_menu = wx.Menu() 45 self._id2visuname = {} 46 47 available_visus = [visu.name for visu in Visualizer.get_available()] 48 49 for appname in available_visus: 50 _id = wx.NewId() 51 visu_menu.Append(_id, appname) 52 self._id2visuname[_id] = appname 53 self.Bind(wx.EVT_MENU, self.OnStructureVisualize, id=_id) 54 55 menu.AppendMenu(-1, 'Visualize', visu_menu) 56 return menu 57 58 def OnStructureConvert(self, event): 59 """Open new frame that allows the user to convert the structure.""" 60 StructureConverterFrame(self, self.structure).Show() 61 62 def OnStructureVisualize(self, event): 63 """"Call the visualizer to visualize the crystalline structure.""" 64 appname = self._id2visuname[event.GetId()] 65 66 try: 67 visu = self.structure.visualize(appname=appname) 68 thread = awx.WorkerThread(self, target=visu) 69 thread.start() 70 71 except Exception: 72 awx.showErrorMessage(self) 73 74 def OnStructureShowBz(self, event): 75 """"Visualize the Brillouin zone with matplotlib.""" 76 self.structure.show_bz() 77 78 79class Has_Ebands(metaclass=abc.ABCMeta): 80 """ 81 Mixin class that provides a menu and callbacks for analyzing electron bands. 82 """ 83 @abc.abstractproperty 84 def ebands(self): 85 """`ElectronBands` object.""" 86 87 def CreateEbandsMenu(self): 88 """Creates the ebands menu.""" 89 # Ebands Menu ID's 90 self.ID_EBANDS_GETINFO = wx.NewId() 91 self.ID_EBANDS_PLOT = wx.NewId() 92 self.ID_EBANDS_DOS = wx.NewId() 93 self.ID_EBANDS_JDOS = wx.NewId() 94 #self.ID_EBANDS_FSURF = wx.NewId() 95 #self.ID_EBANDS_SCISSORS = wx.NewId() 96 97 menu = wx.Menu() 98 99 menu.Append(self.ID_EBANDS_GETINFO, "Get Info", "Show info on the band structure") 100 self.Bind(wx.EVT_MENU, self.OnEbandsGetInfo, id=self.ID_EBANDS_GETINFO) 101 102 menu.Append(self.ID_EBANDS_PLOT, "Plot ebands", "Plot electron bands with matplotlib") 103 self.Bind(wx.EVT_MENU, self.OnEbandsPlot, id=self.ID_EBANDS_PLOT) 104 105 menu.Append(self.ID_EBANDS_DOS, "DOS", "Compute the electron DOS") 106 self.Bind(wx.EVT_MENU, self.OnEbandsDos, id=self.ID_EBANDS_DOS) 107 108 menu.Append(self.ID_EBANDS_JDOS, "JDOS", "Compute the electron Joint DOS") 109 self.Bind(wx.EVT_MENU, self.OnEbandsJdos, id=self.ID_EBANDS_JDOS) 110 111 # TODO 112 #menu.Append(self.ID_EBANDS_FSURF, "Fermi surface", "Visualize the Fermi surface with Xcrysden") 113 #self.Bind(wx.EVT_MENU, self.OnFermiSurface, id=self.ID_EBANDS_FSURF) 114 115 # TODO 116 #menu.Append(self.ID_EBANDS_SCISSORS, "Apply scissors", "Apply a scissors operator") 117 #self.Bind(wx.EVT_MENU, self.OnApplyScissors, id=self.ID_EBANDS_SCISSORS) 118 119 return menu 120 121 def OnEbandsGetInfo(self, event): 122 """Shows info on the bandstructure.""" 123 s = self.ebands.info 124 caption = "Ebands info" 125 wxdg.ScrolledMessageDialog(self, s, caption=caption, style=wx.MAXIMIZE_BOX).Show() 126 127 def OnEbandsPlot(self, event): 128 """Plot band energies with matplotlib.""" 129 self.ebands.plot() 130 131 def OnEbandsDos(self, event): 132 """Open Frame for the computation of the DOS.""" 133 ewx.ElectronDosFrame(self, bands=self.ebands).Show() 134 135 def OnEbandsJdos(self, event): 136 """Open Frame for the computation of the JDOS.""" 137 ewx.ElectronJdosFrame(self, bands=self.ebands).Show() 138 139 def OnFermiSurface(self, event): 140 """Visualize the Fermi surface with Xcrysden.""" 141 try: 142 visu = self.ebands.export_bxsf(".bxsf") 143 144 thread = awx.WorkerThread(self, target=visu) 145 thread.start() 146 147 except Exception: 148 awx.showErrorMessage(self) 149 150 #def OnApplyScissors(self, event): 151 # """ 152 # Read the scissors operator from a pickle file, apply it to the electron bands and save the results. 153 # """ 154 # Get the scissors operator from file 155 # Apply the scissors. 156 # new_ebands = self.ebands.apply_scissors(self, scissors) 157 # new_ebands.plot() 158 # new_ebands.pickle_dump() 159 160 161class Has_MultipleEbands(Has_Ebands): 162 """ 163 Mixin class that provides a menu and callbacks 164 for analyzing and comparing multiple electron bands. 165 """ 166 def CreateEbandsMenu(self): 167 """Creates the ebands menu.""" 168 menu = super(Has_MultipleEbands, self).CreateEbandsMenu() 169 menu.AppendSeparator() 170 171 # Multiple Ebands Menu ID's 172 self.ID_MULTI_EBANDS_PLOT = wx.NewId() 173 self.ID_MULTI_EBANDS_DOS = wx.NewId() 174 #self.ID_MULTI_EBANDS_JDOS = wx.NewId() 175 self.ID_MULTI_EBANDS_BANDSWITHDOS = wx.NewId() 176 177 menu.Append(self.ID_MULTI_EBANDS_PLOT, "Compare ebands", "Plot multiple electron bands") 178 self.Bind(wx.EVT_MENU, self.OnCompareEbands, id=self.ID_MULTI_EBANDS_PLOT) 179 menu.Append(self.ID_MULTI_EBANDS_DOS, "Compare DOSes", "Compare multiple electron DOSes") 180 self.Bind(wx.EVT_MENU, self.OnCompareEdos, id=self.ID_MULTI_EBANDS_DOS) 181 #menu.Append(self.ID_MULTI_EBANDS_JDOS, "Compare JDOSes", "Compare multiple electron JDOSes") 182 #self.Bind(wx.EVT_MENU, self.OnCompareJdos, id=self.ID_MULTI_EBANDS_JDOS) 183 184 menu.Append(self.ID_MULTI_EBANDS_BANDSWITHDOS, "Plot bands and DOS", "Plot electron bands and DOS on the same figure") 185 self.Bind(wx.EVT_MENU, self.onPlotEbandsWithDos, id=self.ID_MULTI_EBANDS_BANDSWITHDOS) 186 187 return menu 188 189 @abc.abstractproperty 190 def ebands_list(self): 191 """Return a list of `ElectronBands`.""" 192 193 @abc.abstractproperty 194 def ebands_filepaths(self): 195 """ 196 Return a list with the absolute paths of the files 197 from which the `ElectronBands` have been read. 198 """ 199 200 def OnCompareEbands(self, event): 201 """Plot multiple electron bands""" 202 plotter = ElectronBandsPlotter() 203 for path, ebands in zip(self.ebands_filepaths, self.ebands_list): 204 label = os.path.relpath(path) 205 plotter.add_ebands(label, ebands) 206 207 try: 208 print(plotter.bands_statdiff()) 209 except: 210 pass 211 plotter.plot() 212 213 def OnCompareEdos(self, event): 214 """Plot multiple electron DOSes""" 215 # Open dialog to get DOS parameters. 216 dialog = ewx.ElectronDosDialog(self) 217 if dialog.ShowModal() == wx.ID_CANCEL: return 218 dos_params = dialog.GetParams() 219 220 plotter = ElectronDosPlotter() 221 for path, ebands in zip(self.ebands_filepaths, self.ebands_list): 222 try: 223 edos = ebands.get_edos(**dos_params) 224 label = os.path.relpath(path) 225 plotter.add_edos(label, edos) 226 except: 227 awx.showErrorMessage(self) 228 229 plotter.plot() 230 231 #def OnCompareJdos(self, event): 232 #"""Plot multiple electron JDOSes""" 233 # Open dialog to get DOS parameters. 234 #dialog = ElectronJdosDialog(self, nsppol, mband) 235 #jdos_params = dialog.GetParams() 236 237 #plotter = ElectronBandsPlotter() 238 #for ebands in self.ebands_list: 239 # jos = ebands.get_jdos(**jdos_params) 240 # plotter.add_edos(label, edos) 241 # 242 #plotter.plot() 243 244 def onPlotEbandsWithDos(self, event): 245 """Plot electron bands with DOS. Requires the specification of two files.""" 246 # Open dialog to get files and DOS parameters. 247 dialog = ewx.EbandsDosDialog(self, self.ebands_filepaths) 248 if dialog.ShowModal() == wx.ID_CANCEL: return 249 250 try: 251 dos_params = dialog.getEdosParams() 252 ipath, idos = dialog.getBandsDosIndex() 253 254 ebands_path = self.ebands_list[ipath] 255 ebands_mesh = self.ebands_list[idos] 256 257 edos = ebands_mesh.get_edos(**dos_params) 258 ebands_path.plot_with_edos(edos) 259 except: 260 awx.showErrorMessage(self) 261 262 263class Has_Tools(object): 264 """ 265 Mixin class that provides a menu with external tools. 266 """ 267 def CreateToolsMenu(self): 268 """Create the tools menu.""" 269 # Tools Menu ID's 270 self.ID_TOOLS_UNIT_CONVERTER = wx.NewId() 271 self.ID_TOOLS_PERIODIC_TABLE = wx.NewId() 272 273 menu = wx.Menu() 274 menu.Append(self.ID_TOOLS_PERIODIC_TABLE, "Periodic table", "Periodic Table") 275 self.Bind(wx.EVT_MENU, self.OnTools_PeriodicTable, id=self.ID_TOOLS_PERIODIC_TABLE) 276 277 menu.Append(self.ID_TOOLS_UNIT_CONVERTER, "Unit converter", "Unit Converter") 278 self.Bind(wx.EVT_MENU, self.OnTools_UnitConverter, id=self.ID_TOOLS_UNIT_CONVERTER) 279 280 return menu 281 282 def OnTools_PeriodicTable(self, event): 283 """Open new frame with the periodic table.""" 284 from awx.elements_gui import WxPeriodicTable 285 WxPeriodicTable(self).Show() 286 287 def OnTools_UnitConverter(self, event): 288 """Open new frame with the unit converter.""" 289 ConverterFrame(self).Show() 290 291 292class Has_NetcdfFiles(metaclass=abc.ABCMeta): 293 """ 294 Mixin class that provides a menu and callbacks 295 for analyzing and comparing netcdf files. 296 """ 297 @abc.abstractproperty 298 def nc_filepaths(self): 299 """List of absolute paths of the netcdf file.""" 300 301 def CreateNetcdfMenu(self): 302 """Creates the ebands menu.""" 303 # Netcdf Menu ID's 304 self.ID_NETCDF_WXNCVIEW = wx.NewId() 305 self.ID_NETCDF_NCDUMP = wx.NewId() 306 self.ID_NETCDF_NCVIEW = wx.NewId() 307 308 menu = wx.Menu() 309 310 menu.Append(self.ID_NETCDF_WXNCVIEW, "wxncview", "Call wxncview") 311 self.Bind(wx.EVT_MENU, self.OnNetcdf_WxNcView, id=self.ID_NETCDF_WXNCVIEW) 312 313 menu.Append(self.ID_NETCDF_NCDUMP, "ncdump", "Show the output of ncdump") 314 self.Bind(wx.EVT_MENU, self.OnNetcdf_NcDump, id=self.ID_NETCDF_NCDUMP) 315 316 menu.Append(self.ID_NETCDF_NCVIEW, "ncview", "Call ncview") 317 self.Bind(wx.EVT_MENU, self.OnNetcdf_NcView, id=self.ID_NETCDF_NCVIEW) 318 319 return menu 320 321 def OnNetcdf_NcDump(self, event): 322 """Call ncdump and show results in a dialog.""" 323 for path in self.nc_filepaths: 324 s = NcDumper().dump(path) 325 caption = "ncdump output for %s" % path 326 wxdg.ScrolledMessageDialog(self, s, caption=caption, style=wx.MAXIMIZE_BOX).Show() 327 328 def OnNetcdf_NcView(self, event): 329 """Call ncview in an subprocess.""" 330 if which("ncview") is None: 331 return awx.showErrorMessage(self, "Cannot find ncview in $PATH") 332 333 for path in self.nc_filepaths: 334 def target(): 335 os.system("ncview %s" % path) 336 337 thread = awx.WorkerThread(self, target=target) 338 thread.start() 339 340 def OnNetcdf_WxNcView(self, event): 341 """Open wxncview frame.""" 342 NcViewerFrame(self, filepaths=self.nc_filepaths).Show() 343 344 345class Has_Phbands(metaclass=abc.ABCMeta): 346 """ 347 Mixin class that provides a menu and callbacks for analyzing phonon bands. 348 """ 349 @abc.abstractproperty 350 def phbands(self): 351 """`PhononBands` object.""" 352 353 def CreatePhbandsMenu(self): 354 """Creates the ebands menu.""" 355 # Ebands Menu ID's 356 self.ID_PHBANDS_ADD_DOS = wx.NewId() 357 self.ID_PHBANDS_ADD_LO_TO = wx.NewId() 358 self.ID_PHBANDS_PLOT = wx.NewId() 359 self.ID_PHBANDS_DOS = wx.NewId() 360 self.ID_MULTI_PHBANDS_BANDSWITHDOS = wx.NewId() 361 362 menu = wx.Menu() 363 364 menu.Append(self.ID_PHBANDS_ADD_DOS, "Add phdos data", "Add the phonon dos data from a PHDOS.nc file") 365 self.Bind(wx.EVT_MENU, self.OnAddDos, id=self.ID_PHBANDS_ADD_DOS) 366 367 menu.Append(self.ID_PHBANDS_ADD_LO_TO, "Add LO-TO data", "Add the LO-TO splitting data from a PHDOS.nc file") 368 self.Bind(wx.EVT_MENU, self.OnAddLoTo, id=self.ID_PHBANDS_ADD_LO_TO) 369 370 menu.Append(self.ID_PHBANDS_PLOT, "Plot phbands", "Plot phonon bands with matplotlib") 371 self.Bind(wx.EVT_MENU, self.OnPhbandsPlot, id=self.ID_PHBANDS_PLOT) 372 373 menu.Append(self.ID_PHBANDS_DOS, "DOS", "Plot the phonon DOS") 374 self.Bind(wx.EVT_MENU, self.OnPhbandsDos, id=self.ID_PHBANDS_DOS) 375 376 menu.Append(self.ID_MULTI_PHBANDS_BANDSWITHDOS, "Plot bands and DOS", "Plot phonon bands and DOS on the same figure") 377 self.Bind(wx.EVT_MENU, self.onPlotPhbandsWithDos, id=self.ID_MULTI_PHBANDS_BANDSWITHDOS) 378 379 return menu 380 381 def OnAddDos(self, event): 382 """Add PHDOS data to the active tab""" 383 dialog = wx.FileDialog(self, message="Choose a PHDOS.nc file", defaultDir=os.getcwd(), 384 wildcard="Netcdf files (*.nc)|*.nc", 385 style=wx.OPEN | wx.CHANGE_DIR) 386 387 if dialog.ShowModal() == wx.ID_CANCEL: return 388 phdos = abiopen(dialog.GetPath()) 389 390 self.active_tab.phdos_file = phdos 391 392 def OnAddLoTo(self, event): 393 """Add LO-TO splitting data to the phbands in the active tab""" 394 dialog = wx.FileDialog(self, message="Choose an anaddb.nc file", defaultDir=os.getcwd(), 395 wildcard="Netcdf files (*.nc)|*.nc", 396 style=wx.OPEN | wx.CHANGE_DIR) 397 398 if dialog.ShowModal() == wx.ID_CANCEL: return 399 400 self.phbands.read_non_anal_from_file(dialog.GetPath()) 401 402 def OnPhbandsPlot(self, event): 403 """Plot phonon frequencies with matplotlib.""" 404 self.phbands.plot() 405 406 def OnPhbandsDos(self, event): 407 """Open Frame for the computation of the DOS.""" 408 if not self.phdos: 409 awx.showErrorMessage(self, message="PHDOS data should be loaded using the menu Phband->Add phdos data") 410 else: 411 plotter = PhononDosPlotter() 412 try: 413 label = os.path.relpath(self.active_phdos_file.filepath) 414 plotter.add_phdos(label, self.phdos) 415 except: 416 awx.showErrorMessage(self) 417 plotter.plot() 418 419 def onPlotPhbandsWithDos(self, event): 420 """Plot phonon bands with DOS""" 421 422 try: 423 if not self.phdos: 424 awx.showErrorMessage(self, message="PHDOS data should be loaded using the menu Phband->Add phdos data") 425 else: 426 self.phbands.plot_with_phdos(self.phdos) 427 except: 428 awx.showErrorMessage(self) 429 430 @abc.abstractproperty 431 def phdos(self): 432 """PHDOS data for the active tab if it has been added. None otherwise""" 433 434 435class Has_MultiplePhbands(Has_Phbands): 436 """ 437 Mixin class that provides a menu and callbacks 438 for analyzing and comparing multiple phonon bands. 439 """ 440 def CreatePhbandsMenu(self): 441 """Creates the ebands menu.""" 442 menu = super(Has_MultiplePhbands, self).CreatePhbandsMenu() 443 menu.AppendSeparator() 444 445 # Multiple Ebands Menu ID's 446 self.ID_MULTI_PHBANDS_PLOT = wx.NewId() 447 self.ID_MULTI_PHBANDS_DOS = wx.NewId() 448 449 menu.Append(self.ID_MULTI_PHBANDS_PLOT, "Compare phbands", "Plot multiple phonon bands") 450 self.Bind(wx.EVT_MENU, self.OnComparePhbands, id=self.ID_MULTI_PHBANDS_PLOT) 451 menu.Append(self.ID_MULTI_PHBANDS_DOS, "Compare DOSes", "Compare multiple phonon DOSes") 452 self.Bind(wx.EVT_MENU, self.OnComparePhdos, id=self.ID_MULTI_PHBANDS_DOS) 453 454 return menu 455 456 @abc.abstractproperty 457 def phbands_list(self): 458 """Return a list of `PhononBands`.""" 459 460 @abc.abstractproperty 461 def phbands_filepaths(self): 462 """ 463 Return a list with the absolute paths of the files 464 from which the `PhononBands` have been read. 465 """ 466 467 @abc.abstractproperty 468 def phdos_list(self): 469 """Return a list of `PhononDos`.""" 470 471 @abc.abstractproperty 472 def phdos_filepaths(self): 473 """ 474 Return a list with the absolute paths of the files 475 from which the `PhononDos` have been read. 476 """ 477 478 def OnComparePhbands(self, event): 479 """Plot multiple phonon bands""" 480 481 dialog = ewx.BandsCompareDialog(self, self.phbands_filepaths) 482 if dialog.ShowModal() == wx.ID_CANCEL: return 483 484 try: 485 selected = dialog.GetSelectedIndices() 486 487 except: 488 awx.showErrorMessage(self) 489 490 plotter = PhononBandsPlotter() 491 492 for i in selected: 493 label = os.path.relpath(self.phbands_filepaths[i]) 494 plotter.add_phbands(label, self.phbands_list[i]) 495 496 try: 497 print(plotter.bands_statdiff()) 498 except: 499 pass 500 plotter.plot() 501 502 def OnComparePhdos(self, event): 503 """Plot multiple phonon DOSes""" 504 505 plotter = PhononDosPlotter() 506 for path, phdos in zip(self.phdos_filepaths, self.phdos_list): 507 try: 508 label = os.path.relpath(path) 509 plotter.add_phdos(label, phdos) 510 except: 511 awx.showErrorMessage(self) 512 513 plotter.plot() 514