diff --git a/src/aiidalab_qe_vibroscopy/app/result/result.py b/src/aiidalab_qe_vibroscopy/app/result/result.py index 8ad8d69..9548a2c 100644 --- a/src/aiidalab_qe_vibroscopy/app/result/result.py +++ b/src/aiidalab_qe_vibroscopy/app/result/result.py @@ -13,14 +13,10 @@ from aiidalab_qe_vibroscopy.app.widgets.phononwidget import PhononWidget from aiidalab_qe_vibroscopy.app.widgets.phononmodel import PhononModel -from aiidalab_qe_vibroscopy.app.widgets.euphonicwidget import ( - EuphonicSuperWidget as EuphonicWidget, -) +from aiidalab_qe_vibroscopy.app.widgets.euphonicwidget import EuphonicWidget from aiidalab_qe_vibroscopy.app.widgets.euphonicmodel import ( - EuphonicBaseResultsModel as EuphonicModel, + EuphonicResultsModel as EuphonicModel, ) -# from aiidalab_qe_vibroscopy.app.widgets.euphonic.model import EuphonicModel -# from aiidalab_qe_vibroscopy.app.widgets.euphonic.widget import EuphonicWidget class VibroResultsPanel(ResultsPanel[VibroResultsModel]): @@ -76,7 +72,7 @@ def render(self): ) tab_data.append(("Dielectric Properties", dielectric_widget)) - needs_euphonic_tab = False # self._model.needs_euphonic_tab() + needs_euphonic_tab = self._model.needs_euphonic_tab() if needs_euphonic_tab: euphonic_model = EuphonicModel() euphonic_widget = EuphonicWidget( diff --git a/src/aiidalab_qe_vibroscopy/app/widgets/euphonic/model.py b/src/aiidalab_qe_vibroscopy/app/widgets/euphonic/model.py index 264ca43..3859069 100644 --- a/src/aiidalab_qe_vibroscopy/app/widgets/euphonic/model.py +++ b/src/aiidalab_qe_vibroscopy/app/widgets/euphonic/model.py @@ -1,7 +1,7 @@ from aiidalab_qe.common.panel import ResultsModel from aiida.common.extendeddicts import AttributeDict import traitlets as tl -from aiidalab_qe_vibroscopy.utils.euphonic.data_manipulation.intensity_maps import ( +from aiidalab_qe_vibroscopy.utils.euphonic.data.export_vibronic_to_euphonic import ( export_euphonic_data, ) diff --git a/src/aiidalab_qe_vibroscopy/app/widgets/euphonicmodel.py b/src/aiidalab_qe_vibroscopy/app/widgets/euphonicmodel.py index 500bd65..2617e77 100644 --- a/src/aiidalab_qe_vibroscopy/app/widgets/euphonicmodel.py +++ b/src/aiidalab_qe_vibroscopy/app/widgets/euphonicmodel.py @@ -1,14 +1,21 @@ import numpy as np import traitlets as tl import copy +from IPython.display import display from aiidalab_qe_vibroscopy.utils.euphonic.data.structure_factors import ( AttrDict, produce_bands_weigthed_data, produce_powder_data, generated_curated_data, +) + +from aiidalab_qe_vibroscopy.utils.euphonic.data.phonopy_interface import ( + generate_force_constant_from_phonopy, +) + +from aiidalab_qe_vibroscopy.utils.euphonic.data.export_vibronic_to_euphonic import ( export_euphonic_data, - generate_force_constant_instance, ) from aiidalab_qe_vibroscopy.utils.euphonic.data.parameters import ( @@ -22,6 +29,7 @@ produce_Q_section_spectrum, ) + from aiidalab_qe.common.mvc import Model @@ -38,21 +46,33 @@ class EuphonicResultsModel(Model): # 2. powder average: pa # 3. Q planes: qp - spectra = {} - path = [] - q_path = None - spectrum_type = "single_crystal" - x_label = None - y_label = "Energy (meV)" - detached_app = False - # Settings for single crystal and powder average q_spacing = tl.Float(0.01) energy_broadening = tl.Float(0.05) energy_bins = tl.Int(200) temperature = tl.Float(0) weighting = tl.Unicode("coherent") - + + def __init__(self, node=None, spectrum_type: str = "single_crystal", **kwargs): + super().__init__(**kwargs) + + self.spectra = {} + self.path = [] + self.q_path = None + self.spectrum_type = spectrum_type + self.xlabel = None + self.ylabel = "Energy (meV)" + self.detached_app = False + if node: + self.vibro = node + + if self.spectrum_type == "single_crystal": + self._inject_single_crystal_settings() + elif self.spectrum_type == "powder": + self._inject_powder_settings() + elif self.spectrum_type == "q_planes": + self._inject_qsection_settings() + def set_model_state(self, parameters: dict): for k, v in parameters.items(): setattr(self, k, v) @@ -78,10 +98,10 @@ def fetch_data(self): """Fetch the data from the database or from the uploaded files.""" # 1. from aiida, so we have the node if hasattr(self, "fc"): - # we already have the data (this happens if I clone the model with already the data inside) - return - if self.node: - ins_data = export_euphonic_data(self.node) + # we already have the data (this happens if I clone the model with already the data inside) + return + if self.vibro: + ins_data = export_euphonic_data(self.vibro) self.fc = ins_data["fc"] self.q_path = ins_data["q_path"] # 2. from uploaded files... @@ -99,7 +119,7 @@ def _inject_single_crystal_settings( # we define specific parameters dictionary and callback function for the single crystal case self.parameters = copy.deepcopy(parameters_single_crystal) self._callback_spectra_generation = produce_bands_weigthed_data - + # Dynamically add a trait for single crystal settings self.add_traits(custom_kpath=tl.Unicode("")) @@ -108,7 +128,7 @@ def _inject_powder_settings( ): self.parameters = copy.deepcopy(parameters_powder) self._callback_spectra_generation = produce_powder_data - + # Dynamically add a trait for powder settings self.add_traits(q_min=tl.Float(0.0)) self.add_traits(q_max=tl.Float(1)) @@ -132,10 +152,7 @@ def get_spectra( self, ): # This is used to update the spectra when the parameters are changed - # and the - if not hasattr(self, "parameters"): - self._inject_single_crystal_settings() - + if self.spectrum_type == "q_planes": self._get_qsection_spectra() return @@ -164,7 +181,7 @@ def get_spectra( ) # curated spectra (labels and so on...) - if spectrum_type == "single_crystal": # single crystal case + if self.spectrum_type == "single_crystal": # single crystal case self.x, self.y = np.meshgrid( spectra[0].x_data.magnitude, spectra[0].y_data.magnitude ) @@ -174,15 +191,15 @@ def get_spectra( self.ticks_positions, self.ticks_labels, ) = generated_curated_data(spectra) - + self.z = final_zspectra.T - self.y = self.y[:,0] - self.x = None # we have the ticks positions and labels - + self.y = self.y[:, 0] + self.x = None # we have the ticks positions and labels + self.xlabel = "" self.ylabel = "Energy (meV)" - - elif spectrum_type == "powder": # powder case + + elif self.spectrum_type == "powder": # powder case # Spectrum2D as output of the powder data self.x, self.y = np.meshgrid( spectra.x_data.magnitude, spectra.y_data.magnitude @@ -191,8 +208,10 @@ def get_spectra( # we don't need to curate the powder data, at variance with the single crystal case. # We can directly use them: self.x = spectra.x_data.magnitude[0] - self.y = self.y[:,0] + self.y = self.y[:, 0] self.z = spectra.z_data.magnitude.T + else: + raise ValueError("Spectrum type not recognized:", self.spectrum_type) def _get_qsection_spectra( self, @@ -227,23 +246,21 @@ def _get_qsection_spectra( temperature=self.parameters_qplanes.temperature, ) - self.av_spec, self.z, self.x, self.y, self.labels = ( - produce_Q_section_spectrum( - modes, - q_array, - h_array, - k_array, - ecenter=self.parameters_qplanes.ecenter, - deltaE=self.parameters_qplanes.deltaE, - bins=self.parameters_qplanes.bins, - spectrum_type=self.parameters_qplanes.spectrum_type, - dw=dw, - labels=labels, - ) + self.av_spec, self.z, self.x, self.y, self.labels = produce_Q_section_spectrum( + modes, + q_array, + h_array, + k_array, + ecenter=self.parameters_qplanes.ecenter, + deltaE=self.parameters_qplanes.deltaE, + bins=self.parameters_qplanes.bins, + spectrum_type=self.parameters_qplanes.spectrum_type, + dw=dw, + labels=labels, ) self.xlabel = "AAA" - self.ylabel = "AAA" - + self.ylabel = "AAA" + def _curate_path_and_labels( self, ): @@ -269,16 +286,33 @@ def _curate_path_and_labels( def produce_phonopy_files(self): # This is used to produce the phonopy files from # PhonopyCalculation data. The files are phonopy.yaml and force_constants.hdf5 - phonopy_yaml, fc_hdf5 = generate_force_constant_instance( + phonopy_yaml, fc_hdf5 = generate_force_constant_from_phonopy( self.node.phonon_bands.creator, mode="download" ) return phonopy_yaml, fc_hdf5 - + def prepare_data_for_download(self): raise NotImplementedError("Need to implement the download of a CSV file") - + # return data, filename + + @staticmethod + def _download(payload, filename): + from IPython.display import Javascript + + javas = Javascript( + """ + var link = document.createElement('a'); + link.href = 'data:text/json;charset=utf-8;base64,{payload}' + link.download = "{filename}" + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + """.format(payload=payload, filename=filename) + ) + display(javas) + def _clone(self): - # in case we want to clone the model. + # in case we want to clone the model. # This is the case when we have the same data and we inject in three # different models: we don't need to fetch three times. - return copy.deepcopy(self) \ No newline at end of file + return copy.deepcopy(self) diff --git a/src/aiidalab_qe_vibroscopy/app/widgets/euphonicwidget.py b/src/aiidalab_qe_vibroscopy/app/widgets/euphonicwidget.py index 8808fb3..a222f9f 100644 --- a/src/aiidalab_qe_vibroscopy/app/widgets/euphonicwidget.py +++ b/src/aiidalab_qe_vibroscopy/app/widgets/euphonicwidget.py @@ -1,19 +1,17 @@ -import pathlib -import tempfile import ipywidgets as ipw from IPython.display import display from aiidalab_qe.common.widgets import LoadingWidget -from aiidalab_qe_vibroscopy.utils.euphonic.data.structure_factors import ( - generate_force_constant_instance, +from aiidalab_qe_vibroscopy.app.widgets.structurefactorwidget import ( + EuphonicStructureFactorWidget, ) - -from aiidalab_qe_vibroscopy.app.widgets.structurefactorwidget import EuphonicStructureFactorWidget +from aiidalab_qe_vibroscopy.app.widgets.euphonicmodel import EuphonicResultsModel ##### START OVERALL WIDGET TO DISPLAY EVERYTHING: + class EuphonicWidget(ipw.VBox): """ Widget that will include everything, @@ -22,7 +20,11 @@ class EuphonicWidget(ipw.VBox): """ def __init__( - self, model: EuphonicResultsModel, node=None, detached_app = False, **kwargs, + self, + model: EuphonicResultsModel, + node=None, + detached_app=False, + **kwargs, ): """ Initialize the Euphonic utility class. @@ -50,11 +52,11 @@ def __init__( fc : optional Force constants if provided. """ - + super().__init__() - + self._model = model # this is the single crystal model. - if node: self._model.vibro = node + self._model.vibro = node self._model.detached_app = detached_app self._model.fc_hdf5_content = None @@ -63,7 +65,7 @@ def __init__( def render(self): if self.rendered: return - + self.tab_widget = ipw.Tab() self.tab_widget.layout.display = "none" self.tab_widget.set_title(0, "Single crystal") @@ -86,13 +88,18 @@ def render(self): if not self._model.detached_app: self.plot_button.disabled = False else: - from aiidalab_qe_vibroscopy.utils.euphonic.detached_app.uploadwidgets import UploadPhonopyWidget + from aiidalab_qe_vibroscopy.utils.euphonic.detached_app.uploadwidgets import ( + UploadPhonopyWidget, + ) + self.upload_widget = UploadPhonopyWidget() - self.upload_widget.reset_uploads.on_click(self._on_reset_uploads_button_clicked) + self.upload_widget.reset_uploads.on_click( + self._on_reset_uploads_button_clicked + ) self.upload_widget.children[0].observe(self._on_upload_yaml, "_counter") self.upload_widget.children[1].observe(self._on_upload_hdf5, "_counter") self.children += (self.upload_widget,) - + self.download_widget = DownloadYamlHdf5Widget(model=self._model) self.download_widget.layout.display = "none" @@ -101,8 +108,8 @@ def render(self): self.tab_widget, self.download_widget, self.loading_widget, - ) - + ) + self.rendered = True def _render_for_real(self, change=None): @@ -111,22 +118,23 @@ def _render_for_real(self, change=None): self.loading_widget.layout.display = "block" self._model.fetch_data() # should be in the model, but I can do it here once for all and then clone the model. - powder_model = self._model._clone() - qsection_model = self._model._clone() + powder_model = EuphonicResultsModel(spectum_type="powder") + qsection_model = EuphonicResultsModel(spectum_type="q_planes") + + for data in ["fc", "q_path"]: + setattr(powder_model, data, getattr(self._model, data)) + setattr(qsection_model, data, getattr(self._model, data)) # I first initialise this widget, to then have the 0K ref for the other two. # the model is passed to the widget. For the other two, I need to generate the model. - singlecrystalwidget = SingleCrystalFullWidget(model=self._model) - - # I need to generate the models for the other two widgets. - self._model._inject_single_crystal_settings() - powder_model._inject_powder_settings() - qsection_model._inject_qsection_settings() + singlecrystalwidget = EuphonicStructureFactorWidget( + node=self._model.vibro, model=self._model, spectrum_type="single_crystal" + ) self.tab_widget.children = ( singlecrystalwidget, - PowderFullWidget(model=powder_model), - QSectionFullWidget(model=qsection_model), + # EuphonicStructureFactorWidget(node=self._model.vibro, model=powder_model, spectrum_type="powder"), + # EuphonicStructureFactorWidget(node=self._model.vibro, model=qsection_model, spectrum_type="q_planes"), ) for widget in self.tab_widget.children: @@ -168,7 +176,8 @@ def _on_upload_hdf5(self, change): self._model.fc_hdf5_content = self.upload_widget.children[1].value[ fname ]["content"] - + + class DownloadYamlHdf5Widget(ipw.HBox): def __init__(self, model): self._model = model diff --git a/src/aiidalab_qe_vibroscopy/app/widgets/structurefactorwidget.py b/src/aiidalab_qe_vibroscopy/app/widgets/structurefactorwidget.py index 51857fc..1fee1f1 100644 --- a/src/aiidalab_qe_vibroscopy/app/widgets/structurefactorwidget.py +++ b/src/aiidalab_qe_vibroscopy/app/widgets/structurefactorwidget.py @@ -1,21 +1,40 @@ +import ipywidgets as ipw +from IPython.display import display + +import numpy as np + # ADD ALL THE IMPORTS. +import plotly.graph_objs as go + +from aiidalab_qe_vibroscopy.app.widgets.euphonicmodel import EuphonicResultsModel + +COLORSCALE = "Viridis" +COLORBAR_DICT = dict(orientation="v", showticklabels=False, x=1, thickness=10, len=0.4) class EuphonicStructureFactorWidget(ipw.VBox): """Description. - + Collects all the button and widget used to define settings for Neutron dynamic structure factor, in all the three cases: single crystal, powder, and q-section.... """ - def __init__(self, model, node=None, spectrum_type = "single_crystal", detached_app = False, **kwargs): + def __init__( + self, + model: EuphonicResultsModel, + node=None, + spectrum_type="single_crystal", + detached_app=False, + **kwargs, + ): super().__init__() self._model = model - if node: self._model.vibro = node + if node: + self._model.vibro = node self._model.spectrum_type = spectrum_type self._model.detached_app = detached_app self.rendered = False - + def render(self): """Render the widget. @@ -23,9 +42,9 @@ def render(self): """ if self.rendered: return - + self._init_view() - + slider_intensity = ipw.FloatRangeSlider( value=[1, 100], # Default selected interval min=1, @@ -67,7 +86,7 @@ def render(self): ) ipw.link( (self._model, "q_spacing"), - (self.q_spacing, "value"), + (q_spacing, "value"), ) q_spacing.observe(self._on_setting_change, names="value") @@ -148,10 +167,32 @@ def render(self): layout=ipw.Layout(width="auto"), ) download_button.on_click(self._download_data) - + + self.children += ( + ipw.HBox( + [ + slider_intensity, + specification_intensity, + ], + layout=ipw.Layout( + justify_content="space-between", + margin="10px 0", + ), + ), + E_units_button, + q_spacing, + energy_broadening, + energy_bins, + temperature, + weight_button, + plot_button, + reset_button, + download_button, + ) + if self._model.spectrum_type == "single_crystal": self.custom_kpath_description = ipw.HTML( - """ + """