Skip to content

Commit

Permalink
Feature/handling molecules (aiidalab#834)
Browse files Browse the repository at this point in the history
- Include the option molecule for StructureData editor
- Band structure calculation should be disabled
- Cell Optimizatio not available if structure is molecule
- kpoint_distance should be disable and force [1,1,1]
- Realoading an old workflow should restore normal behavior of app
  • Loading branch information
AndresOrtegaGuerrero authored Oct 5, 2024
1 parent 9503aed commit 860f59b
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 11 deletions.
5 changes: 5 additions & 0 deletions src/aiidalab_qe/app/configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ def __init__(self, **kwargs):
(self.advanced_settings, "input_structure"),
)
#
ipw.dlink(
(self, "input_structure"),
(self.workchain_settings, "input_structure"),
)
#
self.built_in_settings = [
self.workchain_settings,
self.advanced_settings,
Expand Down
45 changes: 39 additions & 6 deletions src/aiidalab_qe/app/configuration/advanced.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,7 @@ def __init__(self, default_protocol=None, **kwargs):
style={"description_width": "initial"},
)
self.mesh_grid = ipw.HTML()
ipw.dlink(
(self.override, "value"),
(self.kpoints_distance, "disabled"),
lambda override: not override,
)
self.create_kpoints_distance_link()
self.kpoints_distance.observe(self._callback_value_set, "value")

# Hubbard setting widget
Expand Down Expand Up @@ -285,6 +281,20 @@ def __init__(self, default_protocol=None, **kwargs):
# Default settings to trigger the callback
self.reset()

def create_kpoints_distance_link(self):
"""Create the dlink for override and kpoints_distance."""
self.kpoints_distance_link = ipw.dlink(
(self.override, "value"),
(self.kpoints_distance, "disabled"),
lambda override: not override,
)

def remove_kpoints_distance_link(self):
"""Remove the kpoints_distance_link."""
if hasattr(self, "kpoints_distance_link"):
self.kpoints_distance_link.unlink()
del self.kpoints_distance_link

def _create_electron_maxstep_widgets(self):
self.electron_maxstep = ipw.BoundedIntText(
min=20,
Expand Down Expand Up @@ -332,10 +342,22 @@ def _update_input_structure(self, change):
self.hubbard_widget.update_widgets(change["new"])
if isinstance(self.input_structure, HubbardStructureData):
self.override.value = True
if self.input_structure.pbc == (False, False, False):
self.kpoints_distance.value = 100.0
self.kpoints_distance.disabled = True
if hasattr(self, "kpoints_distance_link"):
self.remove_kpoints_distance_link()
else:
# self.kpoints_distance.disabled = False
if not hasattr(self, "kpoints_distance_link"):
self.create_kpoints_distance_link()
else:
self.magnetization.input_structure = None
self.pseudo_setter.structure = None
self.hubbard_widget.update_widgets(None)
self.kpoints_distance.disabled = False
if not hasattr(self, "kpoints_distance_link"):
self.create_kpoints_distance_link()

@tl.observe("electronic_type")
def _electronic_type_changed(self, change):
Expand All @@ -356,7 +378,14 @@ def _update_settings_from_protocol(self, protocol):

parameters = PwBaseWorkChain.get_protocol_inputs(protocol)

self.kpoints_distance.value = parameters["kpoints_distance"]
if self.input_structure:
if self.input_structure.pbc == (False, False, False):
self.kpoints_distance.value = 100.0
self.kpoints_distance.disabled = True
else:
self.kpoints_distance.value = parameters["kpoints_distance"]
else:
self.kpoints_distance.value = parameters["kpoints_distance"]

num_atoms = len(self.input_structure.sites) if self.input_structure else 1

Expand Down Expand Up @@ -630,6 +659,10 @@ def reset(self):
self.pseudo_setter._reset()
else:
self.pseudo_setter._reset()
if self.input_structure.pbc == (False, False, False):
self.kpoints_distance.value = 100.0
self.kpoints_distance.disabled = True

# reset the magnetization
self.magnetization.reset()
# reset the hubbard widget
Expand Down
36 changes: 36 additions & 0 deletions src/aiidalab_qe/app/configuration/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"""

import ipywidgets as ipw
import traitlets as tl

from aiida import orm
from aiida_quantumespresso.common.types import RelaxType
from aiidalab_qe.app.parameters import DEFAULT_PARAMETERS
from aiidalab_qe.app.utils import get_entry_items
Expand Down Expand Up @@ -49,6 +51,8 @@ class WorkChainSettings(Panel):
with less precision and the "precise" protocol to aim at best accuracy (at the price of longer/costlier calculations).</div>"""
)

input_structure = tl.Instance(orm.StructureData, allow_none=True)

def __init__(self, **kwargs):
# RelaxType: degrees of freedom in geometry optimization
self.relax_type = ipw.ToggleButtons(
Expand Down Expand Up @@ -141,6 +145,37 @@ def update_reminder_info(change, name=name):
**kwargs,
)

@tl.observe("input_structure")
def _on_input_structure_change(self, change):
"""Update the relax type options based on the input structure."""
structure = change["new"]
if structure is None or structure.pbc != (False, False, False):
self.relax_type.options = [
("Structure as is", "none"),
("Atomic positions", "positions"),
("Full geometry", "positions_cell"),
]
# Ensure the value is in the options
if self.relax_type.value not in [
option[1] for option in self.relax_type.options
]:
self.relax_type.value = "positions_cell"

self.properties["bands"].run.disabled = False
elif structure.pbc == (False, False, False):
self.relax_type.options = [
("Structure as is", "none"),
("Atomic positions", "positions"),
]
# Ensure the value is in the options
if self.relax_type.value not in [
option[1] for option in self.relax_type.options
]:
self.relax_type.value = "positions"

self.properties["bands"].run.value = False
self.properties["bands"].run.disabled = True

def get_panel_value(self):
# Work chain settings
relax_type = self.relax_type.value
Expand Down Expand Up @@ -192,6 +227,7 @@ def set_panel_value(self, parameters):

def reset(self):
"""Reset the panel to the default value."""
self.input_structure = None
for key in ["relax_type", "spin_type", "electronic_type"]:
getattr(self, key).value = DEFAULT_PARAMETERS["workchain"][key]
self.workchain_protocol.value = DEFAULT_PARAMETERS["workchain"]["protocol"]
Expand Down
1 change: 1 addition & 0 deletions src/aiidalab_qe/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def _observe_process_selection(self, change):
self.structure_step.structure = process.inputs.structure
self.structure_step.confirm()
self.submit_step.process = process

# set ui_parameters
# print out error message if yaml format ui_parameters is not reachable
ui_parameters = process.base.extras.get("ui_parameters", {})
Expand Down
1 change: 1 addition & 0 deletions src/aiidalab_qe/app/result/summary_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
(True, True, True): "xyz",
(True, True, False): "xy",
(True, False, False): "x",
(False, False, False): "molecule",
}

VDW_CORRECTION_VERSION = {
Expand Down
7 changes: 2 additions & 5 deletions src/aiidalab_qe/common/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,11 +465,7 @@ def __init__(self, title=""):
layout={"width": "initial"},
)
self.periodicity = ipw.RadioButtons(
options=[
"xyz",
"xy",
"x",
],
options=["xyz", "xy", "x", "molecule"],
value="xyz",
description="Periodicty: ",
layout={"width": "initial"},
Expand Down Expand Up @@ -613,6 +609,7 @@ def _select_periodicity(self, _=None):
"xyz": (True, True, True),
"xy": (True, True, False),
"x": (True, False, False),
"molecule": (False, False, False),
}
new_structure = deepcopy(self.structure)
new_structure.set_pbc(periodicity_options[self.periodicity.value])
Expand Down
8 changes: 8 additions & 0 deletions src/aiidalab_qe/plugins/pdos/setting.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ def __init__(self, **kwargs):
self.description = ipw.HTML(
"""<div style="line-height: 140%; padding-top: 0px; padding-bottom: 5px">
By default, the tetrahedron method is used for PDOS calculation. If required you can apply Gaussian broadening with a custom degauss value.
<br>
For molecules and systems with localized orbitals, it is recommended to use a custom degauss value.
</div>"""
)
# nscf kpoints setting widget
Expand Down Expand Up @@ -87,6 +89,12 @@ def _procotol_changed(self, change):
@tl.observe("input_structure")
def _update_structure(self, _=None):
self._display_mesh()
# For molecules this is compulsory
if self.input_structure and self.input_structure.pbc == (False, False, False):
self.nscf_kpoints_distance.value = 100
self.nscf_kpoints_distance.disabled = True
self.use_pdos_degauss.value = True
self.use_pdos_degauss.disabled = True

def _display_mesh(self, _=None):
if self.input_structure is None:
Expand Down
37 changes: 37 additions & 0 deletions tests/configuration/test_advanced.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,43 @@ def test_advanced_kpoints_settings():
assert w.value.get("kpoints_distance") == 0.5


def test_advanced_molecule_settings(generate_structure_data):
"""Test kpoint setting of advanced setting widget."""
from aiidalab_qe.app.configuration.advanced import AdvancedSettings

w = AdvancedSettings()

# Check the disable of is bind to override switch
assert w.kpoints_distance.disabled is True

w.override.value = True
assert w.kpoints_distance.disabled is False

# create molecule
structure = generate_structure_data(name="H2O", pbc=(False, False, False))
# Assign the molecule
w.input_structure = structure

# Check override can not modify the kpoints_distance
assert w.kpoints_distance.disabled is True
w.override.value = True
assert w.kpoints_distance.disabled is True

# Confirm the value of kpoints_distance is fixed
assert w.value.get("kpoints_distance") == 100.0

w.protocol = "fast"
assert w.value.get("kpoints_distance") == 100.0

# # Check reset
w.input_structure = None
w.reset()

# # the protocol will not be reset
assert w.protocol == "fast"
assert w.value.get("kpoints_distance") == 0.5


def test_advanced_tot_charge_settings():
"""Test TotCharge widget."""
from aiidalab_qe.app.configuration.advanced import AdvancedSettings
Expand Down
7 changes: 7 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ def _generate_structure_data(name="silicon", pbc=(True, True, True)):
structure.append_atom(position=(1.6, 0.92, 8.47), symbols="S")
structure.append_atom(position=(1.6, 0.92, 11.6), symbols="S")

elif name == "H2O":
cell = [[10.0, 0.0, 0.0], [0.0, 10.0, 0.0], [0.0, 0.0, 10.0]]
structure = orm.StructureData(cell=cell)
structure.append_atom(position=(0.0, 0.0, 0.0), symbols="H")
structure.append_atom(position=(0.0, 0.0, 1.0), symbols="O")
structure.append_atom(position=(0.0, 1.0, 0.0), symbols="H")

structure.pbc = pbc

return structure
Expand Down

0 comments on commit 860f59b

Please sign in to comment.