Skip to content

Commit

Permalink
Implement mechanism to override defaults using a custom config file (#…
Browse files Browse the repository at this point in the history
…1007)

This PR implement a merging mechanism for custom app defaults configurations. Users can now drop a custom config.yml in the /home/jovyan/.aiidalab/quantumespresso/ directory. The file follows the structure of the qeapp.yml default configurations file, though it does not strictly require the inclusion of all fields. The custom config.yml file is processed and merged recursively into the object created from processing qeapp.yml.
  • Loading branch information
edan-bainglass authored Dec 23, 2024
1 parent 6d5a1d3 commit d4c495e
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/aiidalab_qe/app/configuration/advanced/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class AdvancedConfigurationSettingsModel(
electronic_type = tl.Unicode()
spin_orbit = tl.Unicode()

clean_workdir = tl.Bool(False)
clean_workdir = tl.Bool(DEFAULT["advanced"]["clean_workdir"])
override = tl.Bool(False)
total_charge = tl.Float(DEFAULT["advanced"]["tot_charge"])
van_der_waals_options = tl.List(
Expand Down
35 changes: 24 additions & 11 deletions src/aiidalab_qe/app/configuration/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

DEFAULT: dict = DEFAULT_PARAMETERS # type: ignore

NO_RELAXATION_OPTION = ("Structure as is", "none")


class ConfigurationStepModel(
Model,
Expand All @@ -23,8 +25,8 @@ class ConfigurationStepModel(
Confirmable,
):
relax_type_help = tl.Unicode()
relax_type_options = tl.List([DEFAULT["workchain"]["relax_type"]])
relax_type = tl.Unicode(DEFAULT["workchain"]["relax_type"])
relax_type_options = tl.List([NO_RELAXATION_OPTION])
relax_type = tl.Unicode(NO_RELAXATION_OPTION[-1], allow_none=True)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -60,7 +62,7 @@ def update(self):
),
)
relax_type_options = [
("Structure as is", "none"),
NO_RELAXATION_OPTION,
("Atomic positions", "positions"),
("Full geometry", "positions_cell"),
]
Expand All @@ -70,18 +72,23 @@ def update(self):
full_relaxation_option="",
)
relax_type_options = [
("Structure as is", "none"),
NO_RELAXATION_OPTION,
("Atomic positions", "positions"),
]

default = DEFAULT["workchain"]["relax_type"]
default_available = [default in [option[1] for option in relax_type_options]]
relax_type = default if default_available else relax_type_options[-1][-1]

self._defaults = {
"relax_type_help": relax_type_help,
"relax_type_options": relax_type_options,
"relax_type": relax_type_options[-1][-1],
"relax_type": relax_type,
}
with self.hold_trait_notifications():
self.relax_type_help = self._get_default_relax_type_help()
self.relax_type_options = self._get_default_relax_type_options()
self.relax_type = self._get_default_relax_type()

self.relax_type_help = self._get_default_relax_type_help()
self.relax_type_options = self._get_default_relax_type_options()
self.relax_type = self._get_default_relax_type()

def get_model_state(self):
parameters = {
Expand Down Expand Up @@ -148,7 +155,13 @@ def _get_default_relax_type_help(self):
return self._defaults.get("relax_type_help", "")

def _get_default_relax_type_options(self):
return self._defaults.get("relax_type_options", [""])
return self._defaults.get("relax_type_options", [NO_RELAXATION_OPTION])

def _get_default_relax_type(self):
return self._defaults.get("relax_type", "")
options = self._get_default_relax_type_options()
relax_type = self._defaults.get("relax_type", NO_RELAXATION_OPTION[-1])
return (
relax_type
if relax_type in [option[1] for option in options]
else options[-1][-1]
)
47 changes: 47 additions & 0 deletions src/aiidalab_qe/app/parameters/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,54 @@
from collections.abc import Mapping
from importlib import resources
from pathlib import Path

import yaml

from aiidalab_qe.app import parameters


def recursive_merge(d1, d2):
"""Merge dictionary `d2` into dictionary `d1` recursively.
- For keys that are in both dictionaries:
- If values are dictionaries, merge them recursively.
- Otherwise, overwrite the value in `d1` with the value in `d2`.
- Keys in `d2` not in `d1` are added to `d1`.
Parameters
----------
`d1` : `dict`
The dictionary to merge into.
`d2` : `dict`
The dictionary to merge from.
Returns
-------
`dict`
The merged dictionary.
Examples
--------
>>> d1 = {'a': 1, 'b': {'c': 2, 'd': 3}}
>>> d2 = {'b': {'c': 4, 'e': 5}}
>>> recursive_merge(d1, d2)
{'a': 1, 'b': {'c': 4, 'd': 3, 'e': 5}}
"""
for key, value in d2.items():
if key in d1:
if isinstance(d1[key], Mapping) and isinstance(value, Mapping):
recursive_merge(d1[key], value)
else:
d1[key] = value
else:
d1[key] = value
return d1


DEFAULT_PARAMETERS = yaml.safe_load(resources.read_text(parameters, "qeapp.yaml"))


custom_config_file = Path.home() / ".aiidalab" / "quantumespresso" / "config.yml"
if custom_config_file.exists():
custom_config = yaml.safe_load(custom_config_file.read_text())
DEFAULT_PARAMETERS = recursive_merge(DEFAULT_PARAMETERS, custom_config)
1 change: 1 addition & 0 deletions src/aiidalab_qe/app/parameters/qeapp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ workchain:

## Advanced pw settings
advanced:
clean_workdir: false
pseudo_family:
library: SSSP
version: 1.3
Expand Down

0 comments on commit d4c495e

Please sign in to comment.