Skip to content

Commit

Permalink
merge with main
Browse files Browse the repository at this point in the history
  • Loading branch information
AndresOrtegaGuerrero committed Sep 20, 2024
2 parents 1c1e7bc + 040d2c9 commit 9ca3705
Show file tree
Hide file tree
Showing 17 changed files with 653 additions and 149 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Plugin to compute vibrational properties of materials via the aiida-vibroscopy A
Once cloned the repository, `cd` into it and:

```shell
pip install -e
pip install --user .
```

If you want to easily set up phonopy, use the CLI of this package (inspect it via `aiidalab-qe-vibroscopy --help`):
Expand Down
23 changes: 19 additions & 4 deletions src/aiidalab_qe_vibroscopy/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,37 @@
from aiidalab_qe_vibroscopy.app.result import Result
from aiidalab_qe.common.panel import OutlinePanel

from aiidalab_qe.common.widgets import QEAppComputationalResourcesWidget
from aiidalab_qe.common.widgets import QEAppComputationalResourcesWidget, PwCodeResourceSetupWidget


class Outline(OutlinePanel):
title = "Vibrational properties"
# description = "IR and Raman spectra; you may also select phononic and dielectric properties"


phonopy_code = QEAppComputationalResourcesWidget(
description="phonopy",
PhononWorkChainPwCode = PwCodeResourceSetupWidget(
description="pw.x for phonons", #code for the PhononWorkChain workflow",
default_calc_job_plugin="quantumespresso.pw",
)

# The finite electric field does not support npools (does not work with npools>1), so we just set it as QEAppComputationalResourcesWidget
DielectricWorkChainPwCode = QEAppComputationalResourcesWidget(
description="pw.x for dielectric", #code for the DielectricWorChain workflow",
default_calc_job_plugin="quantumespresso.pw",
)

PhonopyCalculationCode = QEAppComputationalResourcesWidget(
description="phonopy", # code for the PhonopyCalculation calcjob",
default_calc_job_plugin="phonopy.phonopy",
)

property = {
"outline": Outline,
"code": {"phonopy": phonopy_code},
"code": {
"phonon":PhononWorkChainPwCode,
"dielectric": DielectricWorkChainPwCode,
"phonopy": PhonopyCalculationCode,
},
"setting": Setting,
"workchain": workchain_and_builder,
"result": Result,
Expand Down
174 changes: 169 additions & 5 deletions src/aiidalab_qe_vibroscopy/app/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,34 @@
import numpy as np

from ..utils.raman.result import export_iramanworkchain_data

from ..utils.dielectric.result import export_dielectric_data, DielectricResults
from ..utils.phonons.result import export_phononworkchain_data

from ..utils.euphonic import (
export_euphonic_data,
EuphonicSuperWidget,
DowloadYamlHdf5Widget,
DownloadYamlHdf5Widget,
)
import plotly.graph_objects as go
import ipywidgets as ipw

from ..utils.raman.result import SpectrumPlotWidget, ActiveModesWidget

def create_html_table(matrix):
"""
Create an HTML table representation of a 3x3 matrix.
:param matrix: List of lists representing a 3x3 matrix
:return: HTML table string
"""
html = '<table border="1" style="border-collapse: collapse;">'
for row in matrix:
html += '<tr>'
for cell in row:
html += f'<td style="padding: 5px; text-align: center;">{cell}</td>'
html += '</tr>'
html += '</table>'
return html

class PhononBandPdosPlotly(BandPdosPlotly):
def __init__(self, bands_data=None, pdos_data=None):
Expand Down Expand Up @@ -84,6 +99,7 @@ def _update_view(self):
spectra_data = export_iramanworkchain_data(self.node)
phonon_data = export_phononworkchain_data(self.node)
ins_data = export_euphonic_data(self.node)
dielectric_data = export_dielectric_data(self.node)

if phonon_data:

Expand All @@ -94,13 +110,20 @@ def _update_view(self):
pdos_data=phonon_data["pdos"][0],
)

download_widget = DowloadYamlHdf5Widget(
# the data (bands and pdos) are the first element of the lists phonon_data["bands"] and phonon_data["pdos"]!
downloadBandsPdos_widget = DownloadBandsPdosWidget(
data=phonon_data,
)
downloadYamlHdf5_widget = DownloadYamlHdf5Widget(
phonopy_node=self.node.outputs.vibronic.phonon_pdos.creator
)

phonon_children += (
_bands_plot_view_class.bandspdosfigure,
download_widget,
ipw.HBox(children=[
downloadBandsPdos_widget,
downloadYamlHdf5_widget,
]),
)

if phonon_data["thermo"]:
Expand Down Expand Up @@ -134,7 +157,13 @@ def _update_view(self):
g.add_scatter(x=T, y=E, name=f"Entropy ({E_units})")
g.add_scatter(x=T, y=Cv, name=f"Specific Heat-V=const ({Cv_units})")

phonon_children += (g,)
downloadThermo_widget = DownloadThermoWidget(
T,F,E,Cv)

phonon_children += (
g,
downloadThermo_widget,
)

tab_titles.append("Phonon properties")

Expand Down Expand Up @@ -196,9 +225,144 @@ def _update_view(self):
)
tab_titles.append(f"Raman/IR spectra")

#create and VBox
my_VBox = ipw.VBox(children=children_spectra, layout=ipw.Layout(width="100%"))

if dielectric_data:

dielectric_results = DielectricResults(dielectric_data)
children_result_widget += (dielectric_results,)
tab_titles.append("Dielectric properties")



self.result_tabs = ipw.Tab(children=children_result_widget)

for title_index in range(len(tab_titles)):
self.result_tabs.set_title(title_index, tab_titles[title_index])

self.children = [self.result_tabs]

class DownloadBandsPdosWidget(ipw.HBox):
def __init__(self, data, **kwargs):

self.download_button = ipw.Button(
description="Download phonon bands and pdos data",
icon="pencil",
button_style="primary",
disabled=False,
layout=ipw.Layout(width="auto"),
)
self.download_button.on_click(self.download_data)
self.bands_data = data["bands"]
self.pdos_data = data["pdos"]

super().__init__(
children=[
self.download_button,
],
)

def download_data(self, _=None):
"""Function to download the phonon data."""
import json
from monty.json import jsanitize
import base64
file_name_bands = "phonon_bands_data.json"
file_name_pdos = "phonon_dos_data.json"
if self.bands_data[0]:
bands_data_export = {}
for key, value in self.bands_data[0].items():
if isinstance(value, np.ndarray):
bands_data_export[key] = value.tolist()
else:
bands_data_export[key] = value

json_str = json.dumps(jsanitize(bands_data_export))
b64_str = base64.b64encode(json_str.encode()).decode()
self._download(payload=b64_str, filename=file_name_bands)
if self.pdos_data:
json_str = json.dumps(jsanitize(self.pdos_data[0]))
b64_str = base64.b64encode(json_str.encode()).decode()
self._download(payload=b64_str, filename=file_name_pdos)

@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)

class DownloadThermoWidget(ipw.HBox):

def __init__(self, T,F,E,Cv, **kwargs):

self.download_button = ipw.Button(
description="Download thermal properties data",
icon="pencil",
button_style="primary",
disabled=False,
layout=ipw.Layout(width="auto"),
)
self.download_button.on_click(self.download_data)

self.temperature = T
self.free_E = F
self.entropy = E
self.Cv = Cv

super().__init__(
children=[
self.download_button,
],
)

def download_data(self, _=None):
"""Function to download the phonon data."""
import json
from monty.json import jsanitize
import base64
file_name = "phonon_thermo_data.json"
data_export = {}
for key, value in zip(
["Temperature (K)","Helmoltz Free Energy (kJ/mol)",
"Entropy (J/K/mol)","Specific Heat-V=const (J/K/mol)"],
[self.temperature, self.free_E,self.entropy, self.Cv]):
if isinstance(value, np.ndarray):
data_export[key] = value.tolist()
else:
data_export[key] = value

json_str = json.dumps(data_export)
b64_str = base64.b64encode(json_str.encode()).decode()
self._download(payload=b64_str, filename=file_name)


@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)
5 changes: 4 additions & 1 deletion src/aiidalab_qe_vibroscopy/app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,14 +220,17 @@ def get_panel_value(self):
"symmetry_symprec": self.symmetry_symprec.value,
}

def load_panel_value(self, input_dict):
def set_panel_value(self, input_dict):
"""Load a dictionary with the input parameters for the plugin."""
self.calc_options.value = input_dict.get("simulation_mode", 1)
self.supercell = input_dict.get("supercell_selector", [2, 2, 2])
self.symmetry_symprec.value = input_dict.get("symmetry_symprec", 1e-5)
self._sc_x.value, self._sc_y.value, self._sc_z.value = self.supercell


def reset(self):
"""Reset the panel"""
self.calc_options.value = 1
self.supercell = [2, 2, 2]
self.symmetry_symprec.value = 1e-5
self._sc_x.value, self._sc_y.value, self._sc_z.value = self.supercell
27 changes: 15 additions & 12 deletions src/aiidalab_qe_vibroscopy/app/workchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def create_resource_config(code_details):
Returns:
dict: A nested dictionary with structured resource configurations.
"""
options = {
metadata = {
"options": {
"resources": {
"num_machines": code_details["nodes"],
Expand All @@ -30,48 +30,50 @@ def create_resource_config(code_details):
}

if "max_wallclock_seconds" in code_details:
options["options"]["max_wallclock_seconds"] = code_details["max_wallclock_seconds"]
metadata["options"]["max_wallclock_seconds"] = code_details["max_wallclock_seconds"]


return options
return metadata


def get_builder(codes, structure, parameters):
from copy import deepcopy

protocol = parameters["workchain"].pop("protocol", "fast")
pw_code = codes.get("pw")["code"]
pw_phonon_code = codes.get("phonon")["code"]
pw_dielectric_code = codes.get("dielectric")["code"]
phonopy_code = codes.get("phonopy")["code"]

simulation_mode = parameters["vibronic"].pop("simulation_mode", 1)

# Define the supercell matrix
supercell_matrix = parameters["vibronic"].pop("supercell_selector", None)

scf_overrides = deepcopy(parameters["advanced"])

# The following include_all is needed to have forces written
overrides = {
"phonon": {
"scf": scf_overrides,
"scf": deepcopy(parameters["advanced"]),
"supercell_matrix": supercell_matrix,
},
"dielectric": {"scf": scf_overrides},
"dielectric": {"scf": deepcopy(parameters["advanced"])},
"symmetry:": {"symprec": parameters["vibronic"]["symmetry_symprec"]},
}

# Update code information with resource configurations
overrides["dielectric"]["scf"]["pw"]["metadata"] = create_resource_config(
codes.get("pw")
codes.get("dielectric")
)

overrides["phonon"]["scf"]["pw"]["metadata"] = create_resource_config(
codes.get("pw")
codes.get("phonon")
)


# Parallelization for phonon calculation
if "parallelization" in codes.get("pw"):
if "parallelization" in codes.get("phonon"):
overrides["phonon"]["scf"]["pw"]["parallelization"] = orm.Dict(
codes.get("pw")["parallelization"]
codes.get("phonon")["parallelization"]
)

# Only for 2D and 1D materials
Expand All @@ -82,7 +84,8 @@ def get_builder(codes, structure, parameters):
] = PwBaseWorkChain.get_protocol_inputs(protocol)["kpoints_distance"]

builder = VibroWorkChain.get_builder_from_protocol(
pw_code=pw_code,
phonon_code=pw_phonon_code,
dielectric_code=pw_dielectric_code,
phonopy_code=phonopy_code,
structure=structure,
protocol=protocol,
Expand Down
9 changes: 0 additions & 9 deletions src/aiidalab_qe_vibroscopy/codes.py

This file was deleted.

Loading

0 comments on commit 9ca3705

Please sign in to comment.