diff --git a/src/aiidalab_qe_vibroscopy/app/result.py b/src/aiidalab_qe_vibroscopy/app/result.py index 36cc7c8..30dae04 100644 --- a/src/aiidalab_qe_vibroscopy/app/result.py +++ b/src/aiidalab_qe_vibroscopy/app/result.py @@ -211,7 +211,9 @@ def _update_view(self): # euphonic if ins_data: - intensity_maps = EuphonicSuperWidget(fc=ins_data["fc"]) + intensity_maps = EuphonicSuperWidget( + fc=ins_data["fc"], q_path=ins_data["q_path"] + ) children_result_widget += (intensity_maps,) tab_titles.append("Inelastic Neutrons") diff --git a/src/aiidalab_qe_vibroscopy/utils/euphonic/__init__.py b/src/aiidalab_qe_vibroscopy/utils/euphonic/__init__.py index d169a46..0d60410 100644 --- a/src/aiidalab_qe_vibroscopy/utils/euphonic/__init__.py +++ b/src/aiidalab_qe_vibroscopy/utils/euphonic/__init__.py @@ -18,6 +18,34 @@ ###### START for detached app: +# spinner for waiting time (supercell estimations) +spinner_html = """ + +
+
+
+""" + # Upload buttons class UploadPhonopyYamlWidget(ipw.FileUpload): @@ -111,7 +139,34 @@ class EuphonicSuperWidget(ipw.VBox): In between, we trigger the initialization of plots via a button. """ - def __init__(self, mode="aiidalab-qe app plugin", fc=None): + def __init__(self, mode="aiidalab-qe app plugin", fc=None, q_path=None): + """ + Initialize the Euphonic utility class. + Parameters: + ----------- + mode : str, optional + The mode of operation, default is "aiidalab-qe app plugin". + fc : optional + Force constants, default is None. + q_path : optional + Q-path for phonon calculations, default is None. If Low-D system, this can be provided. + It is the same path obtained for the PhonopyCalculation of the phonopy_bands. + Attributes: + ----------- + mode : str + The mode of operation. + upload_widget : UploadPhonopyWidget + Widget for uploading phonopy files. + fc_hdf5_content : None + Content of the force constants HDF5 file. + tab_widget : ipw.Tab + Tab widget for different views. + plot_button : ipw.Button + Button to initialize INS data. + fc : optional + Force constants if provided. + """ + self.mode = mode self.upload_widget = UploadPhonopyWidget() @@ -128,6 +183,8 @@ def __init__(self, mode="aiidalab-qe app plugin", fc=None): if fc: self.fc = fc + self.q_path = q_path + self.plot_button = ipw.Button( description="Initialise INS data", icon="pencil", @@ -137,6 +194,11 @@ def __init__(self, mode="aiidalab-qe app plugin", fc=None): ) self.plot_button.on_click(self._on_first_plot_button_clicked) + self.loading_widget = ipw.HTML( + value=spinner_html, + ) + self.loading_widget.layout.display = "none" + if self.mode == "aiidalab-qe app plugin": self.upload_widget.layout.display = "none" self.plot_button.disabled = False @@ -148,6 +210,7 @@ def __init__(self, mode="aiidalab-qe app plugin", fc=None): children=[ self.upload_widget, self.plot_button, + self.loading_widget, self.tab_widget, ], ) @@ -203,10 +266,13 @@ def _generate_force_constants( def _on_first_plot_button_clicked(self, change=None): # It creates the widgets self.plot_button.layout.display = "none" + + self.loading_widget.layout.display = "block" + self.fc = self._generate_force_constants() # I first initialise this widget, to then have the 0K ref for the other two. - singlecrystalwidget = SingleCrystalFullWidget(self.fc) + singlecrystalwidget = SingleCrystalFullWidget(self.fc, self.q_path) self.tab_widget.children = ( singlecrystalwidget, @@ -218,6 +284,8 @@ def _on_first_plot_button_clicked(self, change=None): ), ) + self.loading_widget.layout.display = "none" + self.tab_widget.layout.display = "block" diff --git a/src/aiidalab_qe_vibroscopy/utils/euphonic/euphonic_single_crystal_widgets.py b/src/aiidalab_qe_vibroscopy/utils/euphonic/euphonic_single_crystal_widgets.py index 1437772..f03df86 100644 --- a/src/aiidalab_qe_vibroscopy/utils/euphonic/euphonic_single_crystal_widgets.py +++ b/src/aiidalab_qe_vibroscopy/utils/euphonic/euphonic_single_crystal_widgets.py @@ -2,6 +2,7 @@ from IPython.display import display import numpy as np +import copy import ipywidgets as ipw import plotly.graph_objects as go import plotly.io as pio @@ -196,11 +197,13 @@ class SingleCrystalFullWidget(ipw.VBox): and are from Sears (1992) Neutron News 3(3) pp26--37. """ - def __init__(self, fc, **kwargs): + def __init__(self, fc, q_path, **kwargs): self.fc = fc + self.q_path = q_path self.spectra, self.parameters = produce_bands_weigthed_data( fc=self.fc, + linear_path=self.q_path, plot=False, # CHANGED ) @@ -248,7 +251,9 @@ def _on_plot_button_clicked(self, change=None): "delta_q": parameters_["q_spacing"], } else: - linear_path = None + linear_path = copy.deepcopy(self.q_path) + if linear_path: + linear_path["delta_q"] = parameters_["q_spacing"] self.spectra, self.parameters = produce_bands_weigthed_data( params=parameters_, diff --git a/src/aiidalab_qe_vibroscopy/utils/euphonic/intensity_maps.py b/src/aiidalab_qe_vibroscopy/utils/euphonic/intensity_maps.py index 0bc1a0e..6a7490b 100644 --- a/src/aiidalab_qe_vibroscopy/utils/euphonic/intensity_maps.py +++ b/src/aiidalab_qe_vibroscopy/utils/euphonic/intensity_maps.py @@ -299,6 +299,21 @@ def produce_bands_weigthed_data( x_tick_labels = get_qpoint_labels( modes.qpts, cell=modes.crystal.to_spglib_cell() ) + + # duplication from euphonic/cli/utils.py + if args.e_min is None: + # Subtract small amount from min frequency - otherwise due to unit + # conversions binning of this frequency can vary with different + # architectures/lib versions, making it difficult to test + emin_room = 1e-5 * ureg("meV").to(modes.frequencies.units).magnitude + args.e_min = min(np.min(modes.frequencies.magnitude - emin_room), 0.0) + if args.e_max is None: + args.e_max = np.max(modes.frequencies.magnitude) * 1.05 + if args.e_min >= args.e_max: + raise ValueError( + f"Maximum energy ({args.e_max}) should be greater than minimum ({args.e_min}). " + ) + modes.frequencies_unit = args.energy_unit ebins = _get_energy_bins(modes, args.ebins + 1, emin=args.e_min, emax=args.e_max) @@ -753,12 +768,36 @@ def export_euphonic_data(node, fermi_energy=None): output_set = node.outputs.vibronic.phonon_bands + if any(not element for element in node.inputs.structure.pbc): + vibro_bands = node.inputs.vibronic.phonopy_bands_dict.get_dict() + # Group the band and band_labels + band = vibro_bands["band"] + band_labels = vibro_bands["band_labels"] + + grouped_bands = [ + item + for sublist in [band_labels[i : i + 2] for i in range(len(band_labels) - 1)] + for item in sublist + ] + grouped_q = [ + [tuple(band[i : i + 3]), tuple(band[i + 3 : i + 6])] + for i in range(0, len(band) - 3, 3) + ] + q_path = { + "coordinates": grouped_q, + "labels": grouped_bands, + "delta_q": 0.01, # 1/A + } + else: + q_path = None + phonopy_calc = output_set.creator fc = generate_force_constant_instance(phonopy_calc) # bands = compute_bands(fc) # pdos = compute_pdos(fc) return { "fc": fc, + "q_path": q_path, } # "bands": bands, "pdos": pdos, "thermal": None}