Skip to main content

add-source

Guide for adding a new data source to PhysicsNeMo Curator. Use when adding a remote dataset (HuggingFace, S3, etc.) or local file-based source. Covers discovery questions, file format handling, Mesh/DataArray/AtomicData construction, parallel partitioning, testing, and registration.

Source facts

Repository
NVIDIA/physicsnemo-curator
Last source activity
May 11, 2026 at 03:06
Detected SKILL.md language
English
Stars
66
Forks
30

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
add-source
description
Guide for adding a new data source to PhysicsNeMo Curator. Use when adding a remote dataset (HuggingFace, S3, etc.) or local file-based source. Covers discovery questions, file format handling, Mesh/DataArray/AtomicData construction, parallel partitioning, testing, and registration.
# Adding a New Data Source This skill walks through adding a new `Source` to PhysicsNeMo Curator, from initial research through implementation, testing, and registration. ## Step 0: Gather Requirements Before writing any code, answer these questions. Ask the user if anything is unclear. ### Domain Which submodule does this source belong to? | Domain | Type parameter | Submodule | Dependency group | |--------|---------------|-----------|-----------------| | **mesh** | `Source[Mesh]` | `src/physicsnemo_curator/domains/mesh/` | `mesh` (physicsnemo, pyvista, pyarrow, torch) | | **da** | `Source[xr.DataArray]` | `src/physicsnemo_curator/domains/da/` | `da` (xarray, earth2studio, zarr) | | **atm** | `Source[AtomicData]` | `src/physicsnemo_curator/domains/atm/` | `mesh` (nvalchemi, torch, lmdb) | ### Dataset Information Gather all of the following before proceeding: 1. **Name and URL** — What is the dataset called? Where is it hosted? (HuggingFace `hf://datasets/org/repo`, S3, HTTP, local path) 2. **File format** — What format are the files? (VTK/VTP/VTU, Parquet, HDF5, NetCDF, NumPy, Zarr, LMDB, CSV, custom) 3. **Schema / fields** — What fields does the data contain? (coordinates, velocity, pressure, temperature, connectivity, forces, energies, etc.) 4. **Spatial dimensions** — Is the data 1D, 2D, 3D? Structured grid or unstructured mesh? Molecular graph? 5. **Dataset size** — How large is the full dataset? How many samples? 6. **Organization** — How are files organized? Single file per sample? Run directories (`run_0/`, `run_1/`)? Splits (train/val/test)? Multi-file databases? 7. **License** — What license? Is it permissive enough for inclusion? 8. **Documentation** — Is there a paper, README, or data card? 9. **Parameters** — What varies between samples? (geometry, physics parameters, initial conditions, time steps) 10. **Authentication** — Does accessing the data require tokens or credentials? 11. **Parallel constraints** — Does the data backend have concurrency limitations? (e.g. LMDB single-env-per-process, HDF5 locking) 12. **Backend options** — Is there a fast native reader (Rust) available as well as a Python fallback? ### Output Type Construction For **mesh** sources, determine how to construct `physicsnemo.mesh.Mesh`: - **VTK-based**: Use `pyvista.read()` then `from_pyvista()` (see existing VTK sources) - **Non-VTK** (Parquet, HDF5, NumPy): Construct `Mesh` directly: ```python from physicsnemo.mesh import Mesh mesh = Mesh( points=points_tensor, # torch.Tensor, shape (n_points, 3) cells=cells_tensor, # torch.Tensor, shape (n_cells, nodes_per_cell), dtype=long point_data=point_data_td, # TensorDict with batch_size=[n_points], or None cell_data=cell_data_td, # TensorDict with batch_size=[n_cells], or None global_data=global_data_td,# TensorDict with batch_size=[], or None ) ``` For **da** sources, determine how to construct `xarray.DataArray` with appropriate dimensions, coordinates, and attributes. For **atm** sources, determine how to construct `nvalchemi.data.AtomicData`: ```python from nvalchemi.data import AtomicData data = AtomicData( positions=positions, # torch.Tensor, shape (n_atoms, 3) atomic_numbers=z, # torch.Tensor, shape (n_atoms,) cell=cell, # torch.Tensor, shape (3, 3) or None pbc=pbc, # torch.Tensor, shape (3,) bool energy=energy, # torch.Tensor, shape (1,) or None forces=forces, # torch.Tensor, shape (n_atoms, 3) or None ) ``` ### File Discovery Strategy Each source handles its own file discovery and caching internally. Choose the approach based on dataset organization: | Pattern | Approach | When to use | |---------|----------|------------| | Flat directory of files | `pathlib.Path.glob()` | Local-only sources (VTKSource, ASELMDBSource) | | Remote flat files | `fsspec` + `fs.glob()` | Generic remote file glob | | Run-indexed dirs (`run_0/`, `run_1/`) | `fsspec` + `fs.ls()` + regex | Benchmark datasets with numbered runs (DrivAerML, AhmedML) | | API/backend-driven | Domain library fetcher | Weather reanalysis (ERA5, HRRR via earth2studio) | | Single-file datasets | Direct `fsspec`/`pyarrow` | Parquet tables, Zarr stores | ### Reference: Existing Sources | Source | File | Format | Domain | Good example for | |--------|------|--------|--------|-----------------| | `VTKSource` | `mesh/sources/vtk.py` | VTK | mesh | Local file glob, pyvista/rust backend, relative_path | | `DrivAerMLSource` | `mesh/sources/drivaerml.py` | VTP/VTU | mesh | Remote fsspec, multi-mesh, run_id, mesh_name | | `AhmedMLSource` | `mesh/sources/ahmedml.py` | VTP | mesh | HF remote, CSV metadata, multi-mode, run_id | | `NavierStokesCylinderSource` | `mesh/sources/ns_cylinder.py` | Parquet | mesh | Direct Mesh construction, non-VTK | | `D3PlotSource` | `mesh/sources/d3plot.py` | d3plot | mesh | Binary format, python/rust dual backend | | `AnsysRSTSource` | `mesh/sources/ansys_rst.py` | .rst | mesh | Auto-discovery of results, complex extraction | | `OpenRadiossSource` | `mesh/sources/openradioss.py` | VTK | mesh | Per-timestep multi-file, run_id | | `ERA5Source` | `da/sources/era5.py` | API | da | Multi-backend weather, DataArray, earth2studio | | `HRRRSource` | `da/sources/hrrr.py` | API | da | HRRR 3km analysis, multiple providers | | `ASELMDBSource` | `atm/sources/aselmdb.py` | LMDB | atm | partition_indices, rust fallback, relative_path | | `RandomMeshSource` | `mesh/sources/random.py` | Synthetic | mesh | Testing, minimal source | | `RandomDataArraySource` | `da/sources/random.py` | Synthetic | da | Testing, minimal DA source | | `RandomAtomicSource` | `atm/sources/random.py` | Synthetic | atm | Testing, minimal atomic source | ## Step 1: Create the Source Module Create the source file at the appropriate location: - **mesh**: `src/physicsnemo_curator/domains/mesh/sources/<name>.py` - **da**: `src/physicsnemo_curator/domains/da/sources/<name>.py` - **atm**: `src/physicsnemo_curator/domains/atm/sources/<name>.py` ### Required SPDX Header Every source file MUST start with: ```python # SPDX-FileCopyrightText: Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. # SPDX-FileCopyrightText: All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ``` ### Import Block Template ```python """<Dataset name> data source for PhysicsNeMo Curator. Reads <format> data from <location> and yields :class:`~physicsnemo.mesh.Mesh` objects for use in curator pipelines. Examples -------- >>> source = <ClassName>(...) # doctest: +SKIP >>> len(source) # doctest: +SKIP <expected_count> >>> mesh = next(source[0]) # doctest: +SKIP """ from __future__ import annotations import pathlib import tempfile from typing import TYPE_CHECKING, ClassVar, Literal from physicsnemo_curator.core.base import Param, Source from physicsnemo_curator.core.logging import get_logger if TYPE_CHECKING: from collections.abc import Generator from physicsnemo.mesh import Mesh ``` ### Source Class Template ```python _DEFAULT_URL = "<dataset_url>" class <ClassName>(Source[Mesh]): """Read meshes from <Dataset Name>. <2-3 sentence description of the dataset, what it contains, and where it comes from. Include a link to the paper or dataset page.> Parameters ---------- url : str Base URL for the dataset. storage_options : dict[str, object] | None Extra keyword arguments for the fsspec filesystem (e.g. HF token). cache_storage : str Local directory for caching downloaded files. Note ---- - Dataset: `<org/repo> <<url>>`_ - Paper: `arXiv:<id> <https://arxiv.org/abs/<id>>`_ - License: `<license_name> <<license_url>>`_ """ name: ClassVar[str] = "<Display Name>" description: ClassVar[str] = "<Short description for CLI>" @classmethod def params(cls) -> list[Param]: """Return configurable parameters for this source. Returns ------- list[Param] Parameter list for CLI configuration. """ return [ Param( name="url", description="Base dataset URL", type=str, default=_DEFAULT_URL, ), Param( name="cache_storage", description="Local cache directory for downloaded files", type=str, default="", ), # Add dataset-specific params (mesh_type, split, backend, etc.) ] def __init__( self, url: str = _DEFAULT_URL, storage_options: dict[str, object] | None = None, cache_storage: str = "", ) -> None: """Initialize the source. Parameters ---------- url : str Base dataset URL. storage_options : dict[str, object] | None Extra keyword arguments for the fsspec filesystem. cache_storage : str Local cache directory. """ self._url = url self._storage_options = storage_options or {} self._cache_storage = cache_storage or tempfile.mkdtemp( prefix="curator_<name>_" ) self._log = get_logger(self) # Initialize file discovery and data access here # Load lightweight metadata eagerly (e.g. parameter tables) # Defer heavy data loading (geometry, fields) to __getitem__ self._discover_items() def _discover_items(self) -> None: """Eagerly discover available items (files, runs, etc.).""" ... def __len__(self) -> int: """Return the number of items in this source.""" return self._count def __getitem__(self, index: int) -> Generator[Mesh]: """Yield mesh(es) for the given index. Parameters ---------- index : int Zero-based index. Negative indices are supported. Yields ------ Mesh One or more mesh objects for this index. Raises ------ IndexError If *index* is out of range. """ if index < 0: index += len(self) if index < 0 or index >= len(self): msg = f"Index {index} out of range for source with {len(self)} items." raise IndexError(msg) # Load data for this index and construct Mesh yield mesh ``` ### Logging Use the structured component logger: ```python from physicsnemo_curator.core.logging import get_logger class MySource(Source[Mesh]): def __init__(self, ...) -> None: self._log = get_logger(self) self._log.info("Discovered %d items in %s", count, url) def __getitem__(self, index: int) -> Generator[Mesh]: self._log.debug("Reading index %d", index) ... ``` For module-level logging (outside class scope): ```python from physicsnemo_curator.core.logging import get_logger logger = get_logger("MySourceModule") ``` **Do NOT use `logging.getLogger(__name__)`** — always use `get_logger`. ### Key Implementation Patterns #### Pattern A: Local File-Based (VTKSource, ASELMDBSource) Discovers files at construction time via `pathlib.Path.glob()`, then
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub