Skip to content

Fix bugs in pseudopotentials panel - #1504

Merged
edan-bainglass merged 10 commits into
aiidalab:mainfrom
edan-bainglass:fix-pseudos
May 30, 2026
Merged

Fix bugs in pseudopotentials panel#1504
edan-bainglass merged 10 commits into
aiidalab:mainfrom
edan-bainglass:fix-pseudos

Conversation

@edan-bainglass

@edan-bainglass edan-bainglass commented May 20, 2026

Copy link
Copy Markdown
Member

The panel was incorrectly set when loaded from a process (or duplicated).

Closes #1495

Note that this doesn't work on its own due to a larger issue with the blockers system. This is fixed in another PR.

@codecov

codecov Bot commented May 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.94444% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.35%. Comparing base (dd9c160) to head (5d0807b).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...rc/aiidalab_qe/app/configuration/advanced/model.py 28.57% 10 Missing ⚠️
...lab_qe/app/configuration/advanced/pseudos/model.py 91.42% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1504      +/-   ##
==========================================
- Coverage   75.42%   75.35%   -0.07%     
==========================================
  Files         115      115              
  Lines        7365     7378      +13     
==========================================
+ Hits         5555     5560       +5     
- Misses       1810     1818       +8     
Flag Coverage Δ
python-3.12 75.35% <81.94%> (-0.07%) ⬇️
python-3.9 75.38% <81.94%> (-0.07%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@edan-bainglass

Copy link
Copy Markdown
Member Author

@AndresOrtegaGuerrero I did not add any tests for this. I'll open an issue to add tests, but I don't have time at the moment.

Comment on lines -152 to -159
def update_functionals(self):
if self.locked or not (self.functional and self.family):
return
self.functionals = (
[self.functional] * len(self.input_structure.kinds)
if self.has_structure
else []
)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need for functionals. We have all the information we need for what it was used for. Discarding it to reduce complexity.

Comment on lines +331 to +336
def get_cutoffs_by_index(self, index: int) -> list[float]:
return (
[self.cutoffs[0][index], self.cutoffs[1][index]]
if len(self.cutoffs[0]) > index
else [0.0, 0.0]
)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaner to have this helper here than in the settings panel.

Comment on lines +338 to +350
def update_functional(self):
pseudos: list[UpfData] = []
for uuid in self.dictionary.values():
try:
assert uuid is not None
pseudo = orm.load_node(uuid)
pseudos.append(pseudo)
except (AssertionError, exceptions.NotExistent):
continue
functional_set = {pp.base.extras.get("functional", None) for pp in pseudos}
functional = functional_set.pop() if len(functional_set) == 1 else None
self._defaults["functional"] = functional
self.functional = self._get_default("functional")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is called on change of pseudos in the dictionary. It collects each pseudo's functional (from extras), and sets the functional trait to the common functional if exists, otherwise None - functional is disabled in the UI.

Comment on lines 275 to +327
@@ -288,65 +307,23 @@ def on_default_pseudo(
pseudo = orm.load_node(self._model.dictionary.get(kind_name))
except Exception:
pseudo = None
model.uploaded = model.pseudo is pseudo
model.pseudo = pseudo
model.cutoffs = (
[
self._model.cutoffs[0][index], # type: ignore
self._model.cutoffs[1][index], # type: ignore
]
if len(self._model.cutoffs[0]) > index # type: ignore
else [0.0, 0.0]
)
model.cutoffs = self._model.get_cutoffs_by_index(index)
model.update_pseudo_info()
model.uploaded = False

self._model.observe(
on_default_pseudo,
synchronize_pseudo,
"dictionary",
)

def on_pseudo_upload(
change,
model: PseudoPotentialUploaderModel = uploader_model,
kind_name=kind.name,
index=index,
):
if not (change["new"] and model.pseudo):
return

self._model.family = None
self._model.library = None
self._model.functional = None

self._model.dictionary = {
**self._model.dictionary,
kind_name: model.pseudo.uuid,
}

functional = model.pseudo.base.extras.get("functional", None)
functionals = [*self._model.functionals]
functionals[index] = functional
# The following double-setting is done to force the blockers check,
# which now also bail early if the functionals are empty.
self._model.functionals = []
self._model.functionals = functionals

cutoffs: list = deepcopy(self._model.cutoffs) # type: ignore
cutoffs[0][index] = model.cutoffs[0] # type: ignore
cutoffs[1][index] = model.cutoffs[1] # type: ignore
self._model.cutoffs = cutoffs

uploader_model.observe(
on_pseudo_upload,
"uploaded",
)

uploader = PseudoPotentialUploader(model=uploader_model)
uploader.render()

on_default_pseudo()

self._links.extend(uploader.links)

children.append(uploader)

synchronize_pseudo()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mostly renaming and reshuffling for clarity, plus adjusting the logic w.r.t dropping functionals.

Comment on lines 209 to +243
@@ -218,22 +218,29 @@ def set_model_state(self, state: dict):
pseudos.library = library
pseudos.family = pseudo_family_string
else:
# Otherwise, we assume a custom pseudo upload (warning enabled).
# Everything is set to None except the functional, which we extract from the pseudos.
pseudos.show_upload_warning = True
functionals = []
try:
pp_uuid = next(iter(PW["pseudos"].values()))
pseudo_info = get_pseudo_info(pp_uuid)
pseudos.functional = pseudo_info["functional"]
except Exception:
for pp_uuid in PW["pseudos"].values():
pseudo_info = get_pseudo_info(pp_uuid)
functionals.append(pseudo_info["functional"])
if len(set(functionals)) == 1:
pseudos.functional = functionals[0]
else:
pseudos.functional = None
except Exception as err:
print(f"Error loading pseudos: {err}")
pseudos.functional = None
pseudos.library = None
pseudos.family = None
pseudos.show_upload_warning = True

pseudos.functionals = [pseudos.functional] * num_kinds

if pseudos_dictionary := PW.get("pseudos"):
pseudos.dictionary = pseudos_dictionary
pseudos.ecutwfc = SYSTEM.get("ecutwfc", 0.0)
pseudos.ecutrho = SYSTEM.get("ecutrho", 0.0)
if pseudos_dictionary := PW.get("pseudos"):
# We set these outside of the context manager to avoid a reset by other trait notifications.
pseudos.dictionary = pseudos_dictionary
pseudos.ecutwfc = SYSTEM.get("ecutwfc", 0.0)
pseudos.ecutrho = SYSTEM.get("ecutrho", 0.0)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part I suspect can be simplified, but immediate efforts failed. Keeping as is for now.

Comment thread src/aiidalab_qe/app/configuration/advanced/pseudos/model.py
@edan-bainglass

edan-bainglass commented May 30, 2026

Copy link
Copy Markdown
Member Author

@AndresOrtegaGuerrero did you have any other comments? I'd like to get this in before releasing a new QE app version 🙏 If there are any issues that I missed, we can address them with a patch release next month (including further tests of course).

pseudos: list[UpfData] = []
for uuid in self.dictionary.values():
try:
assert uuid is not None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not if uuid is None: ? , I guess is to put raise an Error ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uuid is rarely None. In practice, it shouldn't ever be None. Hence, instead of checking for a condition that is rarely there (if ever), I rather try what SHOULD work, catching the rare cases where something is out of sync. Ideally, I would implement a better logging system for debugging and add debug messages in these rare events, to let us know what happened. Actually, though it would be annoying for the user, perhaps it is good to still print out something. I'll update this block, separating the two errors with dedicated print messages.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at some older code (see the _check_blockers method in the same file, I indeed split the two error paths, with the assert error leading to a blocker w.r.t non-existent pseudos in the selected sssp library for the selected element. I recall this being an issue for Ce at some point. I should do something similar for the new code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there are missing Psedos for SOC for lanthanides... yes

@AndresOrtegaGuerrero
AndresOrtegaGuerrero self-requested a review May 30, 2026 08:56

@AndresOrtegaGuerrero AndresOrtegaGuerrero left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGMT! I left a comment but it was more like a question

@edan-bainglass
edan-bainglass merged commit dc0ae7d into aiidalab:main May 30, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Functional is not set correctly on process load

2 participants