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