diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8ac03b8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: CI + +on: [push, pull_request] + +jobs: + + pre-commit: + runs-on: ubuntu-latest + + steps: + # Checkout the repository + - name: Checkout code + uses: actions/checkout@v3 + + # Set up Python environment + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + # Install pre-commit + - name: Install pre-commit + run: pip install pre-commit + + # Run pre-commit on all files + - name: Run pre-commit + run: pre-commit run --all-files diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6ea2665..274256e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,12 @@ repos: - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files -- repo: https://github.com/psf/black - rev: 24.4.2 + +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.6.1 hooks: - - id: black + - id: ruff + types_or: [python, pyi, jupyter] + args: [--fix] + - id: ruff-format + types_or: [python, pyi, jupyter] diff --git a/LICENSE.txt b/LICENSE.txt index b1fa6d2..a5525bb 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -5,4 +5,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/pyproject.toml b/pyproject.toml index 32de82b..2c8a33a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,9 @@ dependencies = [ "kaleido", ] +[tool.ruff.lint] +ignore = ["E501", "E402", "TRY003", "RUF012", "N806"] + [project.urls] Source = "https://github.com/mikibonacci/aiidalab-qe-vibroscopy" @@ -37,4 +40,4 @@ Source = "https://github.com/mikibonacci/aiidalab-qe-vibroscopy" "vibronic" = "aiidalab_qe_vibroscopy.app:property" [project.scripts] -"aiidalab-qe-vibroscopy" = "aiidalab_qe_vibroscopy.__main__:cli" \ No newline at end of file +"aiidalab-qe-vibroscopy" = "aiidalab_qe_vibroscopy.__main__:cli" diff --git a/src/aiidalab_qe_vibroscopy/__init__.py b/src/aiidalab_qe_vibroscopy/__init__.py index 63fa439..00b3158 100644 --- a/src/aiidalab_qe_vibroscopy/__init__.py +++ b/src/aiidalab_qe_vibroscopy/__init__.py @@ -1 +1 @@ -"""AiiDAlab Qe plugin for vibrational spectoscopy.""" \ No newline at end of file +"""AiiDAlab Qe plugin for vibrational spectoscopy.""" diff --git a/src/aiidalab_qe_vibroscopy/__main__.py b/src/aiidalab_qe_vibroscopy/__main__.py index b1b936a..c56fa11 100644 --- a/src/aiidalab_qe_vibroscopy/__main__.py +++ b/src/aiidalab_qe_vibroscopy/__main__.py @@ -12,13 +12,13 @@ So we only setup in AiiDA. """ + @click.group() def cli(): pass -@cli.command( - help="Setup phonopy@localhost in the current AiiDA database." -) + +@cli.command(help="Setup phonopy@localhost in the current AiiDA database.") def setup_phonopy(): load_profile() try: @@ -27,10 +27,12 @@ def setup_phonopy(): # Use shutil.which to find the path of the phonopy executable phonopy_path = shutil.which("phonopy") if not phonopy_path: - raise FileNotFoundError("Phonopy code is not found in PATH. \ + raise FileNotFoundError( + "Phonopy code is not found in PATH. \ You should update your PATH. If you have not phonopy in , \ your environment, install the code via \ - `pip install phonopy --user`.") + `pip install phonopy --user`." + ) # Construct the command as a list of arguments command = [ "verdi", @@ -53,5 +55,6 @@ def setup_phonopy(): else: print("Code phonopy@localhost is already installed! Nothing to do here.") + if __name__ == "__main__": - cli() \ No newline at end of file + cli() diff --git a/src/aiidalab_qe_vibroscopy/app/__init__.py b/src/aiidalab_qe_vibroscopy/app/__init__.py index 2550cf2..d4e2de5 100644 --- a/src/aiidalab_qe_vibroscopy/app/__init__.py +++ b/src/aiidalab_qe_vibroscopy/app/__init__.py @@ -3,7 +3,10 @@ from aiidalab_qe_vibroscopy.app.result import Result from aiidalab_qe.common.panel import OutlinePanel -from aiidalab_qe.common.widgets import QEAppComputationalResourcesWidget, PwCodeResourceSetupWidget +from aiidalab_qe.common.widgets import ( + QEAppComputationalResourcesWidget, + PwCodeResourceSetupWidget, +) class Outline(OutlinePanel): @@ -12,28 +15,28 @@ class Outline(OutlinePanel): PhononWorkChainPwCode = PwCodeResourceSetupWidget( - description="pw.x for phonons", #code for the PhononWorkChain workflow", + 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", + 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", + description="phonopy", # code for the PhonopyCalculation calcjob", default_calc_job_plugin="phonopy.phonopy", ) property = { "outline": Outline, "code": { - "phonon":PhononWorkChainPwCode, + "phonon": PhononWorkChainPwCode, "dielectric": DielectricWorkChainPwCode, "phonopy": PhonopyCalculationCode, - }, + }, "setting": Setting, "workchain": workchain_and_builder, "result": Result, diff --git a/src/aiidalab_qe_vibroscopy/app/result.py b/src/aiidalab_qe_vibroscopy/app/result.py index b7153e8..04ac1fa 100644 --- a/src/aiidalab_qe_vibroscopy/app/result.py +++ b/src/aiidalab_qe_vibroscopy/app/result.py @@ -1,12 +1,11 @@ -"""Bands results view widgets +"""Bands results view widgets""" -""" from __future__ import annotations from aiidalab_qe.common.panel import ResultPanel from aiidalab_qe.common.bandpdoswidget import BandPdosPlotly - +from IPython.display import display import numpy as np from ..utils.raman.result import export_iramanworkchain_data @@ -23,21 +22,6 @@ 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 = '' - for row in matrix: - html += '' - for cell in row: - html += f'' - html += '' - html += '
{cell}
' - return html class PhononBandPdosPlotly(BandPdosPlotly): def __init__(self, bands_data=None, pdos_data=None): @@ -92,7 +76,6 @@ class Result(ResultPanel): children_result_widget = () def _update_view(self): - children_result_widget = () tab_titles = [] # this is needed to name the sub panels @@ -102,7 +85,6 @@ def _update_view(self): dielectric_data = export_dielectric_data(self.node) if phonon_data: - phonon_children = () if phonon_data["bands"] or phonon_data["pdos"]: _bands_plot_view_class = PhononBandPdosPlotly( @@ -110,7 +92,7 @@ def _update_view(self): pdos_data=phonon_data["pdos"][0], ) - # the data (bands and pdos) are the first element of the lists phonon_data["bands"] and phonon_data["pdos"]! + # 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, ) @@ -120,10 +102,12 @@ def _update_view(self): phonon_children += ( _bands_plot_view_class.bandspdosfigure, - ipw.HBox(children=[ - downloadBandsPdos_widget, - downloadYamlHdf5_widget, - ]), + ipw.HBox( + children=[ + downloadBandsPdos_widget, + downloadYamlHdf5_widget, + ] + ), ) if phonon_data["thermo"]: @@ -157,9 +141,8 @@ 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})") - downloadThermo_widget = DownloadThermoWidget( - T,F,E,Cv) - + downloadThermo_widget = DownloadThermoWidget(T, F, E, Cv) + phonon_children += ( g, downloadThermo_widget, @@ -179,16 +162,14 @@ def _update_view(self): if ins_data: intensity_maps = EuphonicSuperWidget(fc=ins_data["fc"]) children_result_widget += (intensity_maps,) - tab_titles.append(f"Inelastic Neutrons") + tab_titles.append("Inelastic Neutrons") 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 @@ -223,19 +204,13 @@ 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%")) + tab_titles.append("Raman/IR spectra") 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)): @@ -243,9 +218,9 @@ def _update_view(self): 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", @@ -268,6 +243,7 @@ def download_data(self, _=None): 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]: @@ -298,16 +274,13 @@ def _download(payload, filename): document.body.appendChild(link); link.click(); document.body.removeChild(link); - """.format( - payload=payload, filename=filename - ) + """.format(payload=payload, filename=filename) ) display(javas) - -class DownloadThermoWidget(ipw.HBox): - - def __init__(self, T,F,E,Cv, **kwargs): + +class DownloadThermoWidget(ipw.HBox): + def __init__(self, T, F, E, Cv, **kwargs): self.download_button = ipw.Button( description="Download thermal properties data", icon="pencil", @@ -316,7 +289,7 @@ def __init__(self, T,F,E,Cv, **kwargs): layout=ipw.Layout(width="auto"), ) self.download_button.on_click(self.download_data) - + self.temperature = T self.free_E = F self.entropy = E @@ -331,14 +304,19 @@ def __init__(self, T,F,E,Cv, **kwargs): 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]): + [ + "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: @@ -347,7 +325,6 @@ def download_data(self, _=None): 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): @@ -361,8 +338,6 @@ def _download(payload, filename): document.body.appendChild(link); link.click(); document.body.removeChild(link); - """.format( - payload=payload, filename=filename - ) + """.format(payload=payload, filename=filename) ) - display(javas) \ No newline at end of file + display(javas) diff --git a/src/aiidalab_qe_vibroscopy/app/settings.py b/src/aiidalab_qe_vibroscopy/app/settings.py index 205a906..1a3d67c 100644 --- a/src/aiidalab_qe_vibroscopy/app/settings.py +++ b/src/aiidalab_qe_vibroscopy/app/settings.py @@ -6,17 +6,16 @@ * Miki Bonacci Inspired by Xing Wang """ + import ipywidgets as ipw import traitlets as tl import numpy as np from aiida import orm from aiidalab_qe.common.panel import Panel -from IPython.display import clear_output, display class Setting(Panel): - title = "Vibrational Settings" simulation_mode = [ @@ -216,7 +215,6 @@ def set_panel_value(self, input_dict): self.calc_options.value = input_dict.get("simulation_mode", 1) self.supercell = input_dict.get("supercell_selector", [2, 2, 2]) self._sc_x.value, self._sc_y.value, self._sc_z.value = self.supercell - def reset(self): """Reset the panel""" diff --git a/src/aiidalab_qe_vibroscopy/app/workchain.py b/src/aiidalab_qe_vibroscopy/app/workchain.py index 9943c40..9fcec63 100644 --- a/src/aiidalab_qe_vibroscopy/app/workchain.py +++ b/src/aiidalab_qe_vibroscopy/app/workchain.py @@ -18,21 +18,21 @@ def create_resource_config(code_details): Returns: dict: A nested dictionary with structured resource configurations. """ - metadata = { + metadata = { "options": { "resources": { "num_machines": code_details["nodes"], "num_mpiprocs_per_machine": code_details["ntasks_per_node"], "num_cores_per_mpiproc": code_details["cpus_per_task"], - }, - + }, } } - + if "max_wallclock_seconds" in code_details: - metadata["options"]["max_wallclock_seconds"] = code_details["max_wallclock_seconds"] + metadata["options"]["max_wallclock_seconds"] = code_details[ + "max_wallclock_seconds" + ] - return metadata @@ -40,7 +40,6 @@ 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"] @@ -63,11 +62,10 @@ def get_builder(codes, structure, parameters): overrides["dielectric"]["scf"]["pw"]["metadata"] = create_resource_config( codes.get("dielectric") ) - + overrides["phonon"]["scf"]["pw"]["metadata"] = create_resource_config( codes.get("phonon") ) - # Parallelization for phonon calculation if "parallelization" in codes.get("phonon"): @@ -78,9 +76,9 @@ def get_builder(codes, structure, parameters): # Only for 2D and 1D materials if structure.pbc != (True, True, True): if "kpoints_distance" not in parameters["advanced"]: - overrides["dielectric"]["scf"][ - "kpoints_distance" - ] = PwBaseWorkChain.get_protocol_inputs(protocol)["kpoints_distance"] + overrides["dielectric"]["scf"]["kpoints_distance"] = ( + PwBaseWorkChain.get_protocol_inputs(protocol)["kpoints_distance"] + ) builder = VibroWorkChain.get_builder_from_protocol( phonon_code=pw_phonon_code, diff --git a/src/aiidalab_qe_vibroscopy/utils/dielectric/result.py b/src/aiidalab_qe_vibroscopy/utils/dielectric/result.py index b4770de..ecccf70 100644 --- a/src/aiidalab_qe_vibroscopy/utils/dielectric/result.py +++ b/src/aiidalab_qe_vibroscopy/utils/dielectric/result.py @@ -1,18 +1,20 @@ # -*- coding: utf-8 -*- import ipywidgets as ipw -from IPython.display import HTML, clear_output, display +from IPython.display import HTML, display import base64 import json import numpy as np + class NumpyEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, np.ndarray): return obj.tolist() # Convert ndarray to list return super().default(obj) + def create_html_table(matrix): """ Create an HTML table representation of a 3x3 matrix. @@ -22,11 +24,11 @@ def create_html_table(matrix): """ html = '' for row in matrix: - html += '' + html += "" for cell in row: html += f'' - html += '' - html += '
{cell}
' + html += "" + html += "" return html @@ -38,29 +40,34 @@ def get_priority_tensor(filtered_node): :return: Corresponding to the highest priority key found, or None if not found """ # Define the priority order of keys within the function - priority_keys = ['numerical_accuracy_4', 'numerical_accuracy_2_step_2', 'numerical_accuracy_2_step_1', 'numerical_accuracy_2'] - + priority_keys = [ + "numerical_accuracy_4", + "numerical_accuracy_2_step_2", + "numerical_accuracy_2_step_1", + "numerical_accuracy_2", + ] + # Get the keys from the tensor outputs tensor_keys = filtered_node.keys() - + for key in priority_keys: if key in tensor_keys: return filtered_node[key] - + # If no matching key is found, return None or handle the case as needed return None - -def export_dielectric_data(node): - if not "vibronic" in node.outputs: +def export_dielectric_data(node): + if "vibronic" not in node.outputs: return None - - if not any(key in node.outputs.vibronic for key in ["iraman", "dielectric", "harmonic"]): + + if not any( + key in node.outputs.vibronic for key in ["iraman", "dielectric", "harmonic"] + ): return None else: - if "iraman" in node.outputs.vibronic: vibrational_data = node.outputs.vibronic.iraman.vibrational_data @@ -70,11 +77,17 @@ def export_dielectric_data(node): elif "dielectric" in node.outputs.vibronic: tensor_data = node.outputs.vibronic.dielectric output_data = get_priority_tensor(tensor_data) - dielectric_tensor = output_data.get_array("dielectric").round(6) # Dielectric Constant - born_charges = output_data.get_array("born_charges") # List of Born effective charges per Atom - vol = output_data.get_unitcell().get_cell_volume() # Volume of the cell + dielectric_tensor = output_data.get_array("dielectric").round( + 6 + ) # Dielectric Constant + born_charges = output_data.get_array( + "born_charges" + ) # List of Born effective charges per Atom + vol = output_data.get_unitcell().get_cell_volume() # Volume of the cell raman_tensors = output_data.get_array("raman_tensors") # Raman tensors - nlo_susceptibility = output_data.get_array("nlo_susceptibility") # non-linear optical susceptibility tensor (pm/V) + nlo_susceptibility = output_data.get_array( + "nlo_susceptibility" + ) # non-linear optical susceptibility tensor (pm/V) unit_cell = output_data.get_unitcell().sites return { "dielectric_tensor": dielectric_tensor, @@ -84,13 +97,17 @@ def export_dielectric_data(node): "nlo_susceptibility": nlo_susceptibility, "unit_cell": unit_cell, } - + output_data = get_priority_tensor(vibrational_data) - dielectric_tensor = output_data.dielectric.round(6) # Dielectric Constant - born_charges = output_data.get_array("born_charges") # List of Born effective charges per Atom - vol = output_data.get_unitcell().get_cell_volume() # Volume of the cell - raman_tensors = output_data.get_array("raman_tensors") # Raman tensors - nlo_susceptibility = output_data.nlo_susceptibility # non-linear optical susceptibility tensor (pm/V) + dielectric_tensor = output_data.dielectric.round(6) # Dielectric Constant + born_charges = output_data.get_array( + "born_charges" + ) # List of Born effective charges per Atom + vol = output_data.get_unitcell().get_cell_volume() # Volume of the cell + raman_tensors = output_data.get_array("raman_tensors") # Raman tensors + nlo_susceptibility = ( + output_data.nlo_susceptibility + ) # non-linear optical susceptibility tensor (pm/V) unit_cell = output_data.get_unitcell().sites return { @@ -101,25 +118,20 @@ def export_dielectric_data(node): "nlo_susceptibility": nlo_susceptibility, "unit_cell": unit_cell, } - - - class DielectricResults(ipw.VBox): - def __init__(self, dielectric_data): - - #Helper - self.dielectric_results_help= ipw.HTML( + # Helper + self.dielectric_results_help = ipw.HTML( """
The DielectricWorkchain computes different properties:
-High Freq. Dielectric Tensor
-Born Charges
-Raman Tensors
-The non-linear optical susceptibility tensor
- All information can be downloaded as a JSON file.
- + All information can be downloaded as a JSON file.
+
""" ) @@ -131,7 +143,6 @@ def __init__(self, dielectric_data): self.raman_tensors = dielectric_data["raman_tensors"] self.nlo_susceptibility = dielectric_data["nlo_susceptibility"] - # HTML table with the dielectric tensor self.dielectric_tensor_table = ipw.Output() @@ -141,11 +152,13 @@ def __init__(self, dielectric_data): # HTML table with the Raman tensors @ site self.raman_tensors_table = ipw.Output() - - decimal_places = 6 + decimal_places = 6 # Create the options with rounded positions site_selector_options = [ - (f"{site.kind_name} @ ({', '.join(f'{coord:.{decimal_places}f}' for coord in site.position)})", index) + ( + f"{site.kind_name} @ ({', '.join(f'{coord:.{decimal_places}f}' for coord in site.position)})", + index, + ) for index, site in enumerate(self.unit_cell_sites) ] @@ -156,15 +169,17 @@ def __init__(self, dielectric_data): description="Select atom site:", style={"description_width": "initial"}, ) - #Download button - self.download_button = ipw.Button(description="Download Data", icon="download", button_style="primary") + # Download button + self.download_button = ipw.Button( + description="Download Data", icon="download", button_style="primary" + ) self.download_button.on_click(self.download_data) - #Initialize the HTML table + # Initialize the HTML table self._create_dielectric_tensor_table() - #Initialize Born Charges Table + # Initialize Born Charges Table self._create_born_charges_table(self.site_selector.value) - #Initialize Raman Tensors Table + # Initialize Raman Tensors Table self._create_raman_tensors_table(self.site_selector.value) self.site_selector.observe(self._on_site_selection_change, names="value") @@ -174,7 +189,22 @@ def __init__(self, dielectric_data): ipw.HTML("

Dielectric tensor

"), self.dielectric_tensor_table, self.site_selector, - ipw.HBox([ipw.VBox([ipw.HTML("

Born effective charges

"), self.born_charges_table]), ipw.VBox([ipw.HTML("

Raman Tensor

"), self.raman_tensors_table])]), + ipw.HBox( + [ + ipw.VBox( + [ + ipw.HTML("

Born effective charges

"), + self.born_charges_table, + ] + ), + ipw.VBox( + [ + ipw.HTML("

Raman Tensor

"), + self.raman_tensors_table, + ] + ), + ] + ), self.download_button, ) ) @@ -203,7 +233,7 @@ def _download(payload, filename): """ ) display(javas) - + def _create_html_table(self, matrix): """ Create an HTML table representation of a 3x3 matrix. @@ -213,13 +243,13 @@ def _create_html_table(self, matrix): """ html = '' for row in matrix: - html += '' + html += "" for cell in row: html += f'' - html += '' - html += '
{cell}
' + html += "" + html += "" return html - + def _create_dielectric_tensor_table(self): table_data = self._create_html_table(self.dielectric_tensor) self.dielectric_tensor_table.layout = { @@ -257,12 +287,3 @@ def _on_site_selection_change(self, change): self.raman_tensors_table.clear_output() self._create_born_charges_table(change["new"]) self._create_raman_tensors_table(change["new"]) - - - - - - - - - \ No newline at end of file diff --git a/src/aiidalab_qe_vibroscopy/utils/euphonic/Detached_app.ipynb b/src/aiidalab_qe_vibroscopy/utils/euphonic/Detached_app.ipynb index 53dda6d..98a0cfc 100644 --- a/src/aiidalab_qe_vibroscopy/utils/euphonic/Detached_app.ipynb +++ b/src/aiidalab_qe_vibroscopy/utils/euphonic/Detached_app.ipynb @@ -63,7 +63,7 @@ "style = files(static).joinpath(\"style.css\").read_text()\n", "welcome_message = ipw.HTML(env.from_string(template).render(style=style))\n", "footer = ipw.HTML(\n", - " f'

Copyright (c) 2024 Miki Bonacci (PSI), miki.bonacci@psi.ch;  Version: 0.1.1

'\n", + " '

Copyright (c) 2024 Miki Bonacci (PSI), miki.bonacci@psi.ch;  Version: 0.1.1

'\n", ")\n", "\n", "widget = EuphonicSuperWidget(mode=\"detached\")\n", diff --git a/src/aiidalab_qe_vibroscopy/utils/euphonic/__init__.py b/src/aiidalab_qe_vibroscopy/utils/euphonic/__init__.py index 6c697ce..d169a46 100644 --- a/src/aiidalab_qe_vibroscopy/utils/euphonic/__init__.py +++ b/src/aiidalab_qe_vibroscopy/utils/euphonic/__init__.py @@ -1,33 +1,24 @@ import pathlib import tempfile -import io -import base64 -from IPython.display import HTML, clear_output, display -import euphonic -from phonopy.file_IO import write_force_constants_to_hdf5, write_disp_yaml +from IPython.display import display import ipywidgets as ipw -import plotly.graph_objects as go -import plotly.io as pio # from ..euphonic.bands_pdos import * -from .intensity_maps import * -from .euphonic_single_crystal_widgets import * -from .euphonic_powder_widgets import * -from .euphonic_q_planes_widgets import * +from .intensity_maps import ( + generate_force_constant_instance, + export_euphonic_data, # noqa: F401 +) +from .euphonic_single_crystal_widgets import SingleCrystalFullWidget +from .euphonic_powder_widgets import PowderFullWidget +from .euphonic_q_planes_widgets import QSectionFullWidget -import json -from monty.json import jsanitize - -# sys and os used to prevent euphonic to print in the stdout. -import sys -import os - ###### START for detached app: + # Upload buttons class UploadPhonopyYamlWidget(ipw.FileUpload): def __init__(self, **kwargs): @@ -49,7 +40,6 @@ def __init__(self, **kwargs): class UploadPhonopyWidget(ipw.HBox): def __init__(self, **kwargs): - self.upload_phonopy_yaml = UploadPhonopyYamlWidget(**kwargs) self.upload_phonopy_hdf5 = UploadForceConstantsHdf5Widget(**kwargs) @@ -122,7 +112,6 @@ class EuphonicSuperWidget(ipw.VBox): """ def __init__(self, mode="aiidalab-qe app plugin", fc=None): - self.mode = mode self.upload_widget = UploadPhonopyWidget() @@ -234,7 +223,6 @@ def _on_first_plot_button_clicked(self, change=None): class DownloadYamlHdf5Widget(ipw.HBox): def __init__(self, phonopy_node, **kwargs): - self.download_button = ipw.Button( description="Download phonopy data", icon="pencil", @@ -273,8 +261,6 @@ def _download(payload, filename): document.body.appendChild(link); link.click(); document.body.removeChild(link); - """.format( - payload=payload, filename=filename - ) + """.format(payload=payload, filename=filename) ) display(javas) diff --git a/src/aiidalab_qe_vibroscopy/utils/euphonic/bands_pdos.py b/src/aiidalab_qe_vibroscopy/utils/euphonic/bands_pdos.py index 6ecd164..deaafce 100644 --- a/src/aiidalab_qe_vibroscopy/utils/euphonic/bands_pdos.py +++ b/src/aiidalab_qe_vibroscopy/utils/euphonic/bands_pdos.py @@ -2,7 +2,6 @@ import euphonic as eu import euphonic.util as util -import euphonic.plot as plt from aiidalab_qe.common.bandpdoswidget import cmap diff --git a/src/aiidalab_qe_vibroscopy/utils/euphonic/euphonic_base_widgets.py b/src/aiidalab_qe_vibroscopy/utils/euphonic/euphonic_base_widgets.py index b894f2d..1958068 100644 --- a/src/aiidalab_qe_vibroscopy/utils/euphonic/euphonic_base_widgets.py +++ b/src/aiidalab_qe_vibroscopy/utils/euphonic/euphonic_base_widgets.py @@ -1,25 +1,12 @@ -import pathlib -import tempfile -import io - -import base64 -from IPython.display import HTML, clear_output, display - -import euphonic -from phonopy.file_IO import write_force_constants_to_hdf5, write_disp_yaml +from IPython.display import display +import numpy as np import ipywidgets as ipw -import plotly.graph_objects as go -import plotly.io as pio # from ..euphonic.bands_pdos import * -from .intensity_maps import * -import json -from monty.json import jsanitize +from .intensity_maps import * # noqa: F403 # sys and os used to prevent euphonic to print in the stdout. -import sys -import os ######################## ################################ START DESCRIPTION @@ -41,6 +28,7 @@ def export_phononworkchain_data(node, fermi_energy=None): COLORSCALE = "Viridis" COLORBAR_DICT = dict(orientation="v", showticklabels=False, x=1, thickness=10, len=0.4) + # # Intensity map widget class StructureFactorBasePlotWidget(ipw.VBox): """ @@ -193,14 +181,12 @@ def _update_energy_units(self, change): class StructureFactorSettingsBaseWidget(ipw.VBox): - """ Collects all the button and widget used to define settings for Neutron dynamic structure factor, both single crystal or powder. """ def __init__(self, **kwargs): - super().__init__() self.float_q_spacing = ipw.FloatText( @@ -296,7 +282,6 @@ def _on_setting_changed(self, change): class SingleCrystalSettingsWidget(StructureFactorSettingsBaseWidget): def __init__(self, **kwargs): - self.custom_kpath_description = ipw.HTML( """
diff --git a/src/aiidalab_qe_vibroscopy/utils/euphonic/euphonic_powder_widgets.py b/src/aiidalab_qe_vibroscopy/utils/euphonic/euphonic_powder_widgets.py index f5e86d6..8543482 100644 --- a/src/aiidalab_qe_vibroscopy/utils/euphonic/euphonic_powder_widgets.py +++ b/src/aiidalab_qe_vibroscopy/utils/euphonic/euphonic_powder_widgets.py @@ -1,33 +1,30 @@ -import pathlib -import tempfile -import io - import base64 -from IPython.display import HTML, clear_output, display +from IPython.display import display -import euphonic -from phonopy.file_IO import write_force_constants_to_hdf5, write_disp_yaml import ipywidgets as ipw import plotly.graph_objects as go import plotly.io as pio +import numpy as np # from ..euphonic.bands_pdos import * -from ..euphonic.intensity_maps import * +from ..euphonic.intensity_maps import produce_powder_data, parameters_powder, AttrDict import json from monty.json import jsanitize # sys and os used to prevent euphonic to print in the stdout. -import sys -import os -from aiidalab_qe_vibroscopy.utils.euphonic.euphonic_base_widgets import * +from aiidalab_qe_vibroscopy.utils.euphonic.euphonic_base_widgets import ( + StructureFactorBasePlotWidget, + StructureFactorSettingsBaseWidget, + COLORBAR_DICT, + COLORSCALE, +) class PowderPlotWidget(StructureFactorBasePlotWidget): def __init__(self, spectra, intensity_ref_0K=1, **kwargs): - final_zspectra = spectra.z_data.magnitude final_xspectra = spectra.x_data.magnitude # Data to contour is the sum of two Gaussian functions. @@ -57,9 +54,8 @@ def __init__(self, spectra, intensity_ref_0K=1, **kwargs): ) def _update_spectra(self, spectra): - final_zspectra = spectra.z_data.magnitude - final_xspectra = spectra.x_data.magnitude + final_xspectra = spectra.x_data.magnitude # noqa: F841 # Data to contour is the sum of two Gaussian functions. x, y = np.meshgrid(spectra.x_data.magnitude, spectra.y_data.magnitude) @@ -73,9 +69,11 @@ def _update_spectra(self, spectra): self.fig.add_trace( go.Heatmap( z=final_zspectra.T, - y=y[:, 0] * self.THz_to_meV - if self.E_units_button.value == "meV" - else y[:, 0], + y=( + y[:, 0] * self.THz_to_meV + if self.E_units_button.value == "meV" + else y[:, 0] + ), x=x, colorbar=COLORBAR_DICT, colorscale=COLORSCALE, # imported from euphonic_base_widgets @@ -93,7 +91,6 @@ def _update_spectra(self, spectra): class PowderSettingsWidget(StructureFactorSettingsBaseWidget): def __init__(self, **kwargs): - self.float_qmin = ipw.FloatText( value=0, description="|q|min (1/A)", @@ -173,7 +170,6 @@ class PowderFullWidget(ipw.VBox): """ def __init__(self, fc, intensity_ref_0K=1, **kwargs): - self.fc = fc self.spectra, self.parameters = produce_powder_data( @@ -278,8 +274,6 @@ def _download(payload, filename): document.body.appendChild(link); link.click(); document.body.removeChild(link); - """.format( - payload=payload, filename=filename - ) + """.format(payload=payload, filename=filename) ) display(javas) diff --git a/src/aiidalab_qe_vibroscopy/utils/euphonic/euphonic_q_planes_widgets.py b/src/aiidalab_qe_vibroscopy/utils/euphonic/euphonic_q_planes_widgets.py index fc72baf..c54199c 100644 --- a/src/aiidalab_qe_vibroscopy/utils/euphonic/euphonic_q_planes_widgets.py +++ b/src/aiidalab_qe_vibroscopy/utils/euphonic/euphonic_q_planes_widgets.py @@ -1,18 +1,31 @@ +import ipywidgets as ipw +import base64 +import json +import numpy as np +import plotly.io as pio + +from IPython.display import display + from euphonic.cli.utils import ( - _bands_from_force_constants, - _calc_modes_kwargs, - _compose_style, - _plot_label_kwargs, - get_args, _get_debye_waller, _get_energy_bins, - _get_q_distance, - _get_cli_parser, - load_data_from_file, - matplotlib_save_or_show, ) -from aiidalab_qe_vibroscopy.utils.euphonic.euphonic_base_widgets import * +from aiidalab_qe_vibroscopy.utils.euphonic.euphonic_base_widgets import ( + StructureFactorSettingsBaseWidget, + COLORSCALE, + COLORBAR_DICT, + StructureFactorBasePlotWidget, +) + +from monty.json import jsanitize +import plotly.graph_objects as go + +from aiidalab_qe_vibroscopy.utils.euphonic.intensity_maps import ( + blockPrint, + enablePrint, + AttrDict, +) def produce_Q_section_modes( @@ -26,12 +39,7 @@ def produce_Q_section_modes( k_extension=1, temperature=0, ): - from euphonic import ureg - from aiidalab_qe_vibroscopy.utils.euphonic.intensity_maps import ( - blockPrint, - enablePrint, - ) # see get_Q_section # h: array vector @@ -94,7 +102,6 @@ def produce_Q_section_spectrum( dw=None, labels=None, ): - from aiidalab_qe_vibroscopy.utils.euphonic.intensity_maps import ( blockPrint, enablePrint, @@ -117,9 +124,9 @@ def produce_Q_section_spectrum( sigma = (deltaE) / 2 # Gaussian weights. - weights = np.exp( - -((spectrum.y_data.magnitude - mu) ** 2) / 2 * sigma**2 - ) / np.sqrt(2 * np.pi * sigma**2) + weights = np.exp(-((spectrum.y_data.magnitude - mu) ** 2) / 2 * sigma**2) / np.sqrt( + 2 * np.pi * sigma**2 + ) av_spec = np.average(spectrum.z_data.magnitude, axis=1, weights=weights[:-1]) enablePrint() @@ -128,7 +135,6 @@ def produce_Q_section_spectrum( class QSectionPlotWidget(StructureFactorBasePlotWidget): def __init__(self, h_array, k_array, av_spec, labels, intensity_ref_0K=1, **kwargs): - self.intensity_ref_0K = intensity_ref_0K self.fig = go.FigureWidget() @@ -174,7 +180,6 @@ def _update_spectra( av_spec, labels, ): - # If I do this # self.data = () # I have a delay in the plotting, I have blank plot while it @@ -213,7 +218,6 @@ def _update_spectra( class QSectionSettingsWidget(StructureFactorSettingsBaseWidget): def __init__(self, **kwargs): - super().__init__() self.float_ecenter = ipw.FloatText( @@ -351,7 +355,6 @@ class QSectionFullWidget(ipw.VBox): """ def __init__(self, fc, intensity_ref_0K=1, **kwargs): - self.fc = fc self.title_intensity = ipw.HTML( @@ -502,8 +505,6 @@ def _download(payload, filename): document.body.appendChild(link); link.click(); document.body.removeChild(link); - """.format( - payload=payload, filename=filename - ) + """.format(payload=payload, filename=filename) ) display(javas) 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 d78a42d..1437772 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 @@ -1,33 +1,33 @@ -import pathlib -import tempfile -import io - import base64 -from IPython.display import HTML, clear_output, display - -import euphonic -from phonopy.file_IO import write_force_constants_to_hdf5, write_disp_yaml +from IPython.display import display +import numpy as np import ipywidgets as ipw import plotly.graph_objects as go import plotly.io as pio # from ..euphonic.bands_pdos import * -from ..euphonic.intensity_maps import * +from ..euphonic.intensity_maps import ( + AttrDict, + produce_bands_weigthed_data, + generated_curated_data, +) import json from monty.json import jsanitize # sys and os used to prevent euphonic to print in the stdout. -import sys -import os -from aiidalab_qe_vibroscopy.utils.euphonic.euphonic_base_widgets import * +from aiidalab_qe_vibroscopy.utils.euphonic.euphonic_base_widgets import ( + StructureFactorSettingsBaseWidget, + COLORSCALE, + COLORBAR_DICT, + StructureFactorBasePlotWidget, +) class SingleCrystalPlotWidget(StructureFactorBasePlotWidget): def __init__(self, spectra, intensity_ref_0K=1, **kwargs): - ( final_xspectra, final_zspectra, @@ -71,7 +71,6 @@ def __init__(self, spectra, intensity_ref_0K=1, **kwargs): ) def _update_spectra(self, spectra): - ( final_xspectra, final_zspectra, @@ -91,9 +90,11 @@ def _update_spectra(self, spectra): self.fig.add_trace( go.Heatmap( z=final_zspectra.T, - y=y[:, 0] * self.THz_to_meV - if self.E_units_button.value == "meV" - else y[:, 0], + y=( + y[:, 0] * self.THz_to_meV + if self.E_units_button.value == "meV" + else y[:, 0] + ), x=x, colorbar=COLORBAR_DICT, colorscale=COLORSCALE, # imported from euphonic_base_widgets @@ -115,7 +116,6 @@ def _update_spectra(self, spectra): class SingleCrystalSettingsWidget(StructureFactorSettingsBaseWidget): def __init__(self, **kwargs): - self.custom_kpath_description = ipw.HTML( """
@@ -197,11 +197,11 @@ class SingleCrystalFullWidget(ipw.VBox): """ def __init__(self, fc, **kwargs): - self.fc = fc self.spectra, self.parameters = produce_bands_weigthed_data( - fc=self.fc, plot=False # CHANGED + fc=self.fc, + plot=False, # CHANGED ) self.title_intensity = ipw.HTML( @@ -316,7 +316,7 @@ def curate_path_and_labels( for k in s: labels.append(k.strip()) # AAA missing support for fractions. - l = tuple(map(float, [kk for kk in k.strip().split(" ")])) + l = tuple(map(float, [kk for kk in k.strip().split(" ")])) # noqa: E741 scoords.append(l) coordinates.append(scoords) return coordinates, labels @@ -333,8 +333,6 @@ def _download(payload, filename): document.body.appendChild(link); link.click(); document.body.removeChild(link); - """.format( - payload=payload, filename=filename - ) + """.format(payload=payload, filename=filename) ) display(javas) diff --git a/src/aiidalab_qe_vibroscopy/utils/euphonic/intensity_maps.py b/src/aiidalab_qe_vibroscopy/utils/euphonic/intensity_maps.py index 409e322..0bc1a0e 100644 --- a/src/aiidalab_qe_vibroscopy/utils/euphonic/intensity_maps.py +++ b/src/aiidalab_qe_vibroscopy/utils/euphonic/intensity_maps.py @@ -1,4 +1,3 @@ -from argparse import ArgumentParser from typing import List, Optional import pathlib @@ -15,7 +14,7 @@ Check double imports! """ import euphonic -from euphonic import ureg, Spectrum2D, QpointFrequencies, ForceConstants +from euphonic import ureg, QpointFrequencies, ForceConstants import euphonic.plot from euphonic.util import get_qpoint_labels from euphonic.styles import base_style @@ -24,38 +23,26 @@ _calc_modes_kwargs, _compose_style, _plot_label_kwargs, - get_args, _get_debye_waller, _get_energy_bins, _get_q_distance, - _get_cli_parser, - load_data_from_file, matplotlib_save_or_show, ) from euphonic.cli.utils import ( - _calc_modes_kwargs, - _compose_style, - _get_cli_parser, - _get_debye_waller, - _get_energy_bins, - _get_q_distance, _get_pdos_weighting, _arrange_pdos_groups, - _plot_label_kwargs, ) -from euphonic.cli.utils import load_data_from_file, get_args, matplotlib_save_or_show -import euphonic.plot from euphonic.powder import ( sample_sphere_dos, sample_sphere_pdos, sample_sphere_structure_factor, ) from euphonic.spectra import apply_kinematic_constraints -from euphonic.styles import base_style, intensity_widget_style +from euphonic.styles import intensity_widget_style import euphonic.util -from phonopy.file_IO import write_force_constants_to_hdf5, write_disp_yaml +from phonopy.file_IO import write_force_constants_to_hdf5 # Dummy tqdm function if tqdm progress bars unavailable try: @@ -69,7 +56,9 @@ def tqdm(sequence): return sequence -import sys, os +import sys +import os + # Disable def blockPrint(): @@ -130,9 +119,7 @@ def join_q_paths(coordinates: list, labels: list, delta_q=0.1, G=[0, 0, 0]): list_of_paths = [] Nq_tot = 0 - new_labels_index = ( - [] - ) # here we store the index to then set the labels list as in seekpath, to be refined in the produce_curated_data routine. + new_labels_index = [] # here we store the index to then set the labels list as in seekpath, to be refined in the produce_curated_data routine. for path in coordinates: kxi, kyi, kzi = path[0] # starting point kxf, kyf, kzf = path[1] # end point @@ -273,7 +260,6 @@ def produce_bands_weigthed_data( # print("Getting band path...") # HERE we add the custom path generation: if linear_path: - # 1. get the rl_norm list for conversion delta_q ==> Nq in the join_q_paths structure = fc.crystal.to_spglib_cell() bandpath = seekpath.get_explicit_k_path(structure) @@ -365,10 +351,12 @@ def produce_bands_weigthed_data( style = _compose_style(user_args=args, base=[base_style]) if plot: with matplotlib.style.context(style): - - fig = euphonic.plot.plot_2d( - spectra, vmin=args.vmin, vmax=args.vmax, **plot_label_kwargs - ) + fig = euphonic.plot.plot_2d( # noqa F841 + spectra, # noqa F841 + vmin=args.vmin, # noqa F841 + vmax=args.vmax, # noqa F841 + **plot_label_kwargs, # noqa F841 + ) # noqa F841 matplotlib_save_or_show(save_filename=args.save_to) enablePrint() @@ -756,12 +744,11 @@ def generate_force_constant_instance( def export_euphonic_data(node, fermi_energy=None): - - if not "vibronic" in node.outputs: + if "vibronic" not in node.outputs: # Not a phonon calculation return None else: - if not "phonon_bands" in node.outputs.vibronic: + if "phonon_bands" not in node.outputs.vibronic: return None output_set = node.outputs.vibronic.phonon_bands @@ -802,7 +789,6 @@ def generated_curated_data(spectra): if len(ticks_positions) > 1: if ticks_positions[-1] < ticks_positions[-2] or shift: if ticks_positions[-1] == 0: # new linear path - ticks_positions.pop() last = ticks_labels.pop().strip() diff --git a/src/aiidalab_qe_vibroscopy/utils/harmonic/__init__.py b/src/aiidalab_qe_vibroscopy/utils/harmonic/__init__.py deleted file mode 100644 index e8959e0..0000000 --- a/src/aiidalab_qe_vibroscopy/utils/harmonic/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# from aiidalab_qe_vibroscopy.harmonic.settings import Setting -# from aiidalab_qe_vibroscopy.harmonic.workchain import workchain_and_builder -# from aiidalab_qe_vibroscopy.harmonic.result import Result -from aiidalab_qe.common.panel import OutlinePanel - - -class Outline(OutlinePanel): - title = "Phonon properties" - # description = "Select to proceed with the calculation of the phononic and dielectric properties" - - -property = { - "outline": Outline, - # "setting": Setting, - # "workchain": workchain_and_builder, - # "result": Result, -} diff --git a/src/aiidalab_qe_vibroscopy/utils/harmonic/result.py b/src/aiidalab_qe_vibroscopy/utils/harmonic/result.py deleted file mode 100644 index 5557803..0000000 --- a/src/aiidalab_qe_vibroscopy/utils/harmonic/result.py +++ /dev/null @@ -1,178 +0,0 @@ -"""Bands results view widgets - -""" - - - -from aiidalab_qe.common.panel import ResultPanel - -import numpy as np - - -def export_phononworkchain_data(node, fermi_energy=None): - - """ - We have multiple choices: BANDS, DOS, THERMODYNAMIC, FORCES. - """ - - import json - - from monty.json import jsanitize - - parameters = {} - - if not "vibronic" in node.outputs: - return None - else: - if ( - not "harmonic" in node.outputs.vibronic - and not "phonon" in node.outputs.vibronic - ): - return None - - output_set = ( - node.outputs.vibronic.harmonic - if "harmonic" in node.outputs.vibronic - else node.outputs.vibronic.phonon - ) - - if "output_phonopy" in output_set: - if "phonon_bands" in output_set.output_phonopy: - data = json.loads( - output_set.output_phonopy.phonon_bands._exportcontent( - "json", comments=False - )[0] - ) - # The fermi energy from band calculation is not robust. - """data["fermi_level"] = ( - fermi_energy or node.outputs.phonons.band_parameters["fermi_energy"] - )""" - # to be optimized: use the above results!!! - bands = output_set.output_phonopy.phonon_bands.get_bands() - data["fermi_level"] = 0 - data["Y_label"] = "Dispersion (THz)" - - # it does work now. - parameters["energy_range"] = { - "ymin": np.min(bands) - 0.1, - "ymax": np.max(bands) + 0.1, - } - - # TODO: THERMOD, FORCES; minors: bands-labels, done: no-fermi-in-dos. - - return [jsanitize(data), parameters, "bands"] - elif "total_phonon_dos" in output_set.output_phonopy: - ( - what, - energy_dos, - units_omega, - ) = output_set.output_phonopy.total_phonon_dos.get_x() - ( - dos_name, - dos_data, - units_dos, - ) = output_set.output_phonopy.total_phonon_dos.get_y()[0] - dos = [] - # The total dos parsed - tdos = { - "label": "Total DOS", - "x": energy_dos.tolist(), - "y": dos_data.tolist(), - "borderColor": "#8A8A8A", # dark gray - "backgroundColor": "#8A8A8A", # light gray - "backgroundAlpha": "40%", - "lineStyle": "solid", - } - dos.append(tdos) - - parameters["energy_range"] = { - "ymin": np.min(energy_dos) - 0.1, - "ymax": np.max(energy_dos) + 0.1, - } - - data_dict = { - "fermi_energy": 0, # I do not want it in my plot - "dos": dos, - } - - return [json.loads(json.dumps(data_dict)), parameters, "dos"] - elif "thermal_properties" in output_set.output_phonopy: - what, T, units_k = output_set.output_phonopy.thermal_properties.get_x() - ( - F_name, - F_data, - units_F, - ) = output_set.output_phonopy.thermal_properties.get_y()[0] - ( - Entropy_name, - Entropy_data, - units_entropy, - ) = output_set.output_phonopy.thermal_properties.get_y()[1] - ( - Cv_name, - Cv_data, - units_Cv, - ) = output_set.output_phonopy.thermal_properties.get_y()[2] - - return ( - [T, F_data, units_F, Entropy_data, units_entropy, Cv_data, units_Cv], - [], - "thermal", - ) - - else: - return None - - -class Result(ResultPanel): - - title = "Phonon property" - workchain_label = "phonons" - - def _update_view(self): - bands_data = export_phononworkchain_data(self.node) - - if bands_data[2] == "bands": - _bands_plot_view = BandsPlotWidget( - bands=[bands_data[0]], - **bands_data[1], - ) - self.children = [ - _bands_plot_view, - ] - elif bands_data[2] == "dos": - _bands_plot_view = BandsPlotWidget( - dos=bands_data[0], - plot_fermilevel=False, - show_legend=False, - **bands_data[1], - ) - self.children = [ - _bands_plot_view, - ] - - elif bands_data[2] == "thermal": - import plotly.graph_objects as go - - T = bands_data[0][0] - F = bands_data[0][1] - F_units = bands_data[0][2] - E = bands_data[0][3] - E_units = bands_data[0][4] - Cv = bands_data[0][5] - Cv_units = bands_data[0][6] - - g = go.FigureWidget( - layout=go.Layout( - title=dict(text="Thermal properties"), - barmode="overlay", - ) - ) - g.layout.xaxis.title = "Temperature (K)" - g.add_scatter(x=T, y=F, name=f"Helmoltz Free Energy ({F_units})") - 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})") - - self.children = [ - g, - ] diff --git a/src/aiidalab_qe_vibroscopy/utils/harmonic/settings.py b/src/aiidalab_qe_vibroscopy/utils/harmonic/settings.py deleted file mode 100644 index de4209c..0000000 --- a/src/aiidalab_qe_vibroscopy/utils/harmonic/settings.py +++ /dev/null @@ -1,156 +0,0 @@ -# -*- coding: utf-8 -*- -"""Panel for PhononWorkchain plugin. - -Authors: - - * Miki Bonacci - Inspired by Xing Wang -""" -import ipywidgets as ipw -from aiida.orm import Float, Int, Str - -from aiidalab_qe.common.panel import Panel - - -class Setting(Panel): - title = "Phonons Settings" - identifier = "harmonic" - - def __init__(self, **kwargs): - self.settings_title = ipw.HTML( - """
-

Phonons settings

""" - ) - self.settings_help = ipw.HTML( - """
- Please select the phonon-related properties to be computed in this simulation. - You can select also the size of the supercell to be used; usually, a 2X2X2 supercell is enough - to obtain converged results. -
""" - ) - - self.polar_help = ipw.HTML( - """
- If the material is polar, also 3rd order derivatives will be computed and more - accurate phonon band interpolation is performed. -
""" - ) - - self.workchain_protocol = ipw.ToggleButtons( - options=["fast", "moderate", "precise"], - value="moderate", - ) - - # I want to be able to select more than only one... this has to change at the PhononWorkChain level. - self.phonon_property = ipw.Dropdown( - options=[ - ["band structure", "BANDS"], - ["density of states (DOS)", "DOS"], - ["thermal properties", "THERMODYNAMIC"], - ["force constants", "NONE"], - ["none", "none"], - ], - value="BANDS", - description="Phonon property:", - disabled=False, - style={"description_width": "initial"}, - ) - - # 1. Supercell - self.supercell = [1, 1, 1] - - def change_supercell(_=None): - self.supercell = [ - _supercell[0].value, - _supercell[1].value, - _supercell[2].value, - ] - - _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"}), - ] - for elem in _supercell: - elem.observe(change_supercell, names="value") - self.supercell_selector = ipw.HBox( - children=[ - ipw.HTML( - description="Supercell size:", - style={"description_width": "initial"}, - ) - ] - + _supercell, - ) - - self.dielectric_property = ipw.Dropdown( - options=[ - ["dielectric tensor", "dielectric"], - #'ir', - #'raman', - #'born-charges', - #'nac', - #'bec', - #'susceptibility-derivative', - #'non-linear-susceptibility', - ["none", "none"], - ], - value="none", - description="Dielectric property:", - disabled=False, - style={"description_width": "initial"}, - ) - - # to trigger Dielectric property = Raman... FOR POLAR MATERIALS. - self.material_is_polar = ipw.ToggleButtons( - options=[("Off", "off"), ("On", "on")], - value="off", - style={"description_width": "initial"}, - ) - - self.children = [ - self.settings_title, - self.settings_help, - ipw.HBox( - children=[ - self.phonon_property, - self.supercell_selector, - ], - layout=ipw.Layout(justify_content="flex-start"), - ), - self.dielectric_property, - self.polar_help, - ipw.HBox( - children=[ - ipw.Label( - "Material is polar:", - layout=ipw.Layout(justify_content="flex-start", width="120px"), - ), - self.material_is_polar, - ] - ), - ] - super().__init__(**kwargs) - - def get_panel_value(self): - """Return a dictionary with the input parameters for the plugin.""" - if isinstance(self.phonon_property, str): - return { - "phonon_property": self.phonon_property, - "dielectric_property": self.dielectric_property, - "material_is_polar": self.material_is_polar, - "supercell_selector": self.supercell, - } - return { - "phonon_property": self.phonon_property.value, - "dielectric_property": self.dielectric_property.value, - "material_is_polar": self.material_is_polar.value, - "supercell_selector": self.supercell, - } - - def load_panel_value(self, input_dict): - """Load a dictionary with the input parameters for the plugin.""" - self.phonon_property.value = input_dict.get("phonon_property", "NONE") - self.dielectric_property.value = input_dict.get("dielectric_property", "none") - self.material_is_polar.value = input_dict.get("material_is_polar", "off") - self.supercell = input_dict.get("supercell_selector", [1, 1, 1]) diff --git a/src/aiidalab_qe_vibroscopy/utils/harmonic/workchain.py b/src/aiidalab_qe_vibroscopy/utils/harmonic/workchain.py deleted file mode 100644 index 5ae8f5d..0000000 --- a/src/aiidalab_qe_vibroscopy/utils/harmonic/workchain.py +++ /dev/null @@ -1,114 +0,0 @@ -from aiida.orm import load_code, Dict -from aiida.plugins import WorkflowFactory -from aiida_quantumespresso.common.types import ElectronicType, SpinType - -from aiida_vibroscopy.common.properties import PhononProperty - -HarmonicWorkChain = WorkflowFactory("vibroscopy.phonons.harmonic") - -""" -The logic is that HarmonicWorkchain can run PhononWorkchain and DielectricWorkchain, skipping the second -but not the firstfor now we do not support to run only DielectricWorkchain. -""" - - -def get_builder(codes, structure, parameters): - from copy import deepcopy - - protocol = parameters["workchain"].pop("protocol", "fast") - pw_code = codes.get("pw") - phonopy_code = codes.get("phonopy") - phonon_property = PhononProperty[ - parameters["harmonic"].pop("phonon_property", "none") - ] - supercell_matrix = parameters["harmonic"].pop("supercell_selector", None) - dielectric_property = parameters["harmonic"].pop("dielectric_property", "none") - polar = parameters["harmonic"].pop("material_is_polar", "off") - - if polar == "on": - dielectric_property = "raman" - - scf_overrides = deepcopy(parameters["advanced"]) - overrides = { - "phonon": { - "scf": scf_overrides, - "supercell_matrix": supercell_matrix, - }, - "dielectric": {"scf": scf_overrides, "property": dielectric_property}, - } - - builder = HarmonicWorkChain.get_builder_from_protocol( - pw_code=pw_code, - phonopy_code=phonopy_code, - structure=structure, - protocol=protocol, - overrides=overrides, - phonon_property=phonon_property, - electronic_type=ElectronicType(parameters["workchain"]["electronic_type"]), - spin_type=SpinType(parameters["workchain"]["spin_type"]), - initial_magnetic_moments=parameters["advanced"]["initial_magnetic_moments"], - ) - - # MB supposes phonopy will always run serially, otherwise choose phono3py - # also this is needed to be set here. - builder.phonopy.metadata.options.resources = { - "num_machines": 1, - "num_mpiprocs_per_machine": 1, - } - - builder.phonon.phonopy.metadata.options.resources = ( - builder.phonopy.metadata.options.resources - ) - - # should be automatic inside HarmonicWorkchain. - builder.phonon.phonopy.parameters = Dict(dict={}) - builder.phonopy.parameters = builder.phonon.phonopy.parameters - builder.phonon.phonopy.code = builder.phonopy.code - - builder.phonopy.parameters = Dict(dict=phonon_property.value) - - return builder - - -def trigger_workchain(name, parameters): - if name not in ["harmonic", "phonons", "dielectric", "iraman"]: - return True - import copy - - parameters_ = copy.deepcopy(parameters) - harmonic_params = parameters_.pop("harmonic", {}) - phonon_property = harmonic_params.pop("phonon_property", "none") - dielectric_property = harmonic_params.pop("dielectric_property", "none") - polar = harmonic_params.pop("material_is_polar", "off") - - trigger_spectrum = parameters_.pop("iraman", {}).pop("spectrum", False) - - trigger_harmonic = ( - polar == "on" or (phonon_property != "none" and dielectric_property != "none") - ) and not trigger_spectrum - trigger_phonons = ( - phonon_property != "none" - and dielectric_property == "none" - and not trigger_harmonic - ) and not trigger_spectrum - trigger_dielectric = ( - phonon_property == "none" - and dielectric_property != "none" - and not trigger_harmonic - ) and not trigger_spectrum - - trigger = { - "iraman": trigger_spectrum, - "harmonic": trigger_harmonic, - "phonons": trigger_phonons, - "dielectric": trigger_dielectric, - } - - return trigger[name] - - -workchain_and_builder = { - "workchain": HarmonicWorkChain, - "exclude": ("clean_workdir",), - "get_builder": get_builder, -} diff --git a/src/aiidalab_qe_vibroscopy/utils/phonons/__init__.py b/src/aiidalab_qe_vibroscopy/utils/phonons/__init__.py deleted file mode 100644 index 4fa0bfe..0000000 --- a/src/aiidalab_qe_vibroscopy/utils/phonons/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# from aiidalab_qe_vibroscopy.phonons.settings import Setting -# from aiidalab_qe_vibroscopy.phonons.workchain import workchain_and_builder -# from aiidalab_qe_vibroscopy.phonons.result import Result -from aiidalab_qe.common.panel import OutlinePanel -import ipywidgets as ipw - - -class Outline(OutlinePanel): - title = "Phonon band structure" - help = "Harmonic approximation" - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.layout = ipw.Layout(width="600px", display="none") - - -property = { - "outline": Outline, - # "setting": Setting, - # "workchain": workchain_and_builder, - # "result": Result, -} diff --git a/src/aiidalab_qe_vibroscopy/utils/phonons/result.py b/src/aiidalab_qe_vibroscopy/utils/phonons/result.py index 8d7e5c3..534cc50 100644 --- a/src/aiidalab_qe_vibroscopy/utils/phonons/result.py +++ b/src/aiidalab_qe_vibroscopy/utils/phonons/result.py @@ -1,6 +1,4 @@ -"""Bands results view widgets - -""" +"""Bands results view widgets""" from aiidalab_qe.common.bandpdoswidget import cmap, get_bands_labeling @@ -11,10 +9,10 @@ def replace_symbols_with_uppercase(data): symbols_mapping = { "$\Gamma$": "\u0393", - "$\\Gamma$": "\u0393", + "$\\Gamma$": "\u0393", # noqa: F601 "$\\Delta$": "\u0394", - "$\\Lambda$": "\u039B", - "$\\Sigma$": "\u03A3", + "$\\Lambda$": "\u039b", + "$\\Sigma$": "\u03a3", "$\\Epsilon$": "\u0395", } @@ -25,15 +23,10 @@ def replace_symbols_with_uppercase(data): def export_phononworkchain_data(node, fermi_energy=None): - """ We have multiple choices: BANDS, DOS, THERMODYNAMIC. """ - import json - - from monty.json import jsanitize - full_data = { "bands": None, "pdos": None, @@ -41,11 +34,10 @@ def export_phononworkchain_data(node, fermi_energy=None): } parameters = {} - if not "vibronic" in node.outputs: + if "vibronic" not in node.outputs: return None if "phonon_bands" in node.outputs.vibronic: - """ copied and pasted from aiidalab_qe.common.bandsplotwidget. adapted for phonon outputs @@ -60,7 +52,9 @@ def export_phononworkchain_data(node, fermi_energy=None): replace_symbols_with_uppercase(data["pathlabels"]) data["Y_label"] = "Dispersion (THz)" - bands = node.outputs.vibronic.phonon_bands._get_bandplot_data(cartesian=True, prettify_format=None, join_symbol=None, get_segments=True) + bands = node.outputs.vibronic.phonon_bands._get_bandplot_data( + cartesian=True, prettify_format=None, join_symbol=None, get_segments=True + ) parameters["energy_range"] = { "ymin": np.min(bands["y"]) - 0.1, "ymax": np.max(bands["y"]) + 0.1, @@ -71,7 +65,6 @@ def export_phononworkchain_data(node, fermi_energy=None): full_data["bands"] = [data, parameters] if "phonon_pdos" in node.outputs.vibronic: - phonopy_calc = node.outputs.vibronic.phonon_pdos.creator kwargs = {} @@ -84,9 +77,10 @@ def export_phononworkchain_data(node, fermi_energy=None): symbols = node.inputs.structure.get_ase().get_chemical_symbols() pdos = node.outputs.vibronic.phonon_pdos - index_dict, dos_dict = {}, { - "total_dos": np.zeros(np.shape(pdos.get_y()[0][1])) - } + index_dict, dos_dict = ( + {}, + {"total_dos": np.zeros(np.shape(pdos.get_y()[0][1]))}, + ) for atom in set(symbols): # index lists index_dict[atom] = [ diff --git a/src/aiidalab_qe_vibroscopy/utils/phonons/settings.py b/src/aiidalab_qe_vibroscopy/utils/phonons/settings.py deleted file mode 100644 index a8d759b..0000000 --- a/src/aiidalab_qe_vibroscopy/utils/phonons/settings.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -"""Setting Panel for PhononWorkchain plugin. - -Authors: - - * Miki Bonacci - Inspired by Xing Wang -""" -import ipywidgets as ipw - -from aiidalab_qe.common.panel import Panel -from aiida_vibroscopy.common.properties import PhononProperty - - -class Setting(Panel): - title = "Phonons Settings" - identifier = "phonons" - - def __init__(self, **kwargs): - self.settings_title = ipw.HTML( - """
-

Phonons settings

""" - ) - self.settings_help = ipw.HTML( - """
- Please select the phonon-related properties to be computed in the simulation. - If the material is polar, also 3rd order derivatives will be computed and more - accurate phonon band interpolation is done. -
""" - ) - self.workchain_protocol = ipw.ToggleButtons( - options=["fast", "moderate", "precise"], - value="moderate", - ) - - # I want to be able to select more than only one... this has to change at the PhononWorkChain level. - self.phonon_property = ipw.Dropdown( - options=[ - ["bands", "BANDS"], - ["dos", "DOS"], - ["thermodynamic", "THERMODYNAMIC"], - ["force constants", "NONE"], - ], - value="BANDS", - description="Phonon property:", - disabled=False, - style={"description_width": "initial"}, - ) - - self.children = [ - self.settings_title, - self.settings_help, - self.phonon_property, - ] - super().__init__(**kwargs) - - def get_panel_value(self): - """Return a dictionary with the input parameters for the plugin.""" - if isinstance(self.phonon_property, str): - return { - "phonon_property": self.phonon_property, - } - return { - "phonon_property": self.phonon_property.value, - } - - def load_panel_value(self, input_dict): - """Load a dictionary with the input parameters for the plugin.""" - self.phonon_property.value = input_dict.get("phonon_property", "BANDS") diff --git a/src/aiidalab_qe_vibroscopy/utils/phonons/workchain.py b/src/aiidalab_qe_vibroscopy/utils/phonons/workchain.py deleted file mode 100644 index ba71add..0000000 --- a/src/aiidalab_qe_vibroscopy/utils/phonons/workchain.py +++ /dev/null @@ -1,51 +0,0 @@ -from aiida.orm import load_code -from aiida.plugins import WorkflowFactory -from aiida_quantumespresso.common.types import ElectronicType, SpinType -from aiida_vibroscopy.common.properties import PhononProperty - -PhononWorkChain = WorkflowFactory("vibroscopy.phonons.phonon") - - -def get_builder(codes, structure, parameters): - from copy import deepcopy - - pw_code = codes.get("pw") - phonopy_code = codes.get("phonopy", None) - phonon_property = PhononProperty[ - parameters["harmonic"].pop("phonon_property", "none") - ] - supercell_matrix = parameters["harmonic"].pop("supercell_selector", None) - protocol = parameters["workchain"].pop("protocol", "fast") - scf_overrides = deepcopy(parameters["advanced"]) - overrides = { - "scf": scf_overrides, - "supercell_matrix": supercell_matrix, - } - - builder = PhononWorkChain.get_builder_from_protocol( - pw_code=pw_code, - phonopy_code=phonopy_code, - structure=structure, - protocol=protocol, - overrides=overrides, - phonon_property=phonon_property, - electronic_type=ElectronicType(parameters["workchain"]["electronic_type"]), - spin_type=SpinType(parameters["workchain"]["spin_type"]), - initial_magnetic_moments=parameters["advanced"]["initial_magnetic_moments"], - ) - - # MB supposes phonopy will always run serially, otherwise choose phono3py - # also this is needed to be set here. - builder.phonopy.metadata.options.resources = { - "num_machines": 1, - "num_mpiprocs_per_machine": 1, - } - - return builder - - -workchain_and_builder = { - "workchain": PhononWorkChain, - "exclude": ("clean_workdir",), - "get_builder": get_builder, -} diff --git a/src/aiidalab_qe_vibroscopy/utils/raman/__init__.py b/src/aiidalab_qe_vibroscopy/utils/raman/__init__.py deleted file mode 100644 index 8aa2b69..0000000 --- a/src/aiidalab_qe_vibroscopy/utils/raman/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# from aiidalab_qe_vibroscopy.raman.settings import Setting -# from aiidalab_qe_vibroscopy.raman.workchain import workchain_and_builder -# from aiidalab_qe_vibroscopy.raman.result import Result -from aiidalab_qe.common.panel import OutlinePanel - - -class Outline(OutlinePanel): - title = "Vibrational spectra" - # description = "IR and Raman spectra; you may also select phononic and dielectric properties" - - -property = { - "outline": Outline, - # "setting": Setting, - # "workchain": workchain_and_builder, - # "result": Result, -} diff --git a/src/aiidalab_qe_vibroscopy/utils/raman/result.py b/src/aiidalab_qe_vibroscopy/utils/raman/result.py index c074735..029eaf2 100644 --- a/src/aiidalab_qe_vibroscopy/utils/raman/result.py +++ b/src/aiidalab_qe_vibroscopy/utils/raman/result.py @@ -1,16 +1,13 @@ -"""Bands results view widgets +"""Bands results view widgets""" -""" from __future__ import annotations -from aiidalab_qe.common.panel import ResultPanel import ipywidgets as ipw import numpy as np from IPython.display import HTML, clear_output, display import base64 import json -from ase.visualize import view import nglview as nv from ase import Atoms @@ -26,7 +23,6 @@ def plot_powder( broadening_function=multilorentz, normalize: bool = True, ): - frequencies = np.array(frequencies) intensities = np.array(intensities) @@ -44,12 +40,11 @@ def plot_powder( def export_iramanworkchain_data(node): - """ We have multiple choices: IR, RAMAN. """ - if not "vibronic" in node.outputs: + if "vibronic" not in node.outputs: return None else: if "iraman" in node.outputs.vibronic: @@ -61,7 +56,6 @@ def export_iramanworkchain_data(node): return None if "vibrational_data" in output_node: - # We enable the possibility to provide both spectra. # We give as output or string, or the output node. @@ -87,14 +81,13 @@ def export_iramanworkchain_data(node): # sometimes IR/Raman has not active peaks by symmetry, or due to the fact that 1st order cannot capture them if len(total_intensities) == 0: - spectra_data[ - "Ir" - ] = "No IR modes detected." # explanation added in the main results script of the app. + spectra_data["Ir"] = ( + "No IR modes detected." # explanation added in the main results script of the app. + ) else: spectra_data["Ir"] = output_node if "raman_tensors" in vibro.get_arraynames(): - ( polarized_intensities, depolarized_intensities, @@ -105,9 +98,9 @@ def export_iramanworkchain_data(node): # sometimes IR/Raman has not active peaks by symmetry, or due to the fact that 1st order cannot capture them if len(total_intensities) == 0: - spectra_data[ - "Raman" - ] = "No Raman modes detected." # explanation added in the main results script of the app. + spectra_data["Raman"] = ( + "No Raman modes detected." # explanation added in the main results script of the app. + ) else: spectra_data["Raman"] = output_node @@ -246,9 +239,7 @@ def _download(payload, filename): document.body.appendChild(link); link.click(); document.body.removeChild(link); - """.format( - payload=payload, filename=filename - ) + """.format(payload=payload, filename=filename) ) display(javas) @@ -289,7 +280,9 @@ def _on_plot_button_clicked(self, change): total_intensities = polarized_intensities self.frequencies, self.intensities = plot_powder( - frequencies, total_intensities, self.broadening.value, + frequencies, + total_intensities, + self.broadening.value, ) self._display_figure() @@ -340,7 +333,7 @@ def _check_inputs_correct(self, polarization): try: dir_values = [float(i) for i in input_values] return dir_values, True - except: + except: # noqa: E722 return dir_values, False else: return dir_values, False @@ -357,7 +350,7 @@ def _spectrum_widget(self): fig.layout.xaxis.title = "Wavenumber (cm-1)" fig.layout.yaxis.title = "Intensity (arb. units)" fig.layout.xaxis.nticks = 0 - fig.add_scatter(x=self.frequencies, y=self.intensities, name=f"") + fig.add_scatter(x=self.frequencies, y=self.intensities, name="") fig.update_layout( height=500, width=700, diff --git a/src/aiidalab_qe_vibroscopy/utils/raman/settings.py b/src/aiidalab_qe_vibroscopy/utils/raman/settings.py deleted file mode 100644 index 4d1b907..0000000 --- a/src/aiidalab_qe_vibroscopy/utils/raman/settings.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- -"""Panel for PhononWorkchain plugin. - -Authors: - - * Miki Bonacci - Inspired by Xing Wang -""" -import ipywidgets as ipw -from aiida.orm import Float, Int, Str - -from aiidalab_qe.common.panel import Panel -from aiida_vibroscopy.common.properties import PhononProperty - - -class Setting(Panel): - title = "Vibrational Settings" - - def __init__(self, **kwargs): - self.settings_title = ipw.HTML( - """
-

Vibrational spectra settings

""" - ) - self.settings_help = ipw.HTML( - """
- Please select the vibrational spectrum to be computed in this simulation. -
""" - ) - self.workchain_protocol = ipw.ToggleButtons( - options=["fast", "moderate", "precise"], - value="moderate", - ) - - self.spectrum = ipw.ToggleButtons( - options=[("Infrared", "ir"), ("Raman", "raman")], - value="raman", - style={"description_width": "initial"}, - ) - - # 1. Supercell - self.supercell = [1, 1, 1] - - def change_supercell(_=None): - self.supercell = [ - _supercell[0].value, - _supercell[1].value, - _supercell[2].value, - ] - - _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"}), - ] - for elem in _supercell: - elem.observe(change_supercell, names="value") - self.supercell_selector = ipw.HBox( - children=[ - ipw.HTML( - description="Supercell size:", - style={"description_width": "initial"}, - ) - ] - + _supercell, - ) - - self.children = [ - self.settings_title, - self.settings_help, - ipw.HBox( - children=[ - ipw.Label( - "Spectroscopy:", - layout=ipw.Layout(justify_content="flex-start", width="120px"), - ), - self.spectrum, - ] - ), - self.supercell_selector, - ] - super().__init__(**kwargs) - - def get_panel_value(self): - """Return a dictionary with the input parameters for the plugin.""" - return {"spectrum": self.spectrum.value, "supercell_selector": self.supercell} - - def load_panel_value(self, input_dict): - """Load a dictionary with the input parameters for the plugin.""" - self.spectrum.value = input_dict.get("spectrum", "raman") - self.supercell = input_dict.get("supercell_selector", [1, 1, 1]) diff --git a/src/aiidalab_qe_vibroscopy/utils/raman/workchain.py b/src/aiidalab_qe_vibroscopy/utils/raman/workchain.py deleted file mode 100644 index f001bde..0000000 --- a/src/aiidalab_qe_vibroscopy/utils/raman/workchain.py +++ /dev/null @@ -1,53 +0,0 @@ -from aiida.orm import load_code, Dict -from aiida.plugins import WorkflowFactory -from aiida_quantumespresso.common.types import ElectronicType, SpinType - -IRamanSpectraWorkChain = WorkflowFactory("vibroscopy.spectra.iraman") - - -def get_builder(codes, structure, parameters): - from copy import deepcopy - - protocol = parameters["workchain"].pop("protocol", "fast") - pw_code = codes.get("pw") - phonopy_code = codes.get("phonopy") - supercell_matrix = parameters["iraman"].pop("supercell_selector", None) - dielectric_property = parameters["iraman"]["spectrum"] - res = { - "num_machines": 1, - "num_mpiprocs_per_machine": 1, - } - - scf_overrides = deepcopy(parameters["advanced"]) - overrides = { - "phonon": { - "scf": scf_overrides, - "supercell_matrix": supercell_matrix, - }, - "dielectric": {"scf": scf_overrides, "property": dielectric_property}, - } - - builder = IRamanSpectraWorkChain.get_builder_from_protocol( - code=pw_code, - structure=structure, - protocol=protocol, - overrides=overrides, - electronic_type=ElectronicType(parameters["workchain"]["electronic_type"]), - spin_type=SpinType(parameters["workchain"]["spin_type"]), - initial_magnetic_moments=parameters["advanced"]["initial_magnetic_moments"], - ) - - builder.dielectric.property = dielectric_property - - builder.phonon.phonopy.code = phonopy_code - builder.phonon.phonopy.metadata.options.resources = res - builder.phonon.phonopy.parameters = Dict({}) - - return builder - - -workchain_and_builder = { - "workchain": IRamanSpectraWorkChain, - "exclude": ("clean_workdir",), - "get_builder": get_builder, -} diff --git a/src/aiidalab_qe_vibroscopy/workflows/vibroworkchain.py b/src/aiidalab_qe_vibroscopy/workflows/vibroworkchain.py index 7bc3d29..ee05481 100644 --- a/src/aiidalab_qe_vibroscopy/workflows/vibroworkchain.py +++ b/src/aiidalab_qe_vibroscopy/workflows/vibroworkchain.py @@ -1,12 +1,11 @@ """Implementation of the VibroWorkchain for managing the aiida-vibroscopy workchains.""" + from aiida import orm from aiida.common import AttributeDict -from aiida.engine import ToContext, WorkChain, calcfunction -from aiida.orm import AbstractCode, Int, Float, Dict, Code, StructureData, load_code +from aiida.engine import WorkChain +from aiida.orm import Dict, StructureData from aiida.plugins import WorkflowFactory, CalculationFactory -from aiida_quantumespresso.utils.mapping import prepare_process_inputs -from aiida_quantumespresso.common.types import ElectronicType, SpinType -from aiida.engine import WorkChain, calcfunction, if_ +from aiida.engine import if_ from aiida_vibroscopy.common.properties import PhononProperty from aiida_quantumespresso.calculations.functions.create_kpoints_from_distance import ( create_kpoints_from_distance, @@ -143,7 +142,7 @@ def determine_symmetry_path(structure): and math.isclose(cell_angles[2], 90.0, abs_tol=tolerance) ): "square", ( - math.isclose(cell_lengths[0], cell_lengths[1], abs_tol=tolerance) == False + not math.isclose(cell_lengths[0], cell_lengths[1], abs_tol=tolerance) and math.isclose(cell_angles[2], 90.0, abs_tol=tolerance) ): "rectangular", ( @@ -154,8 +153,8 @@ def determine_symmetry_path(structure): ) ): "rectangular_centered", ( - math.isclose(cell_lengths[0], cell_lengths[1], abs_tol=tolerance) == False - and math.isclose(cell_angles[2], 90.0, abs_tol=tolerance) == False + not math.isclose(cell_lengths[0], cell_lengths[1], abs_tol=tolerance) + and not math.isclose(cell_angles[2], 90.0, abs_tol=tolerance) ): "oblique", } @@ -180,6 +179,7 @@ def determine_symmetry_path(structure): class VibroWorkChain(WorkChain): "WorkChain to compute vibrational property of a crystal." + label = "vibro" @classmethod @@ -235,15 +235,17 @@ def define(cls, spec): }, ) spec.expose_inputs( - PhonopyCalculation, namespace='phonopy_calc', + PhonopyCalculation, + namespace="phonopy_calc", namespace_options={ - 'required': False, 'populate_defaults': False, - 'help': ( - 'Inputs for the `PhonopyCalculation` that will' - 'be used to calculate the inter-atomic force constants, or for post-processing.' - ) + "required": False, + "populate_defaults": False, + "help": ( + "Inputs for the `PhonopyCalculation` that will" + "be used to calculate the inter-atomic force constants, or for post-processing." + ), }, - exclude=['phonopy_data', 'force_constants', 'parameters'], + exclude=["phonopy_data", "force_constants", "parameters"], ) spec.input( "phonopy_bands_dict", @@ -358,7 +360,6 @@ def get_builder_from_protocol( sub processes that are called by this workchain. :return: a process builder instance with all inputs defined ready for launch. """ - from aiida_quantumespresso.workflows.protocols.utils import recursive_merge if simulation_mode not in range(1, 5): raise ValueError("simulation_mode not in [1,2,3,4]") @@ -394,8 +395,10 @@ def get_builder_from_protocol( builder_harmonic.phonopy.parameters = ( builder_harmonic.phonon.phonopy.parameters ) - - builder_harmonic.dielectric.scf.pw.code = dielectric_code # we have a specific code for DielectricWorkChain + + builder_harmonic.dielectric.scf.pw.code = ( + dielectric_code # we have a specific code for DielectricWorkChain + ) builder_harmonic.phonon.phonopy.code = builder_harmonic.phonopy.code builder_harmonic.phonopy.parameters = Dict(dict=phonon_property.value) @@ -429,7 +432,9 @@ def get_builder_from_protocol( "write_mesh": False, } ) - builder.phonopy_thermo_dict = Dict(dict=PhononProperty.THERMODYNAMIC.value) + builder.phonopy_thermo_dict = Dict( + dict=PhononProperty.THERMODYNAMIC.value + ) if structure.pbc == (True, False, False): builder.phonopy_bands_dict = Dict( @@ -440,11 +445,13 @@ def get_builder_from_protocol( "band_labels": [GAMMA, "$\\mathrm{X}$"], } ) - #change symprec for 1D materials to 1e-3 + # change symprec for 1D materials to 1e-3 builder.phonopy_pdos_dict["symmetry_tolerance"] = 1e-3 builder.phonopy_thermo_dict["symmetry_tolerance"] = 1e-3 builder.harmonic.symmetry.symprec = orm.Float(1e-3) - builder.harmonic.phonon.phonopy.parameters = orm.Dict({"symmetry_tolerance": 1e-3}) + builder.harmonic.phonon.phonopy.parameters = orm.Dict( + {"symmetry_tolerance": 1e-3} + ) elif structure.pbc == (True, True, False): symmetry_path = determine_symmetry_path(structure) @@ -477,15 +484,14 @@ def get_builder_from_protocol( ) builder.phonopy_thermo_dict = Dict( - dict= { - 'tprop': True, - 'mesh': 200, # 1000 is too heavy - 'write_mesh': False, - } - ) + dict={ + "tprop": True, + "mesh": 200, # 1000 is too heavy + "write_mesh": False, + } + ) elif simulation_mode == 2: - builder_iraman = IRamanSpectraWorkChain.get_builder_from_protocol( code=phonon_code, structure=structure, @@ -494,7 +500,9 @@ def get_builder_from_protocol( **kwargs, ) - builder_iraman.dielectric.scf.pw.code = dielectric_code # we have a specific code for DielectricWorkChain + builder_iraman.dielectric.scf.pw.code = ( + dielectric_code # we have a specific code for DielectricWorkChain + ) builder_iraman.dielectric.property = dielectric_property builder_iraman.phonon.phonopy.code = phonopy_code @@ -510,12 +518,13 @@ def get_builder_from_protocol( if structure.pbc == (True, False, False): builder_iraman.symmetry.symprec = orm.Float(1e-3) - builder_iraman.phonon.phonopy.parameters = orm.Dict({"symmetry_tolerance": 1e-3}) + builder_iraman.phonon.phonopy.parameters = orm.Dict( + {"symmetry_tolerance": 1e-3} + ) builder.iraman = builder_iraman elif simulation_mode == 3: - builder_phonon = PhononWorkChain.get_builder_from_protocol( pw_code=phonon_code, phonopy_code=phonopy_code, @@ -558,8 +567,10 @@ def get_builder_from_protocol( } ) - builder.phonopy_thermo_dict = Dict(dict=PhononProperty.THERMODYNAMIC.value) - + builder.phonopy_thermo_dict = Dict( + dict=PhononProperty.THERMODYNAMIC.value + ) + if structure.pbc == (True, False, False): builder.phonopy_bands_dict = Dict( dict={ @@ -569,11 +580,13 @@ def get_builder_from_protocol( "band_labels": [GAMMA, "$\\mathrm{X}$"], } ) - #change symprec for 1D materials to 1e-3 + # change symprec for 1D materials to 1e-3 builder.phonopy_pdos_dict["symmetry_tolerance"] = 1e-3 builder.phonopy_thermo_dict["symmetry_tolerance"] = 1e-3 builder.phonon.symmetry.symprec = orm.Float(1e-3) - builder.phonon.phonopy.parameters = orm.Dict({"symmetry_tolerance": 1e-3}) + builder.phonon.phonopy.parameters = orm.Dict( + {"symmetry_tolerance": 1e-3} + ) elif structure.pbc == (True, True, False): symmetry_path = determine_symmetry_path(structure) @@ -606,15 +619,14 @@ def get_builder_from_protocol( ) builder.phonopy_thermo_dict = Dict( - dict= { - 'tprop': True, - 'mesh': 200, # 1000 is too heavy - 'write_mesh': False, - } - ) + dict={ + "tprop": True, + "mesh": 200, # 1000 is too heavy + "write_mesh": False, + } + ) elif simulation_mode == 4: - builder_dielectric = DielectricWorkChain.get_builder_from_protocol( code=dielectric_code, structure=structure, @@ -643,9 +655,9 @@ def get_builder_from_protocol( builder.pop(available_wchains[wchain_idx - 1], None) builder.phonopy_calc.code = phonopy_code builder.phonopy_calc.metadata.options.resources = { - "num_machines": 1, - "num_mpiprocs_per_machine": 1, - } + "num_machines": 1, + "num_mpiprocs_per_machine": 1, + } if structure.pbc == (True, False, False): builder.phonopy_calc.parameters = orm.Dict({"symmetry_tolerance": 1e-3}) builder.structure = structure @@ -709,16 +721,22 @@ def run_phonopy(self): # try in a verdi shell: from plumpy.utils import AttributesFrozendict if self.ctx.key == "phonon": # See how this works in PhononWorkChain - inputs = AttributeDict(self.exposed_inputs(PhonopyCalculation, namespace="phonopy_calc")) + inputs = AttributeDict( + self.exposed_inputs(PhonopyCalculation, namespace="phonopy_calc") + ) inputs.phonopy_data = self.ctx[self.ctx.key].outputs.phonopy_data inputs.parameters = self.inputs[f"phonopy_{calc_type}_dict"] - + elif self.ctx.key == "harmonic": # See how this works in HarmonicWorkChain - inputs = AttributeDict(self.exposed_inputs(PhonopyCalculation, namespace="phonopy_calc")) - inputs.force_constants = list(self.ctx[self.ctx.key].outputs["vibrational_data"].values())[-1] + inputs = AttributeDict( + self.exposed_inputs(PhonopyCalculation, namespace="phonopy_calc") + ) + inputs.force_constants = list( + self.ctx[self.ctx.key].outputs["vibrational_data"].values() + )[-1] inputs.parameters = self.inputs[f"phonopy_{calc_type}_dict"] - + inputs.metadata.call_link_label = key future = self.submit(PhonopyCalculation, **inputs) self.report( @@ -745,19 +763,19 @@ def results(self): if self.ctx["bands"].is_finished_ok: self.out("phonon_bands", self.ctx["bands"].outputs.phonon_bands) else: - self.report(f"the child bands PhonopyCalculation failed") + self.report("the child bands PhonopyCalculation failed") failed = True if self.ctx["pdos"].is_finished_ok: self.out("phonon_pdos", self.ctx["pdos"].outputs.projected_phonon_dos) else: - self.report(f"the child pdos PhonopyCalculation failed") + self.report("the child pdos PhonopyCalculation failed") failed = True if self.ctx["thermo"].is_finished_ok: self.out("phonon_thermo", self.ctx["thermo"].outputs.thermal_properties) else: - self.report(f"the child thermo PhonopyCalculation failed") + self.report("the child thermo PhonopyCalculation failed") failed = True if failed: diff --git a/tests/conftest.py b/tests/conftest.py index 756bdb9..3f6d75a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,7 +11,8 @@ pytest_plugins = ["aiida.manage.tests.pytest_fixtures"] -# TODO: import this from the aiidalab-qe app. For now it is copied. +# TODO: import this from the aiidalab-qe app. For now it is copied. + @pytest.fixture def fixture_localhost(aiida_localhost): @@ -290,6 +291,7 @@ def projwfc_code(aiida_local_code_factory): entry_point="quantumespresso.projwfc", ) + @pytest.fixture def phonopy_code(aiida_local_code_factory): """Return a `Code` configured for the pw.x executable.""" @@ -298,6 +300,7 @@ def phonopy_code(aiida_local_code_factory): label="phonopy", executable="bash", entry_point="phonopy.phonopy" ) + @pytest.fixture() def workchain_settings_generator(): """Return a function that generates a workchain settings dictionary.""" @@ -674,10 +677,14 @@ def _generate_qeapp_workchain( if magnetization_type == "starting_magnetization" else tot_magnetization ) - s2.advanced_settings.magnetization._set_tot_magnetization( - tot_magnetization - ) if electronic_type == "insulator" else s2.advanced_settings.magnetization._set_magnetization_values( - magnetization_values + ( + s2.advanced_settings.magnetization._set_tot_magnetization( + tot_magnetization + ) + if electronic_type == "insulator" + else s2.advanced_settings.magnetization._set_magnetization_values( + magnetization_values + ) ) s2.confirm() diff --git a/tests/test_settings.py b/tests/test_settings.py index a942e5c..f2ca0f1 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -1,21 +1,24 @@ import pytest + @pytest.mark.usefixtures("sssp") def test_settings(): """Test the settings of the vibroscopy app.""" - + from aiidalab_qe.app.configuration import ConfigureQeAppWorkChainStep from ase.build import bulk from aiida.orm import StructureData configure_step = ConfigureQeAppWorkChainStep() # set the input structure - silicon = bulk('Si', 'diamond', a=5.43) + silicon = bulk("Si", "diamond", a=5.43) structure = StructureData(ase=silicon) configure_step.input_structure = structure # select vibrational properties configure_step.workchain_settings.properties["vibronic"].run.value = True - assert configure_step.settings["vibronic"].supercell_widget.layout.display == "block" + assert ( + configure_step.settings["vibronic"].supercell_widget.layout.display == "block" + ) # test get_panel_value for default starting values parameters = configure_step.settings["vibronic"].get_panel_value() print("parameters", parameters) @@ -32,9 +35,13 @@ def test_settings(): assert configure_step.settings["vibronic"].supercell_widget.layout.display == "none" # test that we go back to display it when we select the other modes (0 and 2) configure_step.settings["vibronic"].calc_options.value = 1 - assert configure_step.settings["vibronic"].supercell_widget.layout.display == "block" + assert ( + configure_step.settings["vibronic"].supercell_widget.layout.display == "block" + ) configure_step.settings["vibronic"].calc_options.value = 3 - assert configure_step.settings["vibronic"].supercell_widget.layout.display == "block" + assert ( + configure_step.settings["vibronic"].supercell_widget.layout.display == "block" + ) # test the reset configure_step.settings["vibronic"].reset() parameters = configure_step.settings["vibronic"].get_panel_value() diff --git a/tests/test_submit.py b/tests/test_submit.py index 6851a67..a0466a4 100644 --- a/tests/test_submit.py +++ b/tests/test_submit.py @@ -1,7 +1,11 @@ import pytest -from aiidalab_qe.common.widgets import QEAppComputationalResourcesWidget, PwCodeResourceSetupWidget +from aiidalab_qe.common.widgets import ( + QEAppComputationalResourcesWidget, + PwCodeResourceSetupWidget, +) -#@pytest.mark.skip(reason="Skipping this test for now") + +# @pytest.mark.skip(reason="Skipping this test for now") @pytest.mark.usefixtures("sssp") def test_create_builder_default( data_regression, @@ -13,30 +17,30 @@ def test_create_builder_default( metal, non-magnetic """ - + app = submit_app_generator(properties=["vibronic"]) - + configure_step = app.configure_step - assert configure_step.settings["vibronic"].calc_options.value == 1 - + assert configure_step.settings["vibronic"].calc_options.value == 1 + submit_step = app.submit_step - for step in ["phonopy","dielectric"]: - assert isinstance(submit_step.codes[step],QEAppComputationalResourcesWidget) - assert isinstance(submit_step.codes["phonon"],PwCodeResourceSetupWidget) - + for step in ["phonopy", "dielectric"]: + assert isinstance(submit_step.codes[step], QEAppComputationalResourcesWidget) + assert isinstance(submit_step.codes["phonon"], PwCodeResourceSetupWidget) + submit_step.codes["phonon"].code_selection.refresh() submit_step.codes["dielectric"].code_selection.refresh() submit_step.codes["phonopy"].code_selection.refresh() - + submit_step.codes["pw"].value = pw_code.uuid submit_step.codes["phonon"].value = pw_code.uuid submit_step.codes["dielectric"].value = pw_code.uuid submit_step.codes["phonopy"].value = phonopy_code.uuid - + submit_step.codes["pw"].num_cpus.value = 10 submit_step.codes["phonon"].num_cpus.value = 2 submit_step.codes["dielectric"].num_cpus.value = 3 - + submit_step._create_builder() # since uuid is specific to each run, we remove it from the output ui_parameters = remove_uuid_fields(submit_step.ui_parameters) @@ -45,12 +49,23 @@ def test_create_builder_default( data_regression.check(ui_parameters) # test if create builder successfully builder = submit_step._create_builder() - # In the future, we will check the builder parameters using regresion test - + # In the future, we will check the builder parameters using regresion test + # Check that the resources are indeed different for phonon and dielectric workflows. - assert builder.vibronic.harmonic.phonon.scf.pw.metadata.options.resources['num_mpiprocs_per_machine'] == 2 - assert builder.vibronic.harmonic.dielectric.scf.pw.metadata.options.resources['num_mpiprocs_per_machine'] == 3 - + assert ( + builder.vibronic.harmonic.phonon.scf.pw.metadata.options.resources[ + "num_mpiprocs_per_machine" + ] + == 2 + ) + assert ( + builder.vibronic.harmonic.dielectric.scf.pw.metadata.options.resources[ + "num_mpiprocs_per_machine" + ] + == 3 + ) + + def remove_uuid_fields(data): """ Recursively remove fields that contain UUID values from a dictionary. @@ -80,4 +95,4 @@ def remove_uuid_fields(data): return [remove_uuid_fields(item) for item in data] else: # Return the value unchanged if it's not a dictionary or list - return data \ No newline at end of file + return data