Skip to content

Commit

Permalink
Adding the gui folder. It contains:
Browse files Browse the repository at this point in the history
- app folder with the graphical interface (i.e. widgets);
- koopmansworkchain
  • Loading branch information
mikibonacci committed May 14, 2024
1 parent e5a9875 commit 7b6feca
Show file tree
Hide file tree
Showing 6 changed files with 321 additions and 2 deletions.
10 changes: 8 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ build-backend = "flit_core.buildapi"
# See https://www.python.org/dev/peps/pep-0621/
name = "aiida-koopmans"
dynamic = ["version"] # read from aiida_koopmans/__init__.py
description = "AiiDA plugin that wraps the `kcw' executable."
authors = [{name = "Miki Bonacci"}]
description = "AiiDA plugin that wraps the `kcw' executable and contains other utilites to run Koopmans@AiiDA."
authors = [{name = "Miki Bonacci, Julian Geiger"}]
readme = "README.md"
license = {file = "LICENSE"}
classifiers = [
Expand Down Expand Up @@ -56,6 +56,12 @@ docs = [
[project.entry-points."aiida.calculations"]
"koopmans" = "aiida_koopmans.calculations.kcw:KcwCalculation"

[project.entry-points."aiida.workflows"]
"koopmans_workflow" = "aiida_koopmans.gui.koopmansworkchain.KoopmansWorkChain"

[project.entry-points.'aiidalab_qe.properties']
"koopmans" = "aiida_koopmans.gui.app:property"

[project.entry-points."aiida.parsers"]
"koopmans" = "aiida_koopmans.parsers.kcw:KcwParser"

Expand Down
20 changes: 20 additions & 0 deletions src/aiida_koopmans/gui/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# from aiidalab_qe.bands.result import Result
from aiidalab_qe.common.panel import OutlinePanel

from .result import Result
from .setting import Setting
from .workchain import workchain_and_builder


class Outline(OutlinePanel):
title = "Koopmans electronic band structure"
help = """Koopmans DFPT workflow"""

# for now, no codes are provided.

property = {
"outline": Outline,
"setting": Setting,
"result": Result,
"workchain": workchain_and_builder,
}
103 changes: 103 additions & 0 deletions src/aiida_koopmans/gui/app/result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""Koopmans results view widgets"""
from aiidalab_qe.common.panel import ResultPanel

from aiidalab_qe.common.bandpdoswidget import cmap, get_bands_labeling,BandPdosPlotly

import numpy as np
import json

def replace_symbols_with_uppercase(data):
symbols_mapping = {
"$\Gamma$": "\u0393",
"$\\Gamma$": "\u0393",
"$\\Delta$": "\u0394",
"$\\Lambda$": "\u039B",
"$\\Sigma$": "\u03A3",
"$\\Epsilon$": "\u0395",
}

for sublist in data:
for i, element in enumerate(sublist):
if element in symbols_mapping:
sublist[i] = symbols_mapping[element]

def get_bands_from_koopmans(koopmans_output):
full_data = {
"dft": None,
"koopmans": None,
}
parameters = {}

dft_bands = koopmans_output.interpolated_dft
data = json.loads(
dft_bands._exportcontent("json", comments=False)[0]
)
# The fermi energy from band calculation is not robust.
data["fermi_energy"] = 0
data["pathlabels"] = get_bands_labeling(data)
replace_symbols_with_uppercase(data["pathlabels"])

bands = dft_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,
}
data["band_type_idx"] = bands["band_type_idx"]
data["x"] = bands["x"]
data["y"] = bands["y"]
full_data["dft"] = [data, parameters]

koop_bands = koopmans_output.interpolated_koopmans
data = json.loads(
koop_bands._exportcontent("json", comments=False)[0]
)
# The fermi energy from band calculation is not robust.
data["fermi_energy"] = 0
data["pathlabels"] = get_bands_labeling(data)
replace_symbols_with_uppercase(data["pathlabels"])

bands = koop_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,
}
data["band_type_idx"] = bands["band_type_idx"]
data["x"] = bands["x"]
data["y"] = bands["y"]
full_data["koopmans"] = [data, parameters]

return full_data

class Result(ResultPanel):
"""Result panel for the bands calculation."""

title = "Koopmans bands"
workchain_labels = ["koopmans"]

def __init__(self, node=None, **kwargs):
super().__init__(node=node, **kwargs)

def _update_view(self):
# Check if the workchain has the outputs
try:
koopmans_output = self.node.outputs.koopmans
except AttributeError:
koopmans_output = None

bands = get_bands_from_koopmans(self.node.outputs.koopmans)

fig = BandPdosPlotly(bands_data=bands["koopmans"][0]).bandspdosfigure
fig_drop = BandPdosPlotly(bands_data=bands["dft"][0]).bandspdosfigure
fig.add_scatter(y=fig_drop.data[0].y,x=fig_drop.data[0].x, name='DFT')
del fig_drop

trace_koopmans = fig.data[0]
trace_koopmans.name = 'Koopmans'
trace_koopmans.showlegend = True

fig.layout.title.text = 'Interpolated Koopmans band structure'
fig.layout.autosize = True

self.children = [
fig,
]
88 changes: 88 additions & 0 deletions src/aiida_koopmans/gui/app/setting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
"""Panel for Koopmans bands."""

import ipywidgets as ipw
import json
import pathlib
import tempfile

from aiidalab_qe.common.panel import Panel

class Setting(Panel):
title = "Koopmans bands"
identifier = "koopmans"

def __init__(self, **kwargs):
self.settings_help = ipw.HTML(
"""<div style="line-height: 140%; padding-top: 0px; padding-bottom: 5px">
The Koopmans band structure is computed using the AiiDA-enabled version of the <b>
<a href="https://koopmans-functionals.org/en/latest/"
target="_blank">Koopmans package</b></a> (E. Linscott et al.,
<a href="https://pubs.acs.org/doi/10.1021/acs.jctc.3c00652"
target="_blank">J. Chem. Theory Comput. 2023 <b>19</b>, 20, 2023</a>) and the <a href="https://github.com/mikibonacci/aiida-koopmans"
target="_blank">aiida-koopmans</b></a> plugin, co-developed by Miki Bonacci, Julian Geiger and Edward Linscott (Paul Scherrer Institut, Switzerland).
<br>
<br>
For now, we allow one way to provide Koopmans settings, i.e. through the upload button below. You should pass
the same file that is needed to run a standard Koopmans@AiiDA simulation, i.e. the codes should be set there,
and not in step 3 of the app (this is just a temporary limitation).
<br>
<br>
Only DFPT workflow is available.
</div>""",
layout=ipw.Layout(width="400"),
)

# Upload buttons
self.upload_widget = ipw.FileUpload(
description="Upload Koopmans json file",
multiple=False,
layout={"width": "initial"},
)
self.upload_widget.observe(self._on_upload_json, "value")

self.reset_uploads = ipw.Button(
description="Discard uploaded file",
icon="pencil",
button_style="warning",
disabled=False,
layout=ipw.Layout(width="auto"),
)
self.reset_uploads.observe(self._on_reset_uploads_button_clicked, "value")

self.children = [
self.settings_help,
ipw.HBox(children=[self.upload_widget,self.reset_uploads]),
]
super().__init__(**kwargs)


def _on_reset_uploads_button_clicked(self, change):
self.upload_widget.value.clear()
self.upload_widget._counter = 0

def _on_upload_json(self, change):
# TO BE IMPLEMENTED
if change["new"] != change["old"]:
uploaded_filename = next(iter(self.upload_widget.value))
content = self.upload_widget.value[uploaded_filename]['content']
self.input_dictionary = json.loads(content.decode('utf-8')) # Decode content and parse JSON
print("Uploaded JSON content:")
print(self.input_dictionary)



def get_panel_value(self):
"""Return a dictionary with the input parameters for the plugin."""
return {
"input_dictionary": self.input_dictionary,
}

def set_panel_value(self, input_dict):
"""Load a dictionary with the input parameters for the plugin."""
self.input_dictionary = input_dict.get("input_dictionary", {})

def reset(self):
"""Reset the panel to its default values."""
self.input_dictionary = {}
16 changes: 16 additions & 0 deletions src/aiida_koopmans/gui/app/workchain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from aiida_koopmans.gui.koopmansworkchain import KoopmansWorkChain
from aiida import orm

def get_builder(codes, structure, parameters, **kwargs):
"""Get a builder for the PwBandsWorkChain."""
kcw_wf = KoopmansWorkChain.get_builder()
kcw_wf.input_dictionary = orm.Dict(parameters["koopmans"].pop("input_dictionary",{}))
return kcw_wf


workchain_and_builder = {
"workchain": KoopmansWorkChain,
#"exclude": ("structure", "relax"),
"get_builder": get_builder,
#"update_inputs": update_inputs,
}
86 changes: 86 additions & 0 deletions src/aiida_koopmans/gui/koopmansworkchain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from aiida import orm
from aiida.engine.processes.workchains.workchain import WorkChain
from aiida.engine import calcfunction
import numpy as np


from koopmans.workflows import KoopmansDFPTWorkflow, SinglepointWorkflow

class KoopmansWorkChain(WorkChain):
"""WorkChain to run the koopmans package. Very simple, it is only needed for the GUI.
Args:
inputs (orm.Dict): inputs as obtained from loading the json file.
"""
@classmethod
def define(cls, spec):
"""Define the process specification."""
# yapf: disable
super().define(spec)
spec.input('input_dictionary', valid_type=orm.Dict, required=False,)
spec.input('structure', valid_type=orm.StructureData, required=False,
help="needed if we run in the GUI and we relax the structure before.")

spec.outline(
cls.setup,
cls.run_process,
cls.results,
)

spec.output("alphas", valid_type=orm.List, required= False)
spec.output("interpolated_dft", valid_type=orm.BandsData, required=False)
spec.output("interpolated_koopmans", valid_type=orm.BandsData, required=False)

@classmethod
def from_json(cls,):
pass

def setup(self):
wf = SinglepointWorkflow._fromjsondct(self.inputs.input_dictionary.get_dict())
self.ctx.workflow = KoopmansDFPTWorkflow.fromparent(wf)
return

def run_process(self):
# for now in the DFPT AiiDA wfl we just run_and_get_node, so no need to have the context.
self.ctx.workflow._run()
return

def results(self):

parent = orm.load_node(self.ctx.workflow.dft_wchains_pk[0])
bands_dft = merge_bands(parent.outputs.remote_folder, method="dft")
bands_koopmans = merge_bands(parent.outputs.remote_folder, method="koopmans")

self.out("interpolated_dft",bands_dft)
self.out("interpolated_koopmans",bands_koopmans)

return


@calcfunction
def merge_bands(remote_pw, method="dft"):
# I want to have both dft and koopmans method and call this calcfunction once,
# but for now it is fine to call it twice.
# remote_pw is needed to access self (KoopmansWorkChain) in the calcfunction
workchain = remote_pw.creator.caller.caller

bands = {"dft":[],"koopmans":[]}
method_loop = "dft"
for job in workchain.called:
if job.process_type == "aiida.calculations:koopmans":
method_loop = "koopmans"
if job.process_type == "aiida.workflows:wannier90_workflows.bands":
bands[method_loop].append(job.outputs.band_structure)

for method_merge in [method.value]:
new_bands_array = bands[method_merge][0].get_bands()
for i in range(1,len(bands[method_merge])):
new_bands_array = np.concatenate((new_bands_array,bands[method_merge][i].get_bands()),axis=1)

# Create a band structure object
merged_bands = bands[method_merge][0].clone()
merged_bands.set_bands(new_bands_array)
# merged_bands.store()
#bands[method].append(merged_bands)

return merged_bands

0 comments on commit 7b6feca

Please sign in to comment.