From b74b63311042caa5de48d463f2ae04e4ae889d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Fri, 3 Nov 2023 14:59:22 +0100 Subject: [PATCH] DOC: add missing docstrings and fix linting errors in existing docstrings --- pyproject.toml | 12 +++++-- src/gpgi/__init__.py | 65 +++++++++++++++++++++++++++++++++++++- src/gpgi/api.py | 42 ------------------------- src/gpgi/lib/__init__.py | 1 + src/gpgi/types.py | 68 ++++++++++++++++++++++++++++++++-------- tests/test_grid.py | 8 ++--- 6 files changed, 134 insertions(+), 62 deletions(-) delete mode 100644 src/gpgi/api.py diff --git a/pyproject.toml b/pyproject.toml index 203fd18..b897b09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,12 +78,12 @@ exclude_lines = [ ] [tool.ruff] -exclude = ["*__init__.py"] -ignore = ["E501"] +ignore = ["E501", "D101"] select = [ "E", "F", "W", + "D", # pydocstyle "C4", # flake8-comprehensions "B", # flake8-bugbear "YTT", # flake8-2020 @@ -95,6 +95,14 @@ select = [ combine-as-imports = true known-first-party = ["gpgi"] +[tool.ruff.pydocstyle] +convention = "numpy" + +[tool.ruff.per-file-ignores] +"tests/**" = ["D"] +"setup.py" = ["D"] +"_backports.py" = ["D"] + [tool.mypy] python_version = "3.9" show_error_codes = true diff --git a/src/gpgi/__init__.py b/src/gpgi/__init__.py index 9843120..a3d7144 100644 --- a/src/gpgi/__init__.py +++ b/src/gpgi/__init__.py @@ -1,5 +1,68 @@ +"""gpgi: Fast particle deposition at post-processing time.""" from __future__ import annotations -from .api import load +from typing import Any + +from .types import Dataset, FieldMap, Geometry, Grid, ParticleSet __version__ = "0.14.0" + + +def load( + *, + geometry: str = "cartesian", + grid: dict[str, FieldMap] | None = None, + particles: dict[str, FieldMap] | None = None, + metadata: dict[str, Any] | None = None, +) -> Dataset: + r""" + Load a Dataset. + + Parameters + ---------- + geometry: Literal["cartesian", "polar", "cylindrical", "spherical", "equatorial"] + This flag is used for validation of axis names, order and domain limits. + + grid: dict[str, FieldMap] (optional) + A dictionary representing the grid coordinates as 1D arrays of cell left edges, + and on-grid fields as ND arrays (fields are assumed to be defined on cell + centers) + + particles: dict[str, FieldMap] (optional) + A dictionary representing particle coordinates and associated fields as 1D + arrays + + metadata: dict[str, Any] (optional) + A dictionnary representing arbitrary additional data, that will be attached to + the returned Dataset as an attribute (namely, ds.metadata). This special + attribute is accessible from boundary condition methods as the argument of the + same name. + """ + try: + _geometry = Geometry(geometry) + except ValueError: + raise ValueError( + f"unknown geometry {geometry!r}, expected any of {tuple(_.value for _ in Geometry)}" + ) from None + + _grid: Grid | None = None + if grid is not None: + if "cell_edges" not in grid: + raise ValueError("grid dictionary missing required key 'cell_edges'") + _grid = Grid( + _geometry, cell_edges=grid["cell_edges"], fields=grid.get("fields", {}) + ) + + _particles: ParticleSet | None = None + if particles is not None: + if "coordinates" not in particles: + raise ValueError("particles dictionary missing required key 'coordinates'") + _particles = ParticleSet( + _geometry, + coordinates=particles["coordinates"], + fields=particles.get("fields", {}), + ) + + return Dataset( + geometry=_geometry, grid=_grid, particles=_particles, metadata=metadata + ) diff --git a/src/gpgi/api.py b/src/gpgi/api.py deleted file mode 100644 index 9cd6552..0000000 --- a/src/gpgi/api.py +++ /dev/null @@ -1,42 +0,0 @@ -from __future__ import annotations - -from typing import Any - -from .types import Dataset, FieldMap, Geometry, Grid, ParticleSet - - -def load( - *, - geometry: str = "cartesian", - grid: dict[str, FieldMap] | None = None, - particles: dict[str, FieldMap] | None = None, - metadata: dict[str, Any] | None = None, -) -> Dataset: - try: - _geometry = Geometry(geometry) - except ValueError: - raise ValueError( - f"unknown geometry {geometry!r}, expected any of {tuple(_.value for _ in Geometry)}" - ) from None - - _grid: Grid | None = None - if grid is not None: - if "cell_edges" not in grid: - raise ValueError("grid dictionary missing required key 'cell_edges'") - _grid = Grid( - _geometry, cell_edges=grid["cell_edges"], fields=grid.get("fields", {}) - ) - - _particles: ParticleSet | None = None - if particles is not None: - if "coordinates" not in particles: - raise ValueError("particles dictionary missing required key 'coordinates'") - _particles = ParticleSet( - _geometry, - coordinates=particles["coordinates"], - fields=particles.get("fields", {}), - ) - - return Dataset( - geometry=_geometry, grid=_grid, particles=_particles, metadata=metadata - ) diff --git a/src/gpgi/lib/__init__.py b/src/gpgi/lib/__init__.py index e69de29..2be0adf 100644 --- a/src/gpgi/lib/__init__.py +++ b/src/gpgi/lib/__init__.py @@ -0,0 +1 @@ +"""Python implementations of deposition algorithms.""" diff --git a/src/gpgi/types.py b/src/gpgi/types.py index 864f22b..37434ef 100644 --- a/src/gpgi/types.py +++ b/src/gpgi/types.py @@ -1,3 +1,4 @@ +r"""Define the core data structures of the library: Grid, ParticleSet, and Dataset.""" from __future__ import annotations import enum @@ -48,10 +49,12 @@ class Geometry(StrEnum): SPHERICAL = enum.auto() EQUATORIAL = enum.auto() - def __str__(self) -> str: - if sys.version_info >= (3, 11): - return super().__str__() - else: + if sys.version_info >= (3, 11): + pass + else: + + def __str__(self) -> str: + r"""Return str(self).""" return self.name.lower() @@ -202,7 +205,7 @@ def _validate_geometry(self) -> None: } -class CoordinateValidatorMixin(ValidatorMixin, CoordinateData, ABC): +class _CoordinateValidatorMixin(ValidatorMixin, CoordinateData, ABC): def __init__(self) -> None: super().__init__() dts = { @@ -252,13 +255,26 @@ def _get_safe_datatype(self, reference: np.ndarray | None = None) -> np.dtype: return np.dtype(dt) -class Grid(CoordinateValidatorMixin): +class Grid(_CoordinateValidatorMixin): def __init__( self, geometry: Geometry, cell_edges: FieldMap, fields: FieldMap | None = None, ) -> None: + r""" + Define a Grid from cell left-edges and data fields. + + Parameters + ---------- + geometry: gpgi.types.Geometry + + cell_edges: gpgi.types.FieldMap + Left cell edges in each direction as 1D arrays, including the right edge + of the rightmost cell. + + fields: gpgi.types.FieldMap (optional) + """ self.geometry = geometry self.coordinates = cell_edges @@ -288,18 +304,22 @@ def _validate(self) -> None: @property def cell_edges(self) -> FieldMap: + r"""An alias for self.coordinates.""" return self.coordinates @cached_property def cell_centers(self) -> FieldMap: + r"""The positions of cell centers in each direction.""" return {ax: 0.5 * (arr[1:] + arr[:-1]) for ax, arr in self.coordinates.items()} @cached_property def cell_widths(self) -> FieldMap: + r"""The width of cells, expressed as the difference between consecutive left edges.""" return {ax: np.diff(arr) for ax, arr in self.coordinates.items()} @cached_property def shape(self) -> tuple[int, ...]: + r"""The shape of the grid, as a tuple (nx1, (nx2, (nx3))).""" return tuple(len(_) - 1 for _ in self.cell_edges.values()) @cached_property @@ -308,18 +328,22 @@ def _padded_shape(self) -> tuple[int, ...]: @property def size(self) -> int: + r"""The total number of cells in the grid.""" return reduce(int.__mul__, self.shape) @property def ndim(self) -> int: + r"""The number of spatial dimensions that coordinates are defined in.""" return len(self.axes) @property def cell_volumes(self) -> RealArray: - """ + r""" + The generalized ND-volume of grid cells. + 3D: (nx, ny, nz) array representing volumes 2D: (nx, ny) array representing surface - 1D: (nx,) array representing widths (redundant with cell_widths) + 1D: (nx,) array representing widths (redundant with cell_widths). """ widths = list(self.cell_widths.values()) if self.geometry is Geometry.CARTESIAN: @@ -331,7 +355,7 @@ def cell_volumes(self) -> RealArray: ) -class ParticleSet(CoordinateValidatorMixin): +class ParticleSet(_CoordinateValidatorMixin): def __init__( self, geometry: Geometry, @@ -357,10 +381,12 @@ def _validate(self) -> None: @property def count(self) -> int: + r"""The total number of particles in the set.""" return len(next(iter(self.coordinates.values()))) @property def ndim(self) -> int: + r"""The number of spatial dimensions that coordinates are defined in.""" return len(self.axes) @@ -373,6 +399,24 @@ def __init__( particles: ParticleSet | None = None, metadata: dict[str, Any] | None = None, ) -> None: + r""" + Compose a Dataset from a Grid, a ParticleSet, or both. + + Parameters + ---------- + geometry: gpgi.types.Geometry + An enum member that represents the geometry. + + grid: gpgi.types.Grid (optional) + + particles: gpgi.types.ParticleSet (optional) + + metadata: dict[str, Any] (optional) + A dictionnary representing arbitrary additional data, that will be attached to + the returned Dataset as an attribute (namely, ds.metadata). This special + attribute is accessible from boundary condition methods as the argument of the + same name. + """ self.geometry = geometry if grid is None: @@ -487,8 +531,8 @@ def _setup_host_cell_index(self, verbose: bool = False) -> None: @property def host_cell_index(self) -> np.ndarray[Any, np.dtype[np.uint16]]: r""" - The host cell index (HCI) represents the ndimensional index - of the host cell for each particle. + The ND index of the host cell for each particle. + It has shape (particles.count, grid.ndim). Indices are 0-based and ghost layers are included. """ @@ -530,7 +574,6 @@ def is_sorted(self, *, axes: tuple[int, ...] | None = None) -> bool: Parameters ---------- - axes: tuple[int, ...] specify in which order axes should be used for sorting. """ @@ -544,7 +587,6 @@ def sorted(self, axes: tuple[int, ...] | None = None) -> Self: Parameters ---------- - axes: tuple[int, ...] specify in which order axes should be used for sorting. """ diff --git a/tests/test_grid.py b/tests/test_grid.py index 5e2a03e..ef876aa 100644 --- a/tests/test_grid.py +++ b/tests/test_grid.py @@ -2,11 +2,11 @@ import numpy.testing as npt import pytest -from gpgi.api import load +import gpgi def test_cell_volumes_cartesian(): - ds = load( + ds = gpgi.load( geometry="cartesian", grid={ "cell_edges": { @@ -21,7 +21,7 @@ def test_cell_volumes_cartesian(): def test_cell_volumes_curvilinear(): - ds = load( + ds = gpgi.load( geometry="cylindrical", grid={ "cell_edges": { @@ -38,7 +38,7 @@ def test_cell_volumes_curvilinear(): def test_cell_volumes_shape(): - ds = load( + ds = gpgi.load( grid={ "cell_edges": { "x": np.linspace(0, 1, 3),