Take the cutoffs from the input when the pseudo family has none - #127
Conversation
A pseudo family carrying no recommended cutoffs stopped the build: a plain family was reported as not installed, and a cutoffs family with no stringency as one whose cutoffs could not be obtained. SG15 installs in the second shape, and `aiida-pseudo install family` produces the first. - Pin the family's pseudos in the pw overrides when it recommends no cutoffs, so `ecutwfc` and `ecutrho` come from the input file. - Require both cutoffs in that case, naming the family and the two keywords. - Leave a family that does recommend cutoffs on the path it took before. - Cover the dft/eps/dfpt/wannierize dispatch and the two routes that build their own scf/nscf overrides. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Documentation build overview
5 files changed ·
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #127 +/- ##
==========================================
+ Coverage 79.29% 81.87% +2.57%
==========================================
Files 44 44
Lines 3381 3464 +83
Branches 526 543 +17
==========================================
+ Hits 2681 2836 +155
+ Misses 566 493 -73
- Partials 134 135 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
# Conflicts: # src/koopmans/aiida/workflows/dscf.py # src/koopmans/aiida/workflows/wannierize.py # tests/test_conversion.py
Both routes build their own scf and nscf overrides, and nothing read them: replacing their pinned pseudos with an empty dict left the suite at the same pass count. - Check that a cutoff-less family's pseudos and both of the input's cutoffs reach the pw steps of the block-by-block wannierize route. - Check the same for the Wannier-initialised DSCF route. - Add a shared helper that drives one route's per-step override entry into the pw protocol builder, as that step does when it runs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An input naming `calculator_parameters.pw.system.ecutwfc` alone kept the family's recommended `ecutrho`: against a family recommending 30 eV / 240 eV, a 45 Ry input built a calculation running 45 Ry against 17.6 Ry. - Raise when the input states exactly one cutoff and the family recommends cutoffs, naming the one to add. - Cover the precedence an input stating both already had over a family's recommendation, which no test held in place. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An input naming `calculator_parameters.pw.system.ecutwfc` alone built a pw.x calculation whose `ecutrho` still came from the pseudopotential family: against a family recommending 17.6 Ry, a stated 45 Ry ran at a ratio of 0.4. - An unstated `ecutrho` follows `ecutwfc` at four times it. - `ecutrho` stated alone raises, naming `ecutwfc`. - A stated pair at another ratio takes effect, with a warning. - The pair is resolved after the `pw.system` block is merged over the top-level `calculator_parameters.ecutwfc` shorthand.
A `kcp.system.ecutrho` of 100 Ry beside a `pw.system.ecutwfc` of 45 Ry built a pw.x calculation at a ratio of 2.2 in silence, while the same pair stated in the `pw` block warned. - Run one ratio check over the resolved pair, whatever key it came from. - Name that key in the warning, so the remedy points at the block the value is written in. - Report the warning against the caller of `input_to_pw_parameters` rather than against koopmans' own line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`calculator_parameters.pw.system.ecutwfc: 0.0` parsed, then died with `ZeroDivisionError: float division by zero`; at -5.0 it parsed, built, and warned that "ecutrho = 180 Ry is -36 times ecutwfc = -5 Ry". - Constrain both `pw.system` cutoffs and the top-level `ecutwfc` shorthand to values greater than zero. - Render a `greater_than` violation as "must be greater than 0.0" beside the input-file key that carries it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`{"ecutwfc": 20.0, "pw": {"system": {"ecutwfc": 45.0}}}` parsed and ran,
handing pw.x 45/180 Ry and kcp.x 20/80 Ry. The two codes read their
cutoffs from different keys, and nothing compared them.
- Require every stated wavefunction cutoff to agree, and every stated
density cutoff, across the top-level shorthand and the `pw` and `kcp`
blocks.
- Name each key and its value in the message, so the reader can see
which one to drop.
- Leave a cutoff stated once alone: a block restating the shorthand, or
a block the other leaves silent, still parses.
- Replace the test pinning the old precedence, whose input is now
rejected, with one covering the same resolve-after-merge ordering
through a shorthand `ecutwfc` and a `pw` block `ecutrho`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The strict repo-wide mypy run failed on the new agreement test's `overrides` argument: `Missing type parameters for generic type "dict"`. - Annotate the parameter as `dict[str, float]`, matching the values the parametrized shapes carry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| "unless `calculator_parameters.pw.system.ecutrho` states otherwise." | ||
| ) | ||
|
|
||
| return {"pseudos": get_pseudos_from_family(pseudo_family, structure)} |
There was a problem hiding this comment.
Why do we need to explicitly provide pseudos in this case? Why doesn't the pseudo_family work any more?
There was a problem hiding this comment.
The family still supplies the pseudos — get_pseudos_from_family(pseudo_family, structure) is where they come from either way. What changes is who resolves it, and it is a workaround for upstream rather than something koopmans wants.
Handed a pseudo_family name, get_builder_from_protocol looks up the family's recommended cutoffs before it gets anywhere near the pseudos, and raises when the family has no stringency defined:
ValueError: failed to obtain recommended cutoffs for pseudo family
`CutoffsPseudoPotentialFamily<SG15/1.2/PBE/SR>`: no default
stringency has been defined.
There is no way to tell it "use this family, take the cutoffs from me". But it has a second branch: given explicit pseudos, it skips the cutoff lookup entirely. So resolving the family ourselves one step early takes that branch and dodges a lookup whose result we were going to overwrite anyway.
Two things worth knowing:
- It only fires for a family that publishes no cutoffs (
pseudo_family_has_cutoffsreturns early otherwise), so the normal path is untouched. - It has a visible cost: aiida-quantumespresso then warns
Found unrecognised key in overrides: scf.pw.pseudos.Sion every affected route, about the key its own builder reads. Noise, but a user sees it — flagged in the PR body as worth reporting upstream.
Graded code-read, not reproduced: I have read upstream's two branches, and the tests pin that the pseudos arrive and the cutoffs are the input's. I have not separately reproduced the claim that the explicit-pseudos branch is why the lookup is skipped.
| f"`{source}` = {ecutrho:g} Ry is {ecutrho / ecutwfc:g} times ecutwfc = " | ||
| f"{ecutwfc:g} Ry. koopmans runs norm-conserving pseudopotentials, for which " | ||
| f"the two converge together at a ratio of {NORM_CONSERVING_DUAL:g}. Drop " | ||
| f"`{source}` to take that ratio.", |
There was a problem hiding this comment.
This is far too complex: Should just read: "ecutrho should be 4 x ecutwfc for nomr-conserving pseudos."
There was a problem hiding this comment.
Cut to the rule. It now reads:
`calculator_parameters.pw.system.ecutrho` = 300 Ry: ecutrho should be 4 x ecutwfc
= 180 Ry for norm-conserving pseudopotentials. Drop
`calculator_parameters.pw.system.ecutrho` to take that.
Gone: the sentence explaining that the two converge together. Kept the value it should have been (180 Ry) and the key to drop, so the reader does not have to do the arithmetic or hunt for which key to edit.
The warning spent three lines explaining why the ratio is four before naming the value to change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Just approved, but would probably rather wait till upstream aiida-quantumespresso issue is fixed and dropping the extra guards before merging as they introduce incorrect warnings. |
Pinning a cutoff-less family's pseudos in the pw overrides made aiida-quantumespresso report `Found unrecognised key in overrides: pw.pseudos.Si` on every affected route, about a key its own builder reads. Upstream #1278 stopped it three commits past the pin. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI and Read the Docs fetched a pinned upstream sha, whose protocol builder refuses a pseudo family recommending no cutoffs. - tests.yml: all three jobs clone elinscott/aiida-quantumespresso at `patched`, in the same shape as the neighbouring sibling clones - .readthedocs.yml: the same clone, keeping the `rm -rf` that a reused build environment needs - CLAUDE.md: record that the branch is live again and that the local checkout still tracks upstream main Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pw override entries carried the family's UpfData nodes so that a family recommending no cutoffs would build; aiida-quantumespresso now takes such a family on its own. - drop the pseudo entries from the scf and nscf overrides on the wannierize-blocks and DSCF routes and from `prepare_common_inputs` - rename `pw_pseudo_overrides` to `require_cutoffs_for_family`, which only rejects an input naming no cutoffs against a family recommending none, and drop its unused structure argument - keep that check on the DSCF and wannierize-blocks routes, neither of which goes through `prepare_common_inputs` - cover it on both routes with a test that names the family and the input-file keyword the message must carry - drop the test that only checked UpfData nodes through the graph inputs Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The docstring claimed the user would meet aiida-quantumespresso's message; with the check removed the graph builds and says nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| validate_blocks_separate_occ_and_emp(blocks, nocc) | ||
| validate_blocks_cover_all_occ_bands(blocks, nocc) | ||
|
|
||
| # The DSCF route never calls ``prepare_common_inputs``, so the cutoff check |
There was a problem hiding this comment.
Why doesn't it call prepare_common_inputs?
There was a problem hiding this comment.
Because prepare_common_inputs builds a shape this route does not use, and resolves things it has already resolved.
It returns (structure, pseudo_family, overrides) where overrides is flat — one entry per sub-workflow key, {"scf": {...}, "nscf": {...}} — built for a route that hands the whole thing to one graph builder. dscf_wannier_init_inputs is a helper, called with structure and nbnd already in hand from its caller, and what it assembles is a WannierizeOverrides feeding a per-block fan-out. Calling prepare_common_inputs would redo the structure conversion and the family install, then discard most of what came back.
Worth saying plainly though: three call sites for one check is a wart, and you are right to poke at it. The map today is
| module | prepare_common_inputs |
own cutoff check |
|---|---|---|
dft, eps, dfpt |
yes | via that |
wannierize (whole-manifold) |
yes | via that |
wannierize (block-by-block) |
no | its own |
dscf, trajectory |
no | its own |
So it is not one route being awkward — it is that "prepare the shared pw inputs" and "validate the family against the input" got bundled into one function, and only half the routes want the first. Splitting the validation out, so every route calls it once regardless of how it builds its overrides, would be the real fix. I did not do it here because it touches every route's entry point and this PR was already large; happy to open an issue.
| # This route assembles its own scf/nscf overrides instead of calling | ||
| # ``prepare_common_inputs``, so the cutoff check is its own too. |
There was a problem hiding this comment.
Same root cause, different reason for the split — and this one is a branch rather than a helper.
build_wannierize_workgraph returns early to _build_wannierize_blocks_workgraph at lines 189 and 193, and only reaches prepare_common_inputs at line 197. So the whole-manifold route uses it and the block-by-block route never gets there. The block route needs per-step overrides that feed a fan-out, not the flat one-entry-per-key shape.
Worth flagging that this is exactly where the risk was: I originally briefed this as "only DSCF needs its own check, because wannierize.py calls prepare_common_inputs". It does — on the other branch. Following that brief would have left the block route silently uncovered, and it was caught by writing the negative control rather than by reading.
The two also fail differently when the check is removed, which is why each has its own test: here you get aiida-quantumespresso's stringency message back, because this route builds the pw protocol eagerly. On DSCF you get nothing at all — the graph builds and carries no wavefunction cutoff into its pw.x steps.
Fix for the duplication is the same one as on the DSCF thread: split the validation out of prepare_common_inputs so every route calls it once, however it builds its overrides.
Problem
A pseudopotential family that recommends no cutoffs could not be used, and the failure spoke the wrong language. koopmans' own SG15 installer produces such a family, as does
aiida-pseudo install family. A user who installed SG15 through koopmans and then ran with it met:Every noun in that is aiida-quantumespresso's — stringency, the aiida-pseudo class name,
overrides— and it names a knob the input file does not expose.Separately, cutoffs stated in the input file were half-taken or silently dropped:
pw.system.ecutwfc: 45against a family recommending 17.6 Ry built a calculation running 45 Ry against 17.6 Ry — a ratio of 0.4, a pair neither the input nor the family asked for.ecutwfc: 20at the top level withpw.system.ecutwfc: 45ran pw.x at 45 Ry and kcp.x at 20 Ry, so the two codes saw different grids.ecutwfc: 20at the top level withkcp.system.ecutwfc: 30ran both codes at 20 Ry, discarding the stated 30 — kcp reads the shorthand first and its own block only as a fallback, the opposite precedence to pw.Changes
The cutoffs come from the input file when the family recommends none.
calculator_parameters.ecutwfcalone is a complete statement:ecutrhofollows at four times it, which for the norm-conserving pseudopotentials koopmans is confined to is not an independent quantity — QE documents 4x as the default, and all 286 pseudos across the six families installed here publish exactly that.An input naming no cutoffs against such a family is refused up front, in koopmans' vocabulary rather than the plugin's, naming
calculator_parameters.ecutwfc.That check sits at three call sites, not one: the DSCF and wannierize-blocks routes build their own scf/nscf overrides instead of going through
prepare_common_inputs, so a single check would leave both uncovered.ecutrhostated on its own raises, namingecutwfc. Inverting the ratio is arithmetic, not intent; an input naming only the density cutoff has left the other out by mistake.Any pair off the ratio of four takes effect with a warning naming the key it came from. Nothing is half-taken, so there is nothing to refuse — the warning is what says the pair is not a norm-conserving one.
Every stated cutoff must agree. Values that disagree across
calculator_parameters.ecutwfc,pw.systemandkcp.systemare rejected at parse rather than sending the two codes to different grids.A cutoff of zero or less is rejected at parse. It previously reached a division and produced a bare
ZeroDivisionError; a negative one passed through entirely.CI and Read the Docs now clone aiida-quantumespresso from our fork's
patchedbranch, which carries the upstream fix (👌 Protocols: support pseudo families without recommended cutoffs aiidateam/aiida-quantumespresso#1281) that lets such a family reach a builder at all.Testing
pw.system.ecutwfc: 45against a family recommending 17.6 Ry builds at 45 / 180. Asserting the derived value is what discriminates: a test that only checked the build no longer raises would pass against code silently keeping 17.6.The up-front refusal is pinned on both routes that carry their own check, and the two fail differently when it is removed. On wannierize-blocks the message reverts to aiida-quantumespresso's stringency text, since that route builds the pw protocol eagerly. On DSCF nothing raises at all — the graph builds and carries no wavefunction cutoff into its pw.x steps. Silence, not a worse message, which is why that one needed its own test.
Each rejection has a negative control: with the rule reverted, every raise-case reports
DID NOT RAISE, and the accept-cases still pass.The off-ratio warning is checked outside pytest against real stderr, and a second test pins that an on-ratio value stays silent, so the first cannot pass by warning on everything.
All four tutorial inputs stay legal under the agreement rule.
Note for review
This branch depends on aiida-quantumespresso
patchedatd800109. Until aiidateam merges #1281, k2mainis red if that branch moves.