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