diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 8be5010..024da7b 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -144,6 +144,34 @@ flight: a task whose worker disappears may be retried on the next one, in the sa directory as the interrupted run. ``koopmans backend uninstall`` removes the whole setup, database included. +******************* + Pseudopotentials +******************* + +You do not have to install pseudopotentials up front. The ``pseudo_library`` keyword of +your input file names a family, and ``koopmans`` downloads that family the first time it +is needed. It can fetch `PseudoDojo `_, `SSSP +`_ and `SG15 +`_ families, named like + +- ``PseudoDojo/0.4/LDA/SR/standard/upf`` +- ``SSSP/1.3/PBEsol/efficiency`` +- ``SG15/1.2/PBE/SR`` + +A family that you install yourself works just as well, under whatever label you give it: +``koopmans`` downloads a family only when no installed one carries the label you asked +for. This is the route for pseudopotentials it cannot fetch — the full-relativistic LDA +sets that spin-orbit calculations need, for instance. Point ``aiida-pseudo`` at a +directory holding one file per element: + +.. code-block:: console + + $ aiida-pseudo install family my-lda-fr + +Then set ``pseudo_library`` to ``my-lda-fr``. A family installed this way publishes no +recommended cutoffs, so state ``calculator_parameters.ecutwfc`` in your input file; +``ecutrho`` follows at four times it, the ratio norm-conserving pseudopotentials use. + **************************** Installing for development **************************** diff --git a/src/koopmans/aiida/setup/pseudos.py b/src/koopmans/aiida/setup/pseudos.py index ae54449..15fa7f2 100644 --- a/src/koopmans/aiida/setup/pseudos.py +++ b/src/koopmans/aiida/setup/pseudos.py @@ -14,17 +14,16 @@ def ensure_pseudo_family_installed(pseudo_family: str) -> None: """Ensure a pseudopotential family is installed, installing it if necessary. - Supports PseudoDojo families with labels like: + Any already-installed family is used as it stands, whatever its label. A + label that names no installed family is downloaded, which koopmans can do + for three families: 'PseudoDojo/0.4/LDA/SR/standard/upf' - - SSSP families with labels like: 'SSSP/1.3/PBEsol/efficiency' - - And SG15 ONCV families with labels like: 'SG15/1.2/PBE/SR' Raises: - ValueError: If the family format is not recognized or installation fails. + ValueError: If no family carries the label and koopmans cannot + download it, or if the download fails. """ from aiida.common.exceptions import NotExistent from aiida_pseudo.groups.family import PseudoPotentialFamily @@ -58,7 +57,10 @@ def pseudo_family_has_cutoffs(pseudo_family: str) -> bool: def install_pseudo_family(pseudo_family: str) -> None: - """Install a pseudopotential family. Parse the label and dispatch.""" + """Download and install a pseudopotential family. Parse the label and dispatch. + + No family may already carry the label. + """ parts = pseudo_family.split("/") if parts[0] == "PseudoDojo" and len(parts) == 6: @@ -69,8 +71,16 @@ def install_pseudo_family(pseudo_family: str) -> None: _install_sg15_family(pseudo_family, parts) else: raise ValueError( - f"Unrecognized pseudo family format: '{pseudo_family}'. " - "Expected 'PseudoDojo/version/functional/relativistic/protocol/format', " + f"No installed pseudopotential family has the label '{pseudo_family}', and " + "koopmans cannot download one under that label.\n" + "Install the pseudopotentials yourself, from a directory holding one file " + "per element:\n" + f" aiida-pseudo install family {pseudo_family}\n" + "A family installed this way publishes no recommended cutoffs, so set " + "`calculator_parameters.ecutwfc` in your input file; `ecutrho` follows at " + "four times it.\n" + "Alternatively, name a family koopmans can download for you, as " + "'PseudoDojo/version/functional/relativistic/protocol/format', " "'SSSP/version/functional/protocol', " "or 'SG15/version/functional/relativistic'." ) diff --git a/src/koopmans/input_file/workflow.py b/src/koopmans/input_file/workflow.py index 513d42e..79dbda8 100644 --- a/src/koopmans/input_file/workflow.py +++ b/src/koopmans/input_file/workflow.py @@ -64,7 +64,7 @@ class WorkflowConfig(BaseModel): default=True, description="whether or not to calculate the screening parameters ab-initio" ) pseudo_library: str = Field( - description="the pseudopotential library to use (for valid options, run `koopmans pseudos list`)" + description="the label of the pseudopotential family to use. Any family you have installed yourself is used as it stands, whatever its label; a label naming no installed family is downloaded, which koopmans can do for 'PseudoDojo/version/functional/relativistic/protocol/format', 'SSSP/version/functional/protocol' and 'SG15/version/functional/relativistic'. A family that publishes no recommended cutoffs takes them from `calculator_parameters.ecutwfc` instead" ) screening_method: CalculateScreeningMethod = Field( default=CalculateScreeningMethod.DSCF, diff --git a/tests/fixtures.py b/tests/fixtures.py index fef014c..caf7085 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -521,11 +521,11 @@ def _install_fake_family( The streams are enough for ``UpfData`` validation, not physically meaningful pseudos. ``cutoffs=True`` builds a ``CutoffsPseudoPotentialFamily`` with recommended cutoffs — needed by - builders that call ``get_builder_from_protocol`` eagerly at build time - (the aiida-qe protocol machinery only accepts SSSP, PseudoDojo, or a - cutoffs family); plain families cover ``ensure_pseudo_family_installed``. - ``recommended_cutoffs=False`` leaves that cutoffs family with no stringency - defined, the shape ``_install_sg15_family`` produces. + a build that states none of its own; ``cutoffs=False`` builds a plain + ``PseudoPotentialFamily``, the shape both ``aiida-pseudo install family`` + and ``_install_sg15_family`` produce. + ``recommended_cutoffs=False`` leaves the cutoffs family with no stringency + defined, the shape ``-F pseudo.family.cutoffs`` produces on its own. ``has_so=True`` marks every pseudo fully relativistic. """ from aiida.common.exceptions import NotExistent @@ -575,11 +575,12 @@ def fake_sg15_cutoffs_family(aiida_profile: Any) -> Any: @pytest.fixture def fake_sg15_family_without_cutoffs(aiida_profile: Any) -> Any: - """Install ``SG15/1.2/PBE/FR`` in the shape ``_install_sg15_family`` produces. + """Install ``SG15/1.2/PBE/FR`` as a cutoffs family with no stringency defined. - A ``CutoffsPseudoPotentialFamily`` with no stringency defined, so it can - recommend no cutoffs. A label of its own so it coexists with the other - SG15 fixtures in one session profile. + The half-configured shape a user reaches by passing + ``-F pseudo.family.cutoffs`` and never running ``aiida-pseudo family + cutoffs set``: it can recommend no cutoffs. A label of its own so it + coexists with the other SG15 fixtures in one session profile. """ return _install_fake_family( "SG15/1.2/PBE/FR", {"Si": 4.0}, cutoffs=True, recommended_cutoffs=False diff --git a/tests/test_pseudos.py b/tests/test_pseudos.py new file mode 100644 index 0000000..07a9028 --- /dev/null +++ b/tests/test_pseudos.py @@ -0,0 +1,55 @@ +"""Tests for resolving the ``pseudo_library`` label to a pseudopotential family.""" + +from __future__ import annotations + +from typing import Any + +import pytest + + +class TestEnsurePseudoFamilyInstalled: + """``ensure_pseudo_family_installed`` downloads only what is missing.""" + + def test_installed_family_under_arbitrary_label_is_used_as_is( + self, fake_user_built_family: Any, monkeypatch: pytest.MonkeyPatch + ) -> None: + """A label matching no download format resolves to the installed family.""" + from koopmans.aiida.setup import pseudos + + def _fail(label: str) -> None: + raise AssertionError(f"attempted to download '{label}'") + + monkeypatch.setattr(pseudos, "install_pseudo_family", _fail) + + pseudos.ensure_pseudo_family_installed(fake_user_built_family.label) + + def test_uninstallable_label_reports_both_routes(self, aiida_profile_clean: Any) -> None: + """An unknown label names the install command and the download formats.""" + from koopmans.aiida.setup import pseudos + + with pytest.raises(ValueError) as excinfo: + pseudos.ensure_pseudo_family_installed("my-gaas-fr") + + message = str(excinfo.value) + assert "No installed pseudopotential family has the label 'my-gaas-fr'" in message + assert "aiida-pseudo install family my-gaas-fr\n" in message + assert "calculator_parameters.ecutwfc" in message + assert "PseudoDojo/version/functional/relativistic/protocol/format" in message + + def test_the_install_command_asks_for_no_cutoffs_family(self, aiida_profile_clean: Any) -> None: + """The command offered is the plain-family one, with no cutoffs to set. + + A family built with ``-F pseudo.family.cutoffs`` and no + ``aiida-pseudo family cutoffs set`` can recommend no cutoffs, which is + the half-configured shape the message must not send anyone to; the + cutoffs come from the input file instead. + """ + from koopmans.aiida.setup import pseudos + + with pytest.raises(ValueError) as excinfo: + pseudos.ensure_pseudo_family_installed("my-gaas-fr") + + message = str(excinfo.value) + assert "-F" not in message + assert "pseudo.family.cutoffs" not in message + assert "cutoffs set" not in message