Skip to content

Commit

Permalink
Feature Spin-Orbit coupling (#708)
Browse files Browse the repository at this point in the history
* Feature Spin-Orbit coupling
  • Loading branch information
AndresOrtegaGuerrero committed May 23, 2024
1 parent e044f93 commit f239ca8
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 32 deletions.
33 changes: 33 additions & 0 deletions src/aiidalab_qe/app/configuration/advanced.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,21 @@ def __init__(self, default_protocol=None, **kwargs):
(self.etot_conv_thr, "disabled"),
lambda override: not override,
)
# Spin-Orbit calculation
self.spin_orbit = ipw.ToggleButtons(
options=[
("Off", "wo_soc"),
("On", "soc"),
],
description="Spin-Orbit:",
value="wo_soc",
style={"description_width": "initial"},
)
ipw.dlink(
(self.override, "value"),
(self.spin_orbit, "disabled"),
lambda override: not override,
)

self.pseudo_family_selector = PseudoFamilySelector()
self.pseudo_setter = PseudoSetter()
Expand All @@ -217,6 +232,12 @@ def __init__(self, default_protocol=None, **kwargs):
(self.pseudo_setter, "pseudo_family"),
)
self.kpoints_distance.observe(self._display_mesh, "value")

# Link with PseudoWidget
ipw.dlink(
(self.spin_orbit, "value"),
(self.pseudo_family_selector, "spin_orbit"),
)
self.children = [
self.title,
ipw.HBox(
Expand Down Expand Up @@ -245,6 +266,8 @@ def __init__(self, default_protocol=None, **kwargs):
self.kpoints_description,
ipw.HBox([self.kpoints_distance, self.mesh_grid]),
self.hubbard_widget,
# Spin-Orbit calculation
self.spin_orbit,
self.pseudo_family_selector,
self.pseudo_setter,
]
Expand Down Expand Up @@ -436,6 +459,12 @@ def get_panel_value(self):
self.etot_conv_thr.value
)

# Spin-Orbit calculation
if self.spin_orbit.value == "soc":
parameters["pw"]["parameters"]["SYSTEM"]["lspinorb"] = True
parameters["pw"]["parameters"]["SYSTEM"]["noncolin"] = True
parameters["pw"]["parameters"]["SYSTEM"]["nspin"] = 4

return parameters

def set_insulator_magnetization(self, parameters):
Expand Down Expand Up @@ -483,6 +512,10 @@ def set_panel_value(self, parameters):
self.total_charge.value = parameters["pw"]["parameters"]["SYSTEM"].get(
"tot_charge", 0
)
if "lspinorb" in system:
self.spin_orbit.value = "soc"
else:
self.spin_orbit.value = "wo_soc"
# van der waals correction
self.van_der_waals.value = self.dftd3_version.get(
system.get("dftd3_version"),
Expand Down
68 changes: 53 additions & 15 deletions src/aiidalab_qe/app/configuration/pseudos.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ class PseudoFamilySelector(ipw.VBox):
"""<div style="padding-top: 0px; padding-bottom: 10px">
<h4>Accuracy and precision</h4></div>"""
)
PSEUDO_HELP_SOC = """<div style="line-height: 140%; padding-top: 10px; padding-bottom: 0px; opacity:0.5;">
Spin-orbit coupling (SOC) calculations are supported exclusively with PseudoDojo pseudopotentials.
PseudoDojo offers these pseudopotentials in two versions: standard and stringent.
Here, we utilize the FR (fully relativistic) type from PseudoDojo.
Please ensure you choose appropriate cutoff values for your calculations.
</div>"""

PSEUDO_HELP_WO_SOC = """<div style="line-height: 140%; padding-top: 10px; padding-bottom: 0px; opacity:0.5;">
If you are unsure, select 'SSSP efficiency', which for
most calculations will produce sufficiently accurate results at
comparatively small computational costs. If your calculations require a
higher accuracy, select 'SSSP accuracy' or 'PseudoDojo stringent', which will be computationally
more expensive. SSSP is the standard solid-state pseudopotentials.
The PseudoDojo used here has the SR relativistic type.</div>"""

description = ipw.HTML(
"""<div style="line-height: 140%; padding-top: 0px; padding-bottom: 10px">
Expand All @@ -45,15 +59,7 @@ class PseudoFamilySelector(ipw.VBox):
<b><a href="https://www.materialscloud.org/discover/sssp/table/efficiency"
target="_blank">Pseudopotential family</b></div>"""
)
pseudo_family_help = ipw.HTML(
"""<div style="line-height: 140%; padding-top: 10px; padding-bottom: 0px; opacity:0.5;">
If you are unsure, select 'SSSP efficiency', which for
most calculations will produce sufficiently accurate results at
comparatively small computational costs. If your calculations require a
higher accuracy, select 'SSSP accuracy' or 'PseudoDojo stringent', which will be computationally
more expensive. SSSP is the standard solid-state pseudopotentials.
The PseudoDojo used here has the SR relativistic type.</div>"""
)
pseudo_family_help = ipw.HTML(PSEUDO_HELP_WO_SOC)

dft_functional_prompt = ipw.HTML(
"""
Expand All @@ -68,6 +74,7 @@ class PseudoFamilySelector(ipw.VBox):
)
protocol = tl.Unicode(allow_none=True)
disabled = tl.Bool()
spin_orbit = tl.Unicode()

# output pseudo family widget which is the string of the pseudo family (of the AiiDA group).
value = tl.Unicode(allow_none=True)
Expand Down Expand Up @@ -150,9 +157,14 @@ def set_value(self, _=None):
functional = self.dft_functional.value
# XXX (jusong.yu): a validator is needed to check the family string is consistent with the list of pseudo families defined in the setup_pseudos.py
if library == "PseudoDojo":
pseudo_family_string = (
f"PseudoDojo/{PSEUDODOJO_VERSION}/{functional}/SR/{accuracy}/upf"
)
if self.spin_orbit == "soc":
pseudo_family_string = (
f"PseudoDojo/{PSEUDODOJO_VERSION}/{functional}/FR/{accuracy}/upf"
)
else:
pseudo_family_string = (
f"PseudoDojo/{PSEUDODOJO_VERSION}/{functional}/SR/{accuracy}/upf"
)
elif library == "SSSP":
pseudo_family_string = f"SSSP/{SSSP_VERSION}/{functional}/{accuracy}"
else:
Expand Down Expand Up @@ -190,6 +202,24 @@ def reset(self):
# stay the same while xc selection is changed.
self._update_settings_from_protocol(self.protocol)

@tl.observe("spin_orbit")
def _update_library_selection(self, _):
"""Update the library selection according to the spin orbit value."""
if self.spin_orbit == "soc":
self.library_selection.options = [
"PseudoDojo standard",
"PseudoDojo stringent",
]
self.pseudo_family_help.value = self.PSEUDO_HELP_SOC
else:
self.library_selection.options = [
"SSSP efficiency",
"SSSP precision",
"PseudoDojo standard",
"PseudoDojo stringent",
]
self.pseudo_family_help.value = self.PSEUDO_HELP_WO_SOC

@tl.observe("protocol")
def _protocol_changed(self, _):
"""Input protocol changed, update the value of widgets."""
Expand All @@ -198,9 +228,17 @@ def _protocol_changed(self, _):
def _update_settings_from_protocol(self, protocol):
"""Update the widget values from the given protocol, and trigger the callback."""
# FIXME: this rely on the aiida-quantumespresso, which is not ideal
pseudo_family_string = PwBaseWorkChain.get_protocol_inputs(protocol)[
"pseudo_family"
]

if self.spin_orbit == "soc":
if protocol in ["fast", "moderate"]:
pseudo_family_string = "PseudoDojo/0.4/PBE/FR/standard/upf"
else:
pseudo_family_string = "PseudoDojo/0.4/PBE/FR/stringent/upf"
else:
pseudo_family_string = PwBaseWorkChain.get_protocol_inputs(protocol)[
"pseudo_family"
]

pseudo_family = PseudoFamily.from_string(pseudo_family_string)

self.load_from_pseudo_family(pseudo_family)
Expand Down
3 changes: 3 additions & 0 deletions src/aiidalab_qe/app/result/summary_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ def generate_report_parameters(qeapp_wc):
qeapp_wc.inputs.structure.pbc, "xyz"
)

# Spin-Oribit coupling
report["spin_orbit"] = pw_parameters["SYSTEM"].get("lspinorb", False)

# DFT+U
hubbard_dict = ui_parameters["advanced"].pop("hubbard_parameters", None)
if hubbard_dict:
Expand Down
7 changes: 7 additions & 0 deletions src/aiidalab_qe/app/static/workflow_summary.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@
<td>{{ hubbard_u }}</td>
</tr>
{% endif %}
{% if spin_orbit %}
<tr>
<td>Spin-Orbit</td>
<td>{{ spin_orbit }}</td>
</tr>
{% endif %}

</table>
</div>
</div>
Expand Down
4 changes: 4 additions & 0 deletions src/aiidalab_qe/common/setup_pseudos.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
f"PseudoDojo/{PSEUDODOJO_VERSION}/PBEsol/SR/standard/upf",
f"PseudoDojo/{PSEUDODOJO_VERSION}/PBE/SR/stringent/upf",
f"PseudoDojo/{PSEUDODOJO_VERSION}/PBEsol/SR/stringent/upf",
f"PseudoDojo/{PSEUDODOJO_VERSION}/PBE/FR/standard/upf",
f"PseudoDojo/{PSEUDODOJO_VERSION}/PBEsol/FR/standard/upf",
f"PseudoDojo/{PSEUDODOJO_VERSION}/PBE/FR/stringent/upf",
f"PseudoDojo/{PSEUDODOJO_VERSION}/PBEsol/FR/stringent/upf",
}


Expand Down
12 changes: 2 additions & 10 deletions src/aiidalab_qe/workflows/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
# because we want to decouple the workflows from the app, so I copied it here
# instead of importing it.
# load entry points


def get_entries(entry_point_name="aiidalab_qe.property"):
from importlib.metadata import entry_points

Expand Down Expand Up @@ -190,15 +192,6 @@ def get_builder_from_protocol(
relax_builder.pop("base_final_scf", None) # never run a final scf
builder.relax = relax_builder

# remove starting magnetization if tot_magnetization is set
if (
relax_builder["base"]["pw"]["parameters"]["SYSTEM"].get("tot_magnetization")
is not None
):
builder.relax["base"]["pw"]["parameters"]["SYSTEM"].pop(
"starting_magnetization", None
)

if properties is None:
properties = []
builder.properties = orm.List(list=properties)
Expand All @@ -211,7 +204,6 @@ def get_builder_from_protocol(
plugin_builder = entry_point["get_builder"](
codes, builder.structure, copy.deepcopy(parameters), **kwargs
)
# check if the plugin has a clean_workdir input
plugin_workchain = entry_point["workchain"]
if plugin_workchain.spec().has_input("clean_workdir"):
plugin_builder.clean_workdir = clean_workdir
Expand Down
18 changes: 12 additions & 6 deletions tests/test_pseudo.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def test_setup_pseudos_cmd(tmp_path):
def test_pseudos_installation():
"""Test install_pseudos"""
# Test by compare the pseudos_to_install before and after the installation
assert len(pseudos_to_install()) == 8
assert len(pseudos_to_install()) == 12
EXPECTED_PSEUDOS = {
f"PseudoDojo/{PSEUDODOJO_VERSION}/PBE/SR/standard/upf",
f"SSSP/{SSSP_VERSION}/PBE/efficiency",
Expand All @@ -114,13 +114,13 @@ def test_pseudos_installation():
[_ for _ in _install_pseudos(EXPECTED_PSEUDOS)]

# Two pseudos are installed
assert len(pseudos_to_install()) == 6
assert len(pseudos_to_install()) == 10


@pytest.mark.usefixtures("aiida_profile_clean")
def test_download_and_install_pseudo_from_file(tmp_path):
"""Test download and install pseudo from file."""
assert len(pseudos_to_install()) == 8
assert len(pseudos_to_install()) == 12
EXPECTED_PSEUDOS = {
f"PseudoDojo/{PSEUDODOJO_VERSION}/PBE/SR/standard/upf",
f"SSSP/{SSSP_VERSION}/PBE/efficiency",
Expand All @@ -129,14 +129,14 @@ def test_download_and_install_pseudo_from_file(tmp_path):
# Download the pseudos to the tmp_path but not install
[_ for _ in _install_pseudos(EXPECTED_PSEUDOS, download_only=True, cwd=tmp_path)]

assert len(pseudos_to_install()) == 8
assert len(pseudos_to_install()) == 12
assert len(list(tmp_path.iterdir())) == 2

# Install the pseudos from the tmp_path
[_ for _ in _install_pseudos(EXPECTED_PSEUDOS, cwd=tmp_path)]

# Two pseudos are installed
assert len(pseudos_to_install()) == 6
assert len(pseudos_to_install()) == 10


def test_pseudos_family_selector_widget():
Expand All @@ -147,7 +147,7 @@ def test_pseudos_family_selector_widget():
assert w.override.value is False

w.override.value = True

w.spin_orbit = "wo_soc"
# test the default value
assert w.value == f"SSSP/{SSSP_VERSION}/PBEsol/efficiency"

Expand All @@ -163,6 +163,12 @@ def test_pseudos_family_selector_widget():
w.library_selection.value = "PseudoDojo stringent"
assert w.value == f"PseudoDojo/{PSEUDODOJO_VERSION}/PBE/SR/stringent/upf"

# Test spin-orbit change will update

w.spin_orbit = "soc"
w.protocol = "moderate"
assert w.value == f"PseudoDojo/{PSEUDODOJO_VERSION}/PBE/FR/standard/upf"


@pytest.mark.usefixtures("sssp")
def test_pseudos_setter_widget(generate_structure_data, generate_upf_data):
Expand Down
1 change: 1 addition & 0 deletions tests/test_result/test_summary_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ relax_method: positions_cell
relaxed: positions_cell
scf_kpoints_distance: 0.5
smearing: cold
spin_orbit: false
tot_charge: 0.0
tot_magnetization: false
vdw_corr: none
2 changes: 1 addition & 1 deletion tests_integration/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ def test_pseudos_families_are_installed(aiidalab_exec, nb_user):
assert "PseudoDojo" in output

# Two lines of header, 8 pseudos
assert len(output.splitlines()) == 10
assert len(output.splitlines()) == 14

0 comments on commit f239ca8

Please sign in to comment.