Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/koopmans/aiida/setup/pseudos.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,9 @@ def _install_sssp_family(label: str, parts: list[str]) -> None:
# SG15 ONCV is published as a single frozen tarball on quantum-simulation.org. It
# bundles every version x relativistic variant in one flat archive; the label's
# version/relativistic parts select which subset of UPFs to install. There is no
# upstream ``aiida-pseudo`` installer for SG15, so we install as a plain
# ``CutoffsPseudoPotentialFamily`` so recommended cutoffs can be attached later
# via ``family.set_cutoffs`` without a reinstall.
# upstream ``aiida-pseudo`` installer for SG15, so we build the family ourselves.
# SG15 publishes no recommended cutoffs, so it is a plain
# ``PseudoPotentialFamily`` and ``ecutwfc``/``ecutrho`` come from the input file.
_SG15_ARCHIVE_URL = (
"http://www.quantum-simulation.org/potentials/sg15_oncv/sg15_oncv_upf_2020-02-06.tar.gz"
)
Expand All @@ -199,7 +199,7 @@ def _install_sg15_family(label: str, parts: list[str]) -> None:
import urllib.request

from aiida_pseudo.data.pseudo import UpfData
from aiida_pseudo.groups.family import CutoffsPseudoPotentialFamily
from aiida_pseudo.groups.family import PseudoPotentialFamily

_, version, functional, relativistic = parts

Expand Down Expand Up @@ -257,6 +257,6 @@ def _install_sg15_family(label: str, parts: list[str]) -> None:
"The archive layout may have changed."
)

family = CutoffsPseudoPotentialFamily.create_from_folder(flat, label, pseudo_type=UpfData)
family = PseudoPotentialFamily.create_from_folder(flat, label, pseudo_type=UpfData)

click.echo(f" Successfully installed '{label}' ({family.count()} pseudopotentials)")
86 changes: 86 additions & 0 deletions tests/test_pseudo_install.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""Tests for the SG15 pseudopotential family installer.

The SG15 archive is never downloaded: a synthetic tarball built from the
``fake_upf_content`` streams is served through a patched ``urlopen``, with the
pinned checksum swapped for that tarball's own.
"""

from __future__ import annotations

import hashlib
import io
import tarfile
import urllib.request
from typing import Any

import pytest

from tests.fixtures import fake_upf_content

SG15_LABEL = "SG15/1.2/PBE/SR"

# One member per (element, version, relativistic variant) the flat archive
# bundles, so the installer has to select the label's subset rather than take
# whatever it finds.
_ARCHIVE_MEMBERS = {
"sg15_oncv_upf_2020-02-06/Si_ONCV_PBE-1.2.upf": ("Si", 4.0),
"sg15_oncv_upf_2020-02-06/O_ONCV_PBE-1.2.upf": ("O", 6.0),
"sg15_oncv_upf_2020-02-06/Si_ONCV_PBE-1.0.upf": ("Si", 4.0),
"sg15_oncv_upf_2020-02-06/Si_ONCV_PBE_FR-1.2.upf": ("Si", 4.0),
}


def _synthetic_archive() -> bytes:
"""Return a gzipped tarball shaped like the published SG15 archive."""
buffer = io.BytesIO()
with tarfile.open(fileobj=buffer, mode="w:gz") as tar:
for name, (element, z_valence) in _ARCHIVE_MEMBERS.items():
payload = fake_upf_content(element, z_valence).encode("utf-8")
info = tarfile.TarInfo(name)
info.size = len(payload)
tar.addfile(info, io.BytesIO(payload))
return buffer.getvalue()


@pytest.fixture
def offline_sg15_archive(monkeypatch: pytest.MonkeyPatch) -> bytes:
"""Serve the synthetic archive from ``urlopen`` and pin its checksum."""
from koopmans.aiida.setup import pseudos

archive = _synthetic_archive()
monkeypatch.setattr(pseudos, "_SG15_ARCHIVE_SHA256", hashlib.sha256(archive).hexdigest())
monkeypatch.setattr(urllib.request, "urlopen", lambda url: io.BytesIO(archive))
return archive


class TestInstallSg15Family:
"""The class the installer builds, and the pseudos it selects."""

def test_installs_a_family_that_recommends_no_cutoffs(
self, aiida_profile_clean: Any, offline_sg15_archive: bytes
) -> None:
"""SG15 publishes no cutoffs, so the family must not claim to have any."""
from aiida_pseudo.groups.family import PseudoPotentialFamily
from aiida_pseudo.groups.mixins import RecommendedCutoffMixin

from koopmans.aiida.setup.pseudos import install_pseudo_family

install_pseudo_family(SG15_LABEL)

family = PseudoPotentialFamily.collection.get(label=SG15_LABEL)
assert type(family) is PseudoPotentialFamily
assert not isinstance(family, RecommendedCutoffMixin)

def test_installs_only_the_labelled_version_and_variant(
self, aiida_profile_clean: Any, offline_sg15_archive: bytes
) -> None:
"""The 1.0 and fully relativistic members of the archive stay out."""
from aiida_pseudo.groups.family import PseudoPotentialFamily

from koopmans.aiida.setup.pseudos import install_pseudo_family

install_pseudo_family(SG15_LABEL)

family = PseudoPotentialFamily.collection.get(label=SG15_LABEL)
assert {pseudo.element for pseudo in family.nodes} == {"Si", "O"}
assert family.count() == 2