Skip to content

Commit

Permalink
Tabs introduction.
Browse files Browse the repository at this point in the history
Missing several things:

- energy units conversion
- qplanes
- downloads.
  • Loading branch information
mikibonacci committed Dec 9, 2024
1 parent 27efadb commit 0a10129
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 42 deletions.
39 changes: 34 additions & 5 deletions src/aiidalab_qe_vibroscopy/app/widgets/euphonicmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ class EuphonicResultsModel(Model):
energy_bins = tl.Int(200)
temperature = tl.Float(0)
weighting = tl.Unicode("coherent")
energy_units = tl.Unicode("meV")
intensity_filter = tl.List(trait=tl.Float(), default_value=[0, 100])

THz_to_meV = 4.13566553853599 # conversion factor.
THz_to_cm1 = 33.3564095198155 # conversion factor.

def __init__(self, node=None, spectrum_type: str = "single_crystal", **kwargs):
super().__init__(**kwargs)
Expand Down Expand Up @@ -193,11 +198,13 @@ def get_spectra(
) = 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.energy_conversion_factor(
self.energy_units, "meV"
)
# self.x = None # we have, instead, the ticks positions and labels

self.xlabel = ""
self.ylabel = "Energy (meV)"
self.ylabel = f"Energy ({self.energy_units})"

elif self.spectrum_type == "powder": # powder case
# Spectrum2D as output of the powder data
Expand All @@ -207,8 +214,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.x = spectra.x_data.magnitude
self.y = self.y[:, 0] * self.energy_conversion_factor(
self.energy_units, "meV"
)
self.z = spectra.z_data.magnitude.T
else:
raise ValueError("Spectrum type not recognized:", self.spectrum_type)
Expand Down Expand Up @@ -261,6 +270,26 @@ def _get_qsection_spectra(
self.xlabel = "AAA"
self.ylabel = "AAA"

def energy_conversion_factor(self, new, old):
# TODO: check this is correct.
if new == old:
return 1
if new == "meV":
if old == "THz":
return self.THz_to_meV
elif old == "cm-1":
return self.THz_to_meV * self.THz_to_cm1
elif new == "THz":
if old == "meV":
return 1 / self.THz_to_meV
elif old == "cm-1":
return 1 / self.THz_to_cm1
elif new == "cm-1":
if old == "meV":
return 1 / self.THz_to_meV * self.THz_to_cm1
elif old == "THz":
return self.THz_to_cm1

def _curate_path_and_labels(
self,
):
Expand Down
12 changes: 8 additions & 4 deletions src/aiidalab_qe_vibroscopy/app/widgets/euphonicwidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ 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 = EuphonicResultsModel(spectum_type="powder")
qsection_model = EuphonicResultsModel(spectum_type="q_planes")
powder_model = EuphonicResultsModel(spectrum_type="powder")
qsection_model = EuphonicResultsModel(spectrum_type="q_planes")

for data in ["fc", "q_path"]:
setattr(powder_model, data, getattr(self._model, data))
Expand All @@ -133,8 +133,12 @@ def _render_for_real(self, change=None):

self.tab_widget.children = (
singlecrystalwidget,
# EuphonicStructureFactorWidget(node=self._model.vibro, model=powder_model, spectrum_type="powder"),
# EuphonicStructureFactorWidget(node=self._model.vibro, model=qsection_model, spectrum_type="q_planes"),
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:
Expand Down
110 changes: 77 additions & 33 deletions src/aiidalab_qe_vibroscopy/app/widgets/structurefactorwidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ def render(self):
width="400px",
),
)
ipw.link(
(slider_intensity, "value"),
(self._model, "intensity_filter"),
)
slider_intensity.observe(self._update_intensity_filter, "value")

specification_intensity = ipw.HTML(
"(Intensity is relative to the maximum intensity at T=0K)"
)
Expand All @@ -75,6 +80,10 @@ def render(self):
width="auto",
),
)
ipw.link(
(E_units_button, "value"),
(self._model, "energy_units"),
)
E_units_button.observe(self._update_energy_units, "value")
# MAYBE WE LINK ALSO THIS TO THE MODEL? so we can download the data with the preferred units.

Expand Down Expand Up @@ -113,17 +122,17 @@ def render(self):
)
energy_bins.observe(self._on_setting_change, names="value")

temperature = ipw.FloatText(
self.temperature = ipw.FloatText(
value=self._model.temperature,
step=0.01,
description="T (K)",
disabled=False,
)
ipw.link(
(self._model, "temperature"),
(temperature, "value"),
(self.temperature, "value"),
)
temperature.observe(self._on_setting_change, names="value")
self.temperature.observe(self._on_setting_change, names="value")

weight_button = ipw.ToggleButtons(
options=[
Expand All @@ -141,14 +150,14 @@ def render(self):
)
weight_button.observe(self._on_weight_button_change, names="value")

plot_button = ipw.Button(
self.plot_button = ipw.Button(
description="Replot",
icon="pencil",
button_style="primary",
disabled=True,
layout=ipw.Layout(width="auto"),
)
plot_button.observe(self._on_plot_button_change, names="disabled")
self.plot_button.on_click(self._update_plot)

reset_button = ipw.Button(
description="Reset",
Expand All @@ -159,14 +168,19 @@ def render(self):
)
reset_button.on_click(self._reset_settings)

download_button = ipw.Button(
self.download_button = ipw.Button(
description="Download Data and Plot",
icon="download",
button_style="primary",
disabled=False, # Large files...
layout=ipw.Layout(width="auto"),
)
download_button.on_click(self._download_data)
self.download_button.on_click(self._download_data)
ipw.dlink(
(self.plot_button, "disabled"),
(self.download_button, "disabled"),
lambda x: not x,
)

self.children += (
ipw.HBox(
Expand All @@ -183,11 +197,11 @@ def render(self):
q_spacing,
energy_broadening,
energy_bins,
temperature,
self.temperature,
weight_button,
plot_button,
self.plot_button,
reset_button,
download_button,
self.download_button,
)

if self._model.spectrum_type == "single_crystal":
Expand Down Expand Up @@ -256,6 +270,12 @@ def render(self):
(self.int_npts, "value"),
)
self.int_npts.observe(self._on_setting_change, names="value")
self.children += (
self.qmin,
self.qmax,
self.int_npts,
)

# fi self._model.spectrum_type == "powder"
elif self._model.spectrum_type == "q_planes":
self.ecenter = ipw.FloatText(
Expand Down Expand Up @@ -340,21 +360,28 @@ def render(self):
self.plot_button.description = "Plot"
# self.reset_button.disabled = True
self.download_button.disabled = True

self.children += (
self.ecenter,
self.plane_description_widget,
self.Q0_widget,
self.h_widget,
self.k_widget,
self.energy_broadening,
)
# fi self._model.spectrum_type == "q_planes"

self.children += (self.fig,)
self.children += (self.figure_container,)

self.rendered = True

def _init_view(self, _=None):
self._model.fetch_data()
if not hasattr(self, "fig"):
self.fig = go.FigureWidget()
self.figure_container = ipw.VBox([self.fig])
self._update_plot()

def _on_plot_button_change(self, change):
self.download_button.disabled = not change["new"]

def _on_weight_button_change(self, change):
self._model.temperature = 0
self.temperature.disabled = True if change["new"] == "dos" else False
Expand All @@ -365,24 +392,28 @@ def _on_setting_change(
): # think if we want to do something more evident...
self.plot_button.disabled = False

def _update_plot(self):
def _update_plot(self, _=None):
# update the spectra, i.e. the data contained in the _model.
# TODO: we need to treat differently the update of intensity and units.
# they anyway need to modify the data, but no additional spectra re-generation is really needed.
# so the update_spectra need some more logic, or we call another method.
self._model.get_spectra()

if not self.rendered:
if self._model.spectrum_type == "q_planes":
# hide figure until we have the data
self.figure_container.layout.display = "none"

# First time we render, we set several layout settings.
# Layout settings
if self._model.x:
if hasattr(self._model, "x"):
self.fig["layout"]["xaxis"].update(
title=self._model.xlabel,
range=[min(self._model.x), max(self._model.x)],
range=[np.min(self._model.x), np.max(self._model.x)],
)
self.fig["layout"]["yaxis"].update(
title=self._model.ylabel,
range=[min(self._model.y), max(self._model.y)],
range=[np.min(self._model.y), np.max(self._model.y)],
)

if self.fig.layout.images:
Expand All @@ -400,6 +431,8 @@ def _update_plot(self):

# Update the layout to enable autoscaling
self.fig.update_layout(autosize=True)
elif self._model.spectrum_type == "q_planes" and self.rendered:
self.figure_container.layout.display = "block"

heatmap_trace = go.Heatmap(
z=self._model.z,
Expand Down Expand Up @@ -430,26 +463,37 @@ def _update_plot(self):
self.fig.add_trace(heatmap_trace)
self.fig.data = [self.fig.data[-1]]

def _update_intensity_filter(self, change):
if self.rendered:
self._update_intensity_filter()

def _update_intensity_filter(self):
# the value of the intensity slider is in fractions of the max.
if change["new"] != change["old"]:
self.fig.data[0].zmax = (
change["new"][1] * np.max(self.fig.data[0].z) / 100
) # above this, it is all yellow, i.e. max intensity.
self.fig.data[0].zmin = (
change["new"][0] * np.max(self.fig.data[0].z) / 100
) # below this, it is all blue, i.e. zero intensity
# NOTE: we do this here, as we do not want to replot.
self.fig.data[0].zmax = (
self._model.intensity_filter[1] * np.max(self.fig.data[0].z) / 100
) # above this, it is all yellow, i.e. max intensity.
self.fig.data[0].zmin = (
self._model.intensity_filter[0] * np.max(self.fig.data[0].z) / 100
) # below this, it is all blue, i.e. zero intensity

def _update_energy_units(self, change):
# the value of the intensity slider is in fractions of the max.
if change["new"] != change["old"]:
self.fig.data[0].y = (
self.fig.data[0].y * self.THz_to_meV
if change["new"] == "meV"
else self.fig.data[0].y / self.THz_to_meV
)
self._model.energy_units = change["new"]
self.fig.data[0].y = (
np.array(self.fig.data[0].y)
* self._model.energy_conversion_factor(
new=self._model.energy_units, old=change["old"]
),
)

self.fig["layout"]["yaxis"].update(title=self._model.energy_units)

# Update x-axis and y-axis to enable autoscaling
self.fig.update_xaxes(autorange=True)
self.fig.update_yaxes(autorange=True)

self.fig["layout"]["yaxis"].update(title=change["new"])
# Update the layout to enable autoscaling
self.fig.update_layout(autosize=True)

def _reset_settings(self, _):
self._model.reset()
Expand Down

0 comments on commit 0a10129

Please sign in to comment.