Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Spin-Orbit coupling #708

Merged
merged 15 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",
superstar54 marked this conversation as resolved.
Show resolved Hide resolved
}


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
Loading