1""""Base classes and mixins for AbiPy panels."""
2
3#import abc
4import param
5import panel as pn
6import panel.widgets as pnw
7import bokeh.models.widgets as bkw
8
9from monty.functools import lazy_property
10
11
12def gen_id(n=1, pre="uuid-"):
13    """
14    Generate ``n`` universally unique identifiers prepended with ``pre`` string.
15    Return string if n == 1 or list of strings if n > 1
16    """
17    # The HTML4 spec says:
18    # ID and NAME tokens must begin with a letter ([A-Za-z]) and may be followed by any number of letters,
19    # digits ([0-9]), hyphens ("-"), underscores ("_"), colons (":"), and periods (".").
20    import uuid
21    if n == 1:
22        return pre + str(uuid.uuid4())
23    elif n > 1:
24        return [pre + str(uuid.uuid4()) for i in range(n)]
25    else:
26        raise ValueError("n must be > 0 but got %s" % str(n))
27
28
29def sizing_mode_select(name="sizing_mode", value="scale_both"):
30    """
31    Widget to select the value of sizing_mode. See https://panel.holoviz.org/user_guide/Customization.html
32    """
33    return pn.widgets.Select(name=name, value=value, options=["fixed",
34                 "stretch_width", "stretch_height", "stretch_both",
35                 "scale_height", "scale_width", "scale_both"])
36
37
38class AbipyParameterized(param.Parameterized):
39
40    verbose = param.Integer(0, bounds=(0, None), doc="Verbosity Level")
41    mpi_procs = param.Integer(1, bounds=(1, None), doc="Number of MPI processes used when runnin Fortran code")
42    #fontsize =
43
44    #def get_global_widgets(self):
45    #    return pn.Column(self.verbose, self.mpi_procs)
46
47    @lazy_property
48    def fig_kwargs(self):
49        """Default arguments passed to AbiPy plot methods."""
50        return dict(show=False, fig_close=True)
51
52    @staticmethod
53    def _mp(fig):
54        return pn.pane.Matplotlib(fig, sizing_mode="scale_width")
55
56    @staticmethod
57    def _df(df, disabled=True, sizing_mode="scale_width"):
58        return pn.widgets.DataFrame(df, disabled=disabled, sizing_mode=sizing_mode)
59
60
61#class PanelWithNcFile(AbipyParameterized): #, metaclass=abc.ABCMeta):
62#    """
63#    This frame allows the user to inspect the dimensions and the variables reported in  a netcdf file.
64#    Tab showing information on the netcdf file.
65#    """
66
67
68class PanelWithElectronBands(AbipyParameterized): #, metaclass=abc.ABCMeta):
69
70    # Bands plot
71    with_gaps = pnw.Checkbox(name='Show gaps')
72    #ebands_ylims
73    #ebands_e0
74    # e0: Option used to define the zero of energy in the band structure plot. Possible values:
75    #     - `fermie`: shift all eigenvalues to have zero energy at the Fermi energy (`self.fermie`).
76    #     -  Number e.g e0=0.5: shift all eigenvalues to have zero energy at 0.5 eV
77    #     -  None: Don't shift energies, equivalent to e0=0
78    set_fermie_to_vbm = pnw.Checkbox(name="Set Fermie to VBM")
79
80    plot_ebands_btn = pnw.Button(name="Plot e-bands", button_type='primary')
81
82    # DOS plot.
83    edos_method = pnw.Select(name="e-DOS method", options=["gaussian", "tetra"])
84    edos_step = pnw.Spinner(name='e-DOS step (eV)', value=0.1, step=0.05, start=1e-6, end=None)
85    edos_width = pnw.Spinner(name='e-DOS Gaussian broadening (eV)', value=0.2, step=0.05, start=1e-6, end=None)
86    plot_edos_btn = pnw.Button(name="Plot e-DOS", button_type='primary')
87
88    # Fermi surface plot.
89    fs_viewer = pnw.Select(name="FS viewer", options=["matplotlib", "xcrysden"])
90    plot_fermi_surface_btn = pnw.Button(name="Plot Fermi surface", button_type='primary')
91
92    #@abc.abstractproperty
93    #def ebands(self):
94    #    """Returns the |ElectronBands| object."""
95
96    def get_plot_ebands_widgets(self):
97        """Widgets to plot ebands."""
98        return pn.Column(self.with_gaps, self.set_fermie_to_vbm, self.plot_ebands_btn)
99
100    @param.depends('plot_ebands_btn.clicks')
101    def on_plot_ebands_btn(self):
102        """Button triggering ebands plot."""
103        if self.plot_ebands_btn.clicks == 0: return
104        if self.set_fermie_to_vbm.value:
105            self.ebands.set_fermie_to_vbm()
106
107        fig1 = self.ebands.plot(e0="fermie", ylims=None,
108            with_gaps=self.with_gaps.value, max_phfreq=None, fontsize=8, **self.fig_kwargs)
109
110        fig2 = self.ebands.kpoints.plot(**self.fig_kwargs)
111        row = pn.Row(self._mp(fig1), self._mp(fig2)) #, sizing_mode='scale_width')
112        text = bkw.PreText(text=self.ebands.to_string(verbose=self.verbose))
113        return pn.Column(row, text, sizing_mode='scale_width')
114
115    def get_plot_edos_widgets(self):
116        """Widgets to compute e-DOS."""
117        return pn.Column(self.edos_method, self.edos_step, self.edos_width, self.plot_edos_btn)
118
119    @param.depends('plot_edos_btn.clicks')
120    def on_plot_edos_btn(self):
121        """Button triggering edos plot."""
122        if self.plot_edos_btn.clicks == 0: return
123        edos = self.ebands.get_edos(method=self.edos_method.value, step=self.edos_step.value, width=self.edos_width.value)
124        fig = edos.plot(**self.fig_kwargs)
125        return pn.Row(self._mp(fig), sizing_mode='scale_width')
126
127    def get_plot_fermi_surface_widgets(self):
128        """Widgets to compute e-DOS."""
129        return pn.Column(self.fs_viewer, self.plot_fermi_surface_btn)
130
131    @param.depends('plot_fermi_surface_btn.clicks')
132    def on_plot_fermi_surface_btn(self):
133        if self.plot_fermi_surface_btn.clicks == 0: return
134
135        # Cache eb3d
136        if hasattr(self, "_eb3d"):
137            eb3d = self._eb3d
138        else:
139            # Build ebands in full BZ.
140            eb3d = self._eb3d = self.ebands.get_ebands3d()
141
142        if self.fs_viewer.value == "matplotlib":
143            # Use matplotlib to plot isosurfaces corresponding to the Fermi level (default)
144            # Warning: requires skimage package, rendering could be slow.
145            fig = eb3d.plot_isosurfaces(e0="fermie", cmap=None, **self.fig_kwargs)
146            return pn.Row(self._mp(fig), sizing_mode='scale_width')
147
148        else:
149            raise ValueError("Invalid choice: %s" % self.fs_viewer.value)
150
151        #elif self.fs_viewer.value == "xcrysden":
152            # Alternatively, it's possible to export the data in xcrysden format
153            # and then use `xcrysden --bxsf mgb2.bxsf`
154            #eb3d.to_bxsf("mgb2.bxsf")
155            # If you have mayavi installed, try:
156            #eb3d.mvplot_isosurfaces()
157
158
159class BaseRobotPanel(AbipyParameterized):
160    """pass"""
161
162
163class PanelWithEbandsRobot(BaseRobotPanel): #, metaclass=abc.ABCMeta):
164    """
165    Mixin class for panels with a robot that owns a list of of |ElectronBands|
166    """
167
168    # Widgets to plot ebands.
169    ebands_plotter_mode = pnw.Select(name="Plot Mode", value="gridplot",
170        options=["gridplot", "combiplot", "boxplot", "combiboxplot"]) # "animate",
171    ebands_plotter_btn = pnw.Button(name="Plot", button_type='primary')
172    ebands_df_checkbox = pnw.Checkbox(name='With Ebands DataFrame', value=False)
173
174    # Widgets to plot edos.
175    edos_plotter_mode = pnw.Select(name="Plot Mode", value="gridplot",
176        options=["gridplot", "combiplot"])
177    edos_plotter_btn = pnw.Button(name="Plot", button_type='primary')
178
179    def get_ebands_plotter_widgets(self):
180        return pn.Column(self.ebands_plotter_mode, self.ebands_df_checkbox, self.ebands_plotter_btn)
181
182    @param.depends("ebands_plotter_btn.clicks")
183    def on_ebands_plotter_btn(self):
184        if self.ebands_plotter_btn.clicks == 0: return
185        ebands_plotter = self.robot.get_ebands_plotter()
186        plot_mode = self.ebands_plotter_mode.value
187        plotfunc = getattr(ebands_plotter, plot_mode, None)
188        if plotfunc is None:
189            raise ValueError("Don't know how to handle plot_mode: %s" % plot_mode)
190
191        fig = plotfunc(**self.fig_kwargs)
192        col = pn.Column(self._mp(fig), sizing_mode='scale_width')
193        if self.ebands_df_checkbox.value:
194            df = ebands_plotter.get_ebands_frame(with_spglib=True)
195            col.append(self._df(df))
196
197        return pn.Row(col, sizing_mode='scale_width')
198
199    def get_edos_plotter_widgets(self):
200        return pn.Column(self.edos_plotter_mode, self.edos_plotter_btn)
201
202    @param.depends("edos_plotter_btn.clicks")
203    def on_edos_plotter_btn(self):
204        if self.edos_plotter_btn.clicks == 0: return
205        edos_plotter = self.robot.get_edos_plotter()
206        plot_mode = self.edos_plotter_mode.value
207        plotfunc = getattr(edos_plotter, plot_mode, None)
208        if plotfunc is None:
209            raise ValueError("Don't know how to handle plot_mode: %s" % plot_mode)
210
211        fig = plotfunc(**self.fig_kwargs)
212        return pn.Row(pn.Column(self._mp(fig)), sizing_mode='scale_width')
213