diff --git a/pyproject.toml b/pyproject.toml
index c8381cb..ce4f9a8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -25,7 +25,7 @@ dependencies = [
"pre-commit",
"euphonic",
"kaleido",
- "weas-widget==0.1.15",
+ "weas-widget==0.1.19",
]
[tool.ruff.lint]
diff --git a/src/aiidalab_qe_vibroscopy/app/result.py b/src/aiidalab_qe_vibroscopy/app/result.py
index d62b276..2f6f710 100644
--- a/src/aiidalab_qe_vibroscopy/app/result.py
+++ b/src/aiidalab_qe_vibroscopy/app/result.py
@@ -8,7 +8,6 @@
from IPython.display import display
import numpy as np
-from ..utils.raman.result import export_iramanworkchain_data
from ..utils.phonons.result import export_phononworkchain_data
from ..utils.euphonic import (
@@ -19,8 +18,6 @@
import plotly.graph_objects as go
import ipywidgets as ipw
-from ..utils.raman.result import SpectrumPlotWidget, ActiveModesWidget
-
class PhononBandPdosPlotly(BandPdosPlotly):
def __init__(self, bands_data=None, pdos_data=None):
@@ -82,7 +79,6 @@ def _update_view(self):
children_result_widget = ()
tab_titles = [] # this is needed to name the sub panels
- spectra_data = export_iramanworkchain_data(self.node)
phonon_data = export_phononworkchain_data(self.node)
ins_data = export_euphonic_data(self.node)
@@ -164,48 +160,6 @@ def _update_view(self):
),
) # the comma is required! otherwise the tuple is not detected.
- if spectra_data:
- # Here we should provide the possibility to have both IR and Raman,
- # as the new logic can provide both at the same time.
- # We are gonna use the same widget, providing the correct spectrum_type: "Raman" or "Ir".
- children_spectra = ()
- for spectrum, data in spectra_data.items():
- if not data:
- continue
-
- elif isinstance(data, str):
- # No Modes are detected. So we explain why
- no_mode_widget = ipw.HTML(data)
- explanation_widget = ipw.HTML(
- "This may be due to the fact that the current implementation of aiida-vibroscopy plugin only considers first-order effects."
- )
-
- children_spectra += (
- ipw.VBox([no_mode_widget, explanation_widget]),
- )
-
- else:
- subwidget_title = ipw.HTML(f"
{spectrum} spectroscopy
")
- spectrum_widget = SpectrumPlotWidget(
- node=self.node, output_node=data, spectrum_type=spectrum
- )
- modes_animation = ActiveModesWidget(
- node=self.node, output_node=data, spectrum_type=spectrum
- )
-
- children_spectra += (
- ipw.VBox([subwidget_title, spectrum_widget, modes_animation]),
- )
- children_result_widget += (
- ipw.VBox(
- children=children_spectra,
- layout=ipw.Layout(
- width="100%",
- ),
- ),
- )
- tab_titles.append("Raman/IR spectra")
-
# euphonic
if ins_data:
intensity_maps = EuphonicSuperWidget(
diff --git a/src/aiidalab_qe_vibroscopy/app/result/model.py b/src/aiidalab_qe_vibroscopy/app/result/model.py
index d4862c3..a723014 100644
--- a/src/aiidalab_qe_vibroscopy/app/result/model.py
+++ b/src/aiidalab_qe_vibroscopy/app/result/model.py
@@ -2,10 +2,6 @@
import traitlets as tl
-from aiidalab_qe_vibroscopy.utils.phonons.result import export_phononworkchain_data
-from aiidalab_qe_vibroscopy.utils.euphonic import export_euphonic_data
-
-
class VibroResultsModel(ResultsModel):
identifier = "vibronic"
@@ -30,8 +26,16 @@ def needs_raman_tab(self):
# Here we use _fetch_child_process_node() since the function needs the input_structure in inputs
def needs_phonons_tab(self):
- return export_phononworkchain_data(self._fetch_child_process_node())
+ node = self.get_vibro_node()
+ if not any(
+ key in node for key in ["phonon_bands", "phonon_thermo", "phonon_pdos"]
+ ):
+ return False
+ return True
# Here we use _fetch_child_process_node() since the function needs the input_structure in inputs
def needs_euphonic_tab(self):
- return export_euphonic_data(self._fetch_child_process_node())
+ node = self.get_vibro_node()
+ if not any(key in node for key in ["phonon_bands"]):
+ return False
+ return True
diff --git a/src/aiidalab_qe_vibroscopy/app/result/result.py b/src/aiidalab_qe_vibroscopy/app/result/result.py
index ab24155..ba8001e 100644
--- a/src/aiidalab_qe_vibroscopy/app/result/result.py
+++ b/src/aiidalab_qe_vibroscopy/app/result/result.py
@@ -7,8 +7,8 @@
from aiidalab_qe_vibroscopy.app.widgets.dielectricmodel import DielectricModel
import ipywidgets as ipw
-from aiidalab_qe_vibroscopy.app.widgets.ramanwidget import RamanWidget
-from aiidalab_qe_vibroscopy.app.widgets.ramanmodel import RamanModel
+from aiidalab_qe_vibroscopy.app.widgets.ir_ramanwidget import IRRamanWidget
+from aiidalab_qe_vibroscopy.app.widgets.ir_ramanmodel import IRRamanModel
class VibroResultsPanel(ResultsPanel[VibroResultsModel]):
@@ -37,12 +37,16 @@ def render(self):
needs_raman_tab = self._model.needs_raman_tab()
if needs_raman_tab:
- raman_model = RamanModel()
- raman_widget = RamanWidget(
- model=raman_model,
+ vibroscopy_node = self._model._fetch_child_process_node()
+ input_structure = vibroscopy_node.inputs.structure.get_ase()
+ irraman_model = IRRamanModel()
+ irraman_widget = IRRamanWidget(
+ model=irraman_model,
node=vibro_node,
+ input_structure=input_structure,
)
- tab_data.append(("Raman", raman_widget))
+
+ tab_data.append(("Raman/IR spectra", irraman_widget))
needs_dielectri_tab = self._model.needs_dielectric_tab()
diff --git a/src/aiidalab_qe_vibroscopy/app/widgets/ir_ramanmodel.py b/src/aiidalab_qe_vibroscopy/app/widgets/ir_ramanmodel.py
new file mode 100644
index 0000000..8d9e604
--- /dev/null
+++ b/src/aiidalab_qe_vibroscopy/app/widgets/ir_ramanmodel.py
@@ -0,0 +1,26 @@
+from __future__ import annotations
+from aiidalab_qe.common.mvc import Model
+from aiida.common.extendeddicts import AttributeDict
+from ase.atoms import Atoms
+import traitlets as tl
+
+from aiidalab_qe_vibroscopy.utils.raman.result import export_iramanworkchain_data
+
+
+class IRRamanModel(Model):
+ vibro = tl.Instance(AttributeDict, allow_none=True)
+ input_structure = tl.Instance(Atoms, allow_none=True)
+
+ needs_raman_tab = tl.Bool()
+ needs_ir_tab = tl.Bool()
+
+ def fetch_data(self):
+ spectra_data = export_iramanworkchain_data(self.vibro)
+ if spectra_data["Ir"] == "No IR modes detected.":
+ self.needs_ir_tab = False
+ else:
+ self.needs_ir_tab = True
+ if spectra_data["Raman"] == "No Raman modes detected.":
+ self.needs_raman_tab = False
+ else:
+ self.needs_raman_tab = True
diff --git a/src/aiidalab_qe_vibroscopy/app/widgets/ir_ramanwidget.py b/src/aiidalab_qe_vibroscopy/app/widgets/ir_ramanwidget.py
new file mode 100644
index 0000000..3666076
--- /dev/null
+++ b/src/aiidalab_qe_vibroscopy/app/widgets/ir_ramanwidget.py
@@ -0,0 +1,58 @@
+from aiidalab_qe_vibroscopy.app.widgets.ir_ramanmodel import IRRamanModel
+from aiidalab_qe_vibroscopy.app.widgets.ramanwidget import RamanWidget
+from aiidalab_qe_vibroscopy.app.widgets.ramanmodel import RamanModel
+import ipywidgets as ipw
+
+
+class IRRamanWidget(ipw.VBox):
+ def __init__(self, model: IRRamanModel, node: None, input_structure, **kwargs):
+ super().__init__(
+ children=[ipw.HTML("Loading Raman data...")],
+ **kwargs,
+ )
+ self._model = model
+ self._model.vibro = node
+ self._model.input_structure = input_structure
+ self.rendered = False
+
+ def render(self):
+ if self.rendered:
+ return
+
+ self.children = []
+
+ self.rendered = True
+ self._model.fetch_data()
+ self._needs_raman_widget()
+ self._needs_ir_widget()
+ self.render_widgets()
+
+ def _needs_raman_widget(self):
+ if self._model.needs_raman_tab:
+ self.raman_model = RamanModel()
+ self.raman_widget = RamanWidget(
+ model=self.raman_model,
+ node=self._model.vibro,
+ input_structure=self._model.input_structure,
+ spectrum_type="Raman",
+ )
+ self.children = (*self.children, self.raman_widget)
+
+ def _needs_ir_widget(self):
+ if self._model.needs_ir_tab:
+ self.ir_model = RamanModel()
+ self.ir_widget = RamanWidget(
+ model=self.ir_model,
+ node=self._model.vibro,
+ input_structure=self._model.input_structure,
+ spectrum_type="IR",
+ )
+ self.children = (*self.children, self.ir_widget)
+ else:
+ self.children = (*self.children, ipw.HTML("No IR modes detected."))
+
+ def render_widgets(self):
+ if self._model.needs_raman_tab:
+ self.raman_widget.render()
+ if self._model.needs_ir_tab:
+ self.ir_widget.render()
diff --git a/src/aiidalab_qe_vibroscopy/app/widgets/ramanmodel.py b/src/aiidalab_qe_vibroscopy/app/widgets/ramanmodel.py
index 46e9281..c1bebfa 100644
--- a/src/aiidalab_qe_vibroscopy/app/widgets/ramanmodel.py
+++ b/src/aiidalab_qe_vibroscopy/app/widgets/ramanmodel.py
@@ -2,6 +2,7 @@
from aiidalab_qe.common.mvc import Model
import traitlets as tl
from aiida.common.extendeddicts import AttributeDict
+from ase.atoms import Atoms
from IPython.display import display
import numpy as np
from aiida_vibroscopy.utils.broadenings import multilorentz
@@ -12,21 +13,24 @@
class RamanModel(Model):
vibro = tl.Instance(AttributeDict, allow_none=True)
+ input_structure = tl.Instance(Atoms, allow_none=True)
+ spectrum_type = tl.Unicode()
- raman_plot_type_options = tl.List(
+ plot_type_options = tl.List(
trait=tl.List(tl.Unicode()),
default_value=[
("Powder", "powder"),
("Single Crystal", "single_crystal"),
],
)
- raman_plot_type = tl.Unicode("powder")
- raman_temperature = tl.Float(300)
- raman_frequency_laser = tl.Float(532)
- raman_pol_incoming = tl.Unicode("0 0 1")
- raman_pol_outgoing = tl.Unicode("0 0 1")
- raman_broadening = tl.Float(10.0)
- raman_separate_polarizations = tl.Bool(False)
+
+ plot_type = tl.Unicode("powder")
+ temperature = tl.Float(300)
+ frequency_laser = tl.Float(532)
+ pol_incoming = tl.Unicode("0 0 1")
+ pol_outgoing = tl.Unicode("0 0 1")
+ broadening = tl.Float(10.0)
+ separate_polarizations = tl.Bool(False)
frequencies = []
intensities = []
@@ -34,72 +38,124 @@ class RamanModel(Model):
frequencies_depolarized = []
intensities_depolarized = []
+ # Active modes
+ active_modes_options = tl.List(
+ trait=tl.Tuple((tl.Unicode(), tl.Int())),
+ )
+ active_mode = tl.Int(0)
+ amplitude = tl.Float(3.0)
+
+ supercell_0 = tl.Int(1)
+ supercell_1 = tl.Int(1)
+ supercell_2 = tl.Int(1)
+
def fetch_data(self):
"""Fetch the Raman data from the VibroWorkChain"""
self.raman_data = self.get_vibrational_data(self.vibro)
+ self.raw_frequencies, self.eigenvectors, self.labels = (
+ self.raman_data.run_active_modes(
+ selection_rule=self.spectrum_type.lower(),
+ )
+ )
+ self.rounded_frequencies = [
+ round(frequency, 3) for frequency in self.raw_frequencies
+ ]
+ self.active_modes_options = self._get_active_modes_options()
+
+ def _get_active_modes_options(self):
+ active_modes_options = [
+ (f"{index + 1}: {value}", index)
+ for index, value in enumerate(self.rounded_frequencies)
+ ]
+
+ return active_modes_options
def update_data(self):
"""
- Update the Raman plot data based on the selected plot type and configuration.
+ Update the plot data based on the selected spectrum type, plot type, and configuration.
"""
- if self.raman_plot_type == "powder":
+ if self.plot_type == "powder":
self._update_powder_data()
else:
self._update_single_crystal_data()
def _update_powder_data(self):
"""
- Update data for the powder Raman plot.
+ Update data for the powder plot, handling both Raman and IR spectra.
"""
- (
- polarized_intensities,
- depolarized_intensities,
- frequencies,
- _,
- ) = self.raman_data.run_powder_raman_intensities(
- frequencies=self.raman_frequency_laser,
- temperature=self.raman_temperature,
- )
-
- if self.raman_separate_polarizations:
- self.frequencies, self.intensities = self.generate_plot_data(
- frequencies,
+ if self.spectrum_type == "Raman":
+ (
polarized_intensities,
- self.raman_broadening,
+ depolarized_intensities,
+ frequencies,
+ _,
+ ) = self.raman_data.run_powder_raman_intensities(
+ frequencies=self.frequency_laser,
+ temperature=self.temperature,
)
- self.frequencies_depolarized, self.intensities_depolarized = (
- self.generate_plot_data(
+
+ if self.separate_polarizations:
+ self.frequencies, self.intensities = self.generate_plot_data(
frequencies,
- depolarized_intensities,
- self.raman_broadening,
+ polarized_intensities,
+ self.broadening,
)
- )
- else:
- combined_intensities = polarized_intensities + depolarized_intensities
+ self.frequencies_depolarized, self.intensities_depolarized = (
+ self.generate_plot_data(
+ frequencies,
+ depolarized_intensities,
+ self.broadening,
+ )
+ )
+ else:
+ combined_intensities = polarized_intensities + depolarized_intensities
+ self.frequencies, self.intensities = self.generate_plot_data(
+ frequencies,
+ combined_intensities,
+ self.broadening,
+ )
+ self.frequencies_depolarized, self.intensities_depolarized = [], []
+
+ elif self.spectrum_type == "IR":
+ (
+ intensities,
+ frequencies,
+ _,
+ ) = self.raman_data.run_powder_ir_intensities()
self.frequencies, self.intensities = self.generate_plot_data(
frequencies,
- combined_intensities,
- self.raman_broadening,
+ intensities,
+ self.broadening,
)
self.frequencies_depolarized, self.intensities_depolarized = [], []
def _update_single_crystal_data(self):
"""
- Update data for the single crystal Raman plot.
+ Update data for the single crystal plot, handling both Raman and IR spectra.
"""
- dir_incoming, _ = self._check_inputs_correct(self.raman_pol_incoming)
- dir_outgoing, _ = self._check_inputs_correct(self.raman_pol_outgoing)
-
- (
- intensities,
- frequencies,
- labels,
- ) = self.raman_data.run_single_crystal_raman_intensities(
- pol_incoming=dir_incoming,
- pol_outgoing=dir_outgoing,
- frequencies=self.raman_frequency_laser,
- temperature=self.raman_temperature,
- )
+ dir_incoming, _ = self._check_inputs_correct(self.pol_incoming)
+
+ if self.spectrum_type == "Raman":
+ dir_outgoing, _ = self._check_inputs_correct(self.pol_outgoing)
+ (
+ intensities,
+ frequencies,
+ _,
+ ) = self.raman_data.run_single_crystal_raman_intensities(
+ pol_incoming=dir_incoming,
+ pol_outgoing=dir_outgoing,
+ frequencies=self.frequency_laser,
+ temperature=self.temperature,
+ )
+ elif self.spectrum_type == "IR":
+ (
+ intensities,
+ frequencies,
+ _,
+ ) = self.raman_data.run_single_crystal_ir_intensities(
+ pol_incoming=dir_incoming
+ )
+
self.frequencies, self.intensities = self.generate_plot_data(
frequencies, intensities
)
@@ -114,7 +170,7 @@ def update_plot(self, plot):
"""
update_function = (
self._update_powder_plot
- if self.raman_plot_type == "powder"
+ if self.plot_type == "powder"
else self._update_single_crystal_plot
)
update_function(plot)
@@ -126,7 +182,7 @@ def _update_powder_plot(self, plot):
Parameters:
plot: The plotly.graph_objs.Figure widget to update.
"""
- if self.raman_separate_polarizations:
+ if self.separate_polarizations:
self._update_polarized_and_depolarized(plot)
else:
self._clear_depolarized_and_update(plot)
@@ -149,7 +205,7 @@ def _update_polarized_and_depolarized(self, plot):
name="Depolarized",
)
)
- plot.layout.title.text = "Powder Raman Spectrum"
+ plot.layout.title.text = f"Powder {self.spectrum_type} Spectrum"
elif len(plot.data) == 2:
self._update_trace(
plot.data[0], self.frequencies, self.intensities, "Polarized"
@@ -160,7 +216,7 @@ def _update_polarized_and_depolarized(self, plot):
self.intensities_depolarized,
"Depolarized",
)
- plot.layout.title.text = "Powder Raman Spectrum"
+ plot.layout.title.text = f"Powder {self.spectrum_type} Spectrum"
def _clear_depolarized_and_update(self, plot):
"""
@@ -173,10 +229,10 @@ def _clear_depolarized_and_update(self, plot):
self._update_trace(plot.data[0], self.frequencies, self.intensities, "")
plot.data[1].x = []
plot.data[1].y = []
- plot.layout.title.text = "Powder Raman Spectrum"
+ plot.layout.title.text = f"Powder{self.spectrum_type} Spectrum"
elif len(plot.data) == 1:
self._update_trace(plot.data[0], self.frequencies, self.intensities, "")
- plot.layout.title.text = "Powder Raman Spectrum"
+ plot.layout.title.text = f"Powder{self.spectrum_type} Spectrum"
def _update_single_crystal_plot(self, plot):
"""
@@ -189,10 +245,10 @@ def _update_single_crystal_plot(self, plot):
self._update_trace(plot.data[0], self.frequencies, self.intensities, "")
plot.data[1].x = []
plot.data[1].y = []
- plot.layout.title.text = "Single Crystal Raman Spectrum"
+ plot.layout.title.text = f"Single Crystal {self.spectrum_type} Spectrum"
elif len(plot.data) == 1:
self._update_trace(plot.data[0], self.frequencies, self.intensities, "")
- plot.layout.title.text = "Single Crystal Raman Spectrum"
+ plot.layout.title.text = f"Single Crystal {self.spectrum_type} Spectrum"
def _update_trace(self, trace, x_data, y_data, name):
"""
@@ -273,9 +329,45 @@ def generate_plot_data(
return x_range, y_range
+ def modes_table(self):
+ """Display table with the active modes."""
+ # Create an HTML table with the active modes
+ table_data = [list(x) for x in zip(self.rounded_frequencies, self.labels)]
+ table_html = ""
+ table_html += "Frequencies (cm-1) | Label |
"
+ for row in table_data:
+ table_html += ""
+ for cell in row:
+ table_html += "{} | ".format(cell)
+ table_html += "
"
+ table_html += "
"
+
+ return table_html
+
+ def set_vibrational_mode_animation(self, weas):
+ eigenvector = self.eigenvectors[self.active_mode]
+ phonon_setting = {
+ "eigenvectors": np.array(
+ [[[real_part, 0] for real_part in row] for row in eigenvector]
+ ),
+ "kpoint": [0, 0, 0], # optional
+ "amplitude": self.amplitude,
+ "factor": self.amplitude * 0.6,
+ "nframes": 20,
+ "repeat": [
+ self.supercell_0,
+ self.supercell_1,
+ self.supercell_2,
+ ],
+ "color": "black",
+ "radius": 0.1,
+ }
+ weas.avr.phonon_setting = phonon_setting
+ return weas
+
def download_data(self, _=None):
filename = "spectra.json"
- if self.raman_separate_polarizations:
+ if self.separate_polarizations:
my_dict = {
"Frequencies cm-1": self.frequencies.tolist(),
"Polarized intensities": self.intensities.tolist(),
diff --git a/src/aiidalab_qe_vibroscopy/app/widgets/ramanwidget.py b/src/aiidalab_qe_vibroscopy/app/widgets/ramanwidget.py
index 085fa7e..503ead8 100644
--- a/src/aiidalab_qe_vibroscopy/app/widgets/ramanwidget.py
+++ b/src/aiidalab_qe_vibroscopy/app/widgets/ramanwidget.py
@@ -2,6 +2,8 @@
from aiidalab_qe_vibroscopy.app.widgets.ramanmodel import RamanModel
import plotly.graph_objects as go
from aiidalab_widgets_base.utils import StatusHTML
+from IPython.display import HTML, clear_output, display
+from weas_widget import WeasWidget
class RamanWidget(ipw.VBox):
@@ -9,100 +11,118 @@ class RamanWidget(ipw.VBox):
Widget to display Raman properties Tab
"""
- def __init__(self, model: RamanModel, node: None, **kwargs):
+ def __init__(
+ self, model: RamanModel, node: None, input_structure, spectrum_type, **kwargs
+ ):
super().__init__(
children=[ipw.HTML("Loading Raman data...")],
**kwargs,
)
self._model = model
+ self._model.spectrum_type = spectrum_type
self._model.vibro = node
+ self._model.input_structure = input_structure
self.rendered = False
def render(self):
if self.rendered:
return
- self.raman_plot_type = ipw.ToggleButtons(
+ self.guiConfig = {
+ "enabled": True,
+ "components": {
+ "atomsControl": True,
+ "buttons": True,
+ "cameraControls": True,
+ },
+ "buttons": {
+ "fullscreen": True,
+ "download": True,
+ "measurement": True,
+ },
+ }
+
+ self.plot_type = ipw.ToggleButtons(
description="Spectrum type:",
style={"description_width": "initial"},
)
ipw.dlink(
- (self._model, "raman_plot_type_options"),
- (self.raman_plot_type, "options"),
+ (self._model, "plot_type_options"),
+ (self.plot_type, "options"),
)
ipw.link(
- (self._model, "raman_plot_type"),
- (self.raman_plot_type, "value"),
+ (self._model, "plot_type"),
+ (self.plot_type, "value"),
)
- self.raman_plot_type.observe(self._on_raman_plot_type_change, names="value")
- self.raman_temperature = ipw.FloatText(
+ self.plot_type.observe(self._on_plot_type_change, names="value")
+ self.temperature = ipw.FloatText(
description="Temperature (K):",
style={"description_width": "initial"},
)
ipw.link(
- (self._model, "raman_temperature"),
- (self.raman_temperature, "value"),
+ (self._model, "temperature"),
+ (self.temperature, "value"),
)
- self.raman_frequency_laser = ipw.FloatText(
+ self.frequency_laser = ipw.FloatText(
description="Laser frequency (nm):",
style={"description_width": "initial"},
)
ipw.link(
- (self._model, "raman_frequency_laser"),
- (self.raman_frequency_laser, "value"),
+ (self._model, "frequency_laser"),
+ (self.frequency_laser, "value"),
)
- self.raman_pol_incoming = ipw.Text(
+ self.pol_incoming = ipw.Text(
description="Incoming polarization:",
style={"description_width": "initial"},
layout=ipw.Layout(visibility="hidden"),
)
ipw.link(
- (self._model, "raman_pol_incoming"),
- (self.raman_pol_incoming, "value"),
+ (self._model, "pol_incoming"),
+ (self.pol_incoming, "value"),
)
- self.raman_pol_outgoing = ipw.Text(
+ self.pol_outgoing = ipw.Text(
description="Outgoing polarization:",
style={"description_width": "initial"},
layout=ipw.Layout(visibility="hidden"),
)
ipw.link(
- (self._model, "raman_pol_outgoing"),
- (self.raman_pol_outgoing, "value"),
+ (self._model, "pol_outgoing"),
+ (self.pol_outgoing, "value"),
)
- self.raman_plot_button = ipw.Button(
+ self.plot_button = ipw.Button(
description="Update Plot",
icon="pencil",
button_style="primary",
layout=ipw.Layout(width="auto"),
)
- self.raman_plot_button.on_click(self._on_raman_plot_button_click)
- self.raman_download_button = ipw.Button(
+ self.plot_button.on_click(self._on_plot_button_click)
+ self.download_button = ipw.Button(
description="Download Data",
icon="download",
button_style="primary",
layout=ipw.Layout(width="auto"),
)
- self.raman_download_button.on_click(self._model.download_data)
+ self.download_button.on_click(self._model.download_data)
self._wrong_syntax = StatusHTML(clear_after=8)
- self.raman_broadening = ipw.FloatText(
+ self.broadening = ipw.FloatText(
description="Broadening (cm-1):",
style={"description_width": "initial"},
)
ipw.link(
- (self._model, "raman_broadening"),
- (self.raman_broadening, "value"),
+ (self._model, "broadening"),
+ (self.broadening, "value"),
)
- self.raman_separate_polarized = ipw.Checkbox(
+ self.separate_polarizations = ipw.Checkbox(
description="Separate polarized and depolarized intensities",
style={"description_width": "initial"},
)
ipw.link(
- (self._model, "raman_separate_polarizations"),
- (self.raman_separate_polarized, "value"),
+ (self._model, "separate_polarizations"),
+ (self.separate_polarizations, "value"),
)
- self.raman_spectrum = go.FigureWidget(
+ self.spectrum = go.FigureWidget(
layout=go.Layout(
title=dict(text="Powder Raman spectrum"),
barmode="overlay",
@@ -119,51 +139,149 @@ def render(self):
)
)
+ # Active Modes
+ self.modes_table = ipw.Output()
+ self.animation = ipw.Output()
+
+ self.active_modes = ipw.Dropdown(
+ description="Select mode:",
+ style={"description_width": "initial"},
+ )
+ ipw.dlink(
+ (self._model, "active_modes_options"),
+ (self.active_modes, "options"),
+ )
+ self.amplitude = ipw.FloatText(
+ description="Amplitude :",
+ style={"description_width": "initial"},
+ )
+ ipw.link(
+ (self._model, "amplitude"),
+ (self.amplitude, "value"),
+ )
+ self._supercell = [
+ ipw.BoundedIntText(min=1, layout={"width": "40px"}),
+ ipw.BoundedIntText(min=1, layout={"width": "40px"}),
+ ipw.BoundedIntText(min=1, layout={"width": "40px"}),
+ ]
+ for i, widget in enumerate(self._supercell):
+ ipw.link(
+ (self._model, f"supercell_{i}"),
+ (widget, "value"),
+ )
+
+ self.supercell_selector = ipw.HBox(
+ [
+ ipw.HTML(
+ description="Super cell:", style={"description_width": "initial"}
+ )
+ ]
+ + self._supercell
+ )
+ # WeasWidget Setting
+ self.weas = WeasWidget(guiConfig=self.guiConfig)
+ self.weas.from_ase(self._model.input_structure)
+ self.weas.avr.model_style = 1
+ self.weas.avr.color_type = "JMOL"
+
+ widget_list = [
+ self.active_modes,
+ self.amplitude,
+ self._supercell[0],
+ self._supercell[1],
+ self._supercell[2],
+ ]
+ for elem in widget_list:
+ elem.observe(self._select_active_mode, names="value")
+
self.children = [
- ipw.HTML("Raman spectroscopy
"),
+ ipw.HTML(f"{self._model.spectrum_type} spectroscopy
"),
ipw.HTML(
"""
Select the type of Raman spectrum to plot.
"""
),
- self.raman_plot_type,
- self.raman_temperature,
- self.raman_frequency_laser,
- self.raman_broadening,
- self.raman_separate_polarized,
- self.raman_pol_incoming,
- self.raman_pol_outgoing,
- ipw.HBox([self.raman_plot_button, self.raman_download_button]),
- self.raman_spectrum,
- ipw.HTML("IR spectroscopy
"),
+ self.plot_type,
+ self.temperature,
+ self.frequency_laser,
+ self.broadening,
+ self.separate_polarizations,
+ self.pol_incoming,
+ self.pol_outgoing,
+ ipw.HBox([self.plot_button, self.download_button]),
+ self.spectrum,
+ ipw.HBox(
+ [
+ ipw.VBox(
+ [
+ ipw.HTML(
+ value=f"{self._model.spectrum_type} Active Modes"
+ ),
+ self.modes_table,
+ ]
+ ),
+ ipw.VBox(
+ [
+ self.active_modes,
+ self.amplitude,
+ self.supercell_selector,
+ self.animation,
+ ],
+ ),
+ ]
+ ),
]
self.rendered = True
+
self._initial_view()
def _initial_view(self):
self._model.fetch_data()
self._model.update_data()
- self.raman_spectrum.add_scatter(
+ if self._model.spectrum_type == "IR":
+ self.temperature.layout.display = "none"
+ self.frequency_laser.layout.display = "none"
+ self.pol_outgoing.layout.display == "none"
+ self.separate_polarizations.layout.display = "none"
+
+ self.spectrum.add_scatter(
x=self._model.frequencies, y=self._model.intensities, name=""
)
+ self.spectrum.layout.title.text = f"Powder {self._model.spectrum_type} Spectrum"
+ self.modes_table.layout = {
+ "overflow": "auto",
+ "height": "200px",
+ "width": "150px",
+ }
+ self.weas = self._model.set_vibrational_mode_animation(self.weas)
+ with self.animation:
+ clear_output()
+ display(self.weas)
- def _on_raman_plot_type_change(self, change):
+ with self.modes_table:
+ clear_output()
+ display(HTML(self._model.modes_table()))
+
+ def _on_plot_type_change(self, change):
if change["new"] == "single_crystal":
- self.raman_pol_incoming.layout.visibility = "visible"
- self.raman_pol_outgoing.layout.visibility = "visible"
- self.raman_separate_polarized.layout.visibility = "hidden"
+ self.pol_incoming.layout.visibility = "visible"
+ if self._model.spectrum_type == "Raman":
+ self.pol_outgoing.layout.visibility = "visible"
+ else:
+ self.separate_polarizations.layout.display = "none"
+ self.separate_polarizations.layout.visibility = "hidden"
else:
- self.raman_pol_incoming.layout.visibility = "hidden"
- self.raman_pol_outgoing.layout.visibility = "hidden"
- self.raman_separate_polarized.layout.visibility = "visible"
+ self.pol_incoming.layout.visibility = "hidden"
+ self.pol_outgoing.layout.visibility = "hidden"
+ self.separate_polarizations.layout.visibility = "visible"
- def _on_raman_plot_button_click(self, _):
+ def _on_plot_button_click(self, _):
_, incoming_syntax_ok = self._model._check_inputs_correct(
- self.raman_pol_incoming.value
+ self.pol_incoming.value
)
_, outgoing_syntax_ok = self._model._check_inputs_correct(
- self.raman_pol_outgoing.value
+ self.pol_outgoing.value
)
if not (incoming_syntax_ok and outgoing_syntax_ok):
self._wrong_syntax.message = """
@@ -173,4 +291,10 @@ def _on_raman_plot_button_click(self, _):
"""
return
self._model.update_data()
- self._model.update_plot(self.raman_spectrum)
+ self._model.update_plot(self.spectrum)
+
+ def _select_active_mode(self, _):
+ self.weas = self._model.set_vibrational_mode_animation(self.weas)
+ with self.animation:
+ clear_output()
+ display(self.weas)
diff --git a/src/aiidalab_qe_vibroscopy/utils/raman/result.py b/src/aiidalab_qe_vibroscopy/utils/raman/result.py
index 4b59e17..bc488e6 100644
--- a/src/aiidalab_qe_vibroscopy/utils/raman/result.py
+++ b/src/aiidalab_qe_vibroscopy/utils/raman/result.py
@@ -3,11 +3,8 @@
from __future__ import annotations
-import ipywidgets as ipw
import numpy as np
-from IPython.display import HTML, clear_output, display
-from weas_widget import WeasWidget
from aiida_vibroscopy.utils.broadenings import multilorentz
@@ -101,184 +98,3 @@ def export_iramanworkchain_data(node):
return spectra_data
else:
return None
-
-
-class ActiveModesWidget(ipw.VBox):
- """Widget that display an animation (nglview) of the active modes."""
-
- def __init__(self, node, output_node, spectrum_type, **kwargs):
- self.node = node
- self.output_node = output_node
- self.spectrum_type = spectrum_type
-
- # WeasWidget configuration
- self.guiConfig = {
- "enabled": True,
- "components": {
- "atomsControl": True,
- "buttons": True,
- "cameraControls": True,
- },
- "buttons": {
- "fullscreen": True,
- "download": True,
- "measurement": True,
- },
- }
- # VibrationalData
- vibrational_data = self.output_node.vibrational_data
- self.vibro = (
- vibrational_data.numerical_accuracy_4
- if hasattr(vibrational_data, "numerical_accuracy_4")
- else vibrational_data.numerical_accuracy_2
- )
-
- # Raman or IR active modes
- selection_rule = self.spectrum_type.lower()
- frequencies, self.eigenvectors, self.labels = self.vibro.run_active_modes(
- selection_rule=selection_rule,
- )
- self.rounded_frequencies = [round(frequency, 3) for frequency in frequencies]
-
- # StructureData
- self.structure_ase = self.node.inputs.structure.get_ase()
-
- modes_values = [
- f"{index + 1}: {value}"
- for index, value in enumerate(self.rounded_frequencies)
- ]
- # Create Raman modes widget
- self.active_modes = ipw.Dropdown(
- options=modes_values,
- value=modes_values[0], # Default value
- description="Select mode:",
- style={"description_width": "initial"},
- )
-
- self.amplitude = ipw.FloatText(
- value=3.0,
- description="Amplitude :",
- disabled=False,
- style={"description_width": "initial"},
- )
-
- self._supercell = [
- ipw.BoundedIntText(value=1, min=1, layout={"width": "40px"}),
- ipw.BoundedIntText(value=1, min=1, layout={"width": "40px"}),
- ipw.BoundedIntText(value=1, min=1, layout={"width": "40px"}),
- ]
-
- self.supercell_selector = ipw.HBox(
- [
- ipw.HTML(
- description="Super cell:", style={"description_width": "initial"}
- )
- ]
- + self._supercell
- )
-
- self.modes_table = ipw.Output()
- self.animation = ipw.Output()
- self._display_table()
- self._select_active_mode(None)
- widget_list = [
- self.active_modes,
- self.amplitude,
- self._supercell[0],
- self._supercell[1],
- self._supercell[2],
- ]
- for elem in widget_list:
- elem.observe(self._select_active_mode, names="value")
-
- super().__init__(
- children=[
- ipw.HBox(
- [
- ipw.VBox(
- [
- ipw.HTML(
- value=f" {self.spectrum_type} Active Modes "
- ),
- self.modes_table,
- ]
- ),
- ipw.VBox(
- [
- self.active_modes,
- self.amplitude,
- self.supercell_selector,
- self.animation,
- ],
- layout={"justify_content": "center"},
- ),
- ]
- ),
- ]
- )
-
- def _display_table(self):
- """Display table with the active modes."""
- # Create an HTML table with the active modes
- table_data = [list(x) for x in zip(self.rounded_frequencies, self.labels)]
- table_html = ""
- table_html += "Frequencies (cm-1) | Label |
"
- for row in table_data:
- table_html += ""
- for cell in row:
- table_html += "{} | ".format(cell)
- table_html += "
"
- table_html += "
"
- # Set layout to a fix size
- self.modes_table.layout = {
- "overflow": "auto",
- "height": "200px",
- "width": "150px",
- }
- with self.modes_table:
- clear_output()
- display(HTML(table_html))
-
- def _select_active_mode(self, change):
- """Display animation of the selected active mode."""
- self._animation_widget()
- with self.animation:
- clear_output()
- display(self.weas)
-
- def _animation_widget(self):
- """Create animation widget."""
- # Get the index of the selected mode
- index_str = self.active_modes.value.split(":")[0]
- index = int(index_str) - 1
- # Get the eigenvector of the selected mode
- eigenvector = self.eigenvectors[index]
- # Get the amplitude of the selected mode
- amplitude = self.amplitude.value
- # Get the structure of the selected mode
- structure = self.structure_ase
-
- self.weas = WeasWidget(guiConfig=self.guiConfig)
- self.weas.from_ase(structure)
-
- phonon_setting = {
- "eigenvectors": np.array(
- [[[real_part, 0] for real_part in row] for row in eigenvector]
- ),
- "kpoint": [0, 0, 0], # optional
- "amplitude": amplitude,
- "factor": amplitude * 0.6,
- "nframes": 20,
- "repeat": [
- self._supercell[0].value,
- self._supercell[1].value,
- self._supercell[2].value,
- ],
- "color": "black",
- "radius": 0.1,
- }
- self.weas.avr.phonon_setting = phonon_setting
-
- self.weas.avr.model_style = 1
- self.weas.avr.color_type = "JMOL"
- self.weas.avr.vf.show = True