Source code for xnn.common.data.hub.base

"""Dataset registry and the ``load_dataset`` entry point.

A :class:`DatasetBuilder` knows how to download and preprocess one upstream
dataset into xnn' native *structure dicts* (the list-of-dicts format consumed
by :class:`~xnn.common.data.dataset.AtomicDataset`). Builders self-register
under a short name via :func:`register_dataset`; :func:`load_dataset` looks one
up and drives it, giving a HuggingFace ``load_dataset()``-style one-liner.
"""
from __future__ import annotations

from abc import ABC, abstractmethod
from pathlib import Path
from typing import Any, Optional, Union

from ._download import default_cache_dir

_REGISTRY: dict[str, "DatasetBuilder"] = {}


[docs] class DatasetBuilder(ABC): """Base class for a downloadable, preprocessable dataset. Subclasses set the class attribute :attr:`name` and implement :meth:`load`, which downloads (with caching) and converts the upstream data into xnn structure dicts. Register an instance with :func:`register_dataset` to make it available through :func:`load_dataset`. Attributes ---------- name : str Short registry key (e.g. ``"rmd17"``). description : str One-line human-readable summary, shown by :func:`list_datasets`. """ name: str = "" description: str = ""
[docs] @abstractmethod def load(self, *, split: Optional[str], cache_dir: Path, **kwargs: Any) -> Union[dict[str, list[dict]], list[dict]]: """Download, preprocess, and return the dataset as structure dicts. Parameters ---------- split : str or None Which split to return. ``None`` returns every split as a mapping ``{split_name: structures}``; a name returns that split's list. cache_dir : pathlib.Path Base cache directory; the builder stores its files under ``cache_dir / self.name``. **kwargs Builder-specific options. Returns ------- dict of {str: list of dict} or list of dict All splits (``split is None``) or the requested split, each a list of structure dicts in the format accepted by :func:`~xnn.common.data.dataset.structure_to_graph`. """ raise NotImplementedError
[docs] def register_dataset(builder: DatasetBuilder) -> DatasetBuilder: """Register a builder instance under its :attr:`~DatasetBuilder.name`. Parameters ---------- builder : DatasetBuilder The builder to register. Returns ------- DatasetBuilder The same ``builder``, so this can be used as a decorator on a zero-argument construction. """ _REGISTRY[builder.name] = builder return builder
[docs] def get_builder(name: str) -> DatasetBuilder: """Look up a registered builder by name. Parameters ---------- name : str Registry key. Returns ------- DatasetBuilder The registered builder. Raises ------ KeyError If no dataset is registered under ``name``. """ try: return _REGISTRY[name] except KeyError: raise KeyError( f"unknown dataset {name!r}; available: {', '.join(list_datasets())}" ) from None
[docs] def list_datasets() -> list[str]: """Return the names of all registered datasets, sorted. Returns ------- list of str Registered dataset names. """ return sorted(_REGISTRY)
[docs] def load_dataset(name: str, *, split: Optional[str] = None, cutoff: Optional[float] = None, cache_dir: Optional[Union[str, Path]] = None, **kwargs: Any): """Download and preprocess an upstream dataset in one call. Looks up the builder registered under ``name`` and returns its data as xnn structure dicts, optionally wrapped as ready-to-train :class:`~xnn.common.data.dataset.AtomicDataset` objects. Files are downloaded (and cached, verified by MD5) under ``cache_dir / name`` -- by default the repository's ``datasets/`` directory, so re-running is instant and offline. See :func:`~xnn.common.data.hub._download.default_cache_dir`. Parameters ---------- name : str Registered dataset name; see :func:`list_datasets` (e.g. ``"rmd17"``). split : str, optional Which split to return. ``None`` (default) returns a mapping ``{split_name: data}``; a name (e.g. ``"train"``) returns just that split. Available splits are dataset-specific. cutoff : float, optional If given, each returned split is wrapped in an ``AtomicDataset`` built with this neighbor-list cutoff (ready for a ``DataLoader``). If omitted, raw lists of structure dicts are returned. cache_dir : str or pathlib.Path, optional Base cache directory. Defaults to :func:`~xnn.common.data.hub._download.default_cache_dir`. **kwargs Forwarded to the builder's :meth:`DatasetBuilder.load` (e.g. rMD17's ``molecule``, ``fold``, ``units``). Returns ------- dict or list or AtomicDataset Structure-dict lists (or ``AtomicDataset``\\ s when ``cutoff`` is set), as a per-split mapping when ``split is None`` else for the single split. Examples -------- >>> from xnn.common.data import load_dataset >>> splits = load_dataset("rmd17", molecule="aspirin") # {"train","test"} >>> train = load_dataset("rmd17", molecule="aspirin", split="train", cutoff=5.0) """ builder = get_builder(name) base = Path(cache_dir).expanduser() if cache_dir is not None else default_cache_dir() result = builder.load(split=split, cache_dir=base, **kwargs) if cutoff is None: return result from ..dataset import AtomicDataset if isinstance(result, dict): return {k: AtomicDataset(v, cutoff) for k, v in result.items()} return AtomicDataset(result, cutoff)