Skip to content

Add support for arbitrary pseudopotential families to get_pseudo_orbitals - #94

Open
elinscott wants to merge 6 commits into
aiidateam:mainfrom
elinscott:pseudo-orbitals-fallback
Open

Add support for arbitrary pseudopotential families to get_pseudo_orbitals#94
elinscott wants to merge 6 commits into
aiidateam:mainfrom
elinscott:pseudo-orbitals-fallback

Conversation

@elinscott

@elinscott elinscott commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Problem

get_pseudo_orbitals resolves pseudopotentials exclusively by md5 lookup against the bundled semicore tables, so any pseudo outside the curated families fails outright with Cannot find pseudopotential <X> with md5 ...

Change

Resolution now proceeds in three steps, and only the last resort is an error:

  1. Explicit overrideget_pseudo_orbitals gains an overrides mapping, threaded through Wannier90BaseWorkChain.get_builder_from_protocol as pseudo_orbitals_overrides, so a user can state {"Ti": {"pswfcs": ["3S", "3P", "4S", "3D"], "semicores": ["3S", "3P"]}} and bypass all lookup.
  2. Table lookup — unchanged behaviour for curated families
  3. Read from the UPF — the valence orbitals are read directly from the pseudo's own PP_PSWFC block (via upf-tools): each PP_CHI atomic wave function states its angular momentum and principal quantum number, so the pseudo wave function labels (3S, 5D, ...) are read off rather than guessed. The UPF states its own valence, so there is no aufbau guessing.
    • the principal number is taken from the PP_CHI label, which carries the true atomic n across generators; the PP_CHI n attribute is deliberately not used for it, because some ultrasoft/PAW generators write the pseudo (node-based) principal number there (radial nodes + l + 1), so a node-free 6s/6p/5d projector is labelled n=1/2/3. When a label is empty or non-standard the label is reconstructed from the entry's n/l.
    • fully-relativistic pseudos list one PP_CHI per j channel; these are collapsed to one label per unique (n, l), in file order.
    • derived entries always carry an empty semicores list, and a warning is emitted stating that the orbitals were read from the pseudopotential file and how to override them.

Raises ValueError when the orbitals cannot be read from the file (no parseable PP_PSWFC, no atomic wave functions, an entry whose (n, l) cannot be recovered, or occupations that do not sum to z_valence), with a message telling the user how to provide this information explicitly via overrides.

Notes

  • No behaviour change for any pseudo covered by the bundled tables.
  • exclude_semicore: True with a UPF-derived entry excludes nothing (because semicores is not read from the file).
  • The PP_PSWFC block is read via upf-tools, added as a dependency.
  • The summed PP_CHI occupations must agree with z_valence (to numerical noise); a disagreement can mean a valence orbital is missing from PP_PSWFC, so the derivation fails closed to the overrides ValueError.
  • A pseudo that carries no atomic wave functions cannot be read this way and gets the ValueError asking for an overrides entry. Notably this includes the entire SG15 family (its UPFs ship an empty PP_PSWFC block), so SG15 still requires a bundled table entry or explicit overrides; deriving from other UPF sections for such pseudos would be a separate follow-up.

Testing

Unit tests in tests/utils/test_pseudo.py cover the derivation (SR, FR j-collapse, label edge cases), the fail-closed paths, and the tier precedence, all on synthetic UPF content.

The derivation was also swept over 1202 real UPFs (PseudoDojo SR+FR, SG15, plus assorted ONCV/ultrasoft/PAW/v1 files): zero wrong label sets — every derivation matched the curated tables or labels predicted independently from PP_INPUTFILE, and the 202 SG15 files fail closed as intended.

@elinscott elinscott changed the title Add support for other pseudopotential families to get_pseudo_orbitals Add support for arbitrary pseudopotential families to get_pseudo_orbitals Jul 8, 2026
… tables

get_pseudo_orbitals resolved pseudos exclusively by md5 against the
bundled semicore tables, so any family without a table (SG15, custom
pseudos, ...) failed outright with "Cannot find pseudopotential <X>
with md5 ...". Instead of accreting per-family JSON files, resolve in
three tiers: an explicit per-kind `overrides` mapping of `PseudoOrbitals` TypedDicts (threaded through
Wannier90BaseWorkChain.get_builder_from_protocol as
`pseudo_orbitals_overrides`), then the bundled tables, then an aufbau
inference from the pseudo's z_valence (cross-checked against the UPF's
atomic-wave-function angular momenta when readable, warning emitted,
semicores left empty so nothing is silently excluded). The hard error
remains only when inference is impossible.

The inference and tier precedence are unit-tested in
tests/utils/test_pseudo.py with duck-typed pseudo stubs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@elinscott
elinscott force-pushed the pseudo-orbitals-fallback branch from 751f408 to 20c7d37 Compare July 8, 2026 12:28
@elinscott

Copy link
Copy Markdown
Collaborator Author

Note: tests expected to fail until #95 is merged

elinscott and others added 4 commits July 14, 2026 15:03
Fully-relativistic pseudopotentials list one atomic wavefunction per j
channel (l > 0 shells appear twice, j = l +/- 1/2), so the raw
angular-momentum multiset never matches the per-shell aufbau inference and
every FR pseudo outside the bundled tables was rejected. Count each l > 0
shell once per j pair before comparing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t the UPF

_infer_pseudo_orbitals returned an unvalidated aufbau guess whenever the
UPF cross-check could not run (unparseable content, empty or absent atomic
wave functions), so out-of-table heavy and f-block pseudos silently produced
wrong orbital sets (e.g. Au z_valence=11 -> ['4F','5D'], Pb z_valence=4 ->
['5D','6P']) instead of raising. The cross-check now reads the UPF angular
momenta via upf-tools (added to dependencies), and any failure to read them
returns None so get_pseudo_orbitals raises the pre-existing ValueError.
Warning text now states the inference was validated against the UPF. Tests
drive the cross-check with parseable synthetic UPF content: scalar- and
fully-relativistic pass cases, a heavy-element rejection, and a fail-closed
unparseable case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
_infer_pseudo_orbitals guessed the valence orbitals from z_valence via an
aufbau (reverse-Madelung) walk and then validated the guess against the UPF's
angular momenta. The walk is wrong for f-block and heavy elements, where it
mistakes deep-core f shells for valence (Au z_valence=19 -> ['4F','5D'] instead
of 5S 5P 5D 6S; Pb -> ['5D','6P'] instead of 5D 6S 6P), so those pseudos failed
validation and raised even though the UPF states its own valence.

Replace the guess-then-validate path with _derive_pseudo_orbitals_from_upf,
which reads the labels directly from each PP_CHI: the spectroscopic letter from
the angular momentum l, and the principal quantum number from the label's
leading digits. The label's n is preferred over the PP_CHI n attribute on
purpose: some ultrasoft/PAW generators write the pseudo principal number there
(radial nodes + l + 1), so a node-free 6s/6p/5d projector carries n=1/2/3 while
the label keeps the true 6S/6P/5D. Reading the attribute produced nonsense such
as Mg -> ['1S','2P'] and Au.pz-rrkjus -> ['2P','3D','1S']; using the label
gives ['3S','3P'] and ['6P','5D','6S']. The attribute is used only when the
label has no leading digit (empty or non-standard label). Fully-relativistic
j-splits collapse to one label per unique (n, l) in file order. The aufbau walk
is deleted; z_valence now only backs a soft occupation-sum sanity check that
warns rather than rejects. Fail-closed behaviour is preserved: an unreadable or
absent PP_PSWFC returns None so get_pseudo_orbitals raises the pre-existing
overrides ValueError. Warning text now says the orbitals were read from the
pseudopotential file.

Verified by deriving orbitals for all 144 PseudoDojo nc-sr-05 SR UPFs (pswfcs
match the curated md5 table and occupations sum to z_valence for every file),
plus out-of-table ONCV (sr and FR), ultrasoft/PAW and old v1-format pseudos:
heavy elements now come out physically correct, the FR j-split collapses, and a
real ONCV pseudo with no PP_PSWFC (He) fails closed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A disagreement can indicate a valence orbital missing from PP_PSWFC,
which previously produced only a warning while returning a possibly
incomplete orbital set. Zero of 1202 real UPFs tested trip the check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@elinscott elinscott added this to the v2.9.0 milestone Aug 14, 2026
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.

1 participant