From 9af15a0cc0ead33508c583c815e1e07a535e6d9c Mon Sep 17 00:00:00 2001 From: Huite Bootsma Date: Thu, 9 Nov 2023 12:25:41 +0100 Subject: [PATCH] Use ruff to lint --- .pre-commit-config.yaml | 21 +++++-------- docs/conf.py | 2 -- examples/connectivity.py | 2 +- examples/overlap_regridder.py | 4 +-- examples/regridder_overview.py | 4 +-- ruff.toml | 19 ++++++++++++ setup.cfg | 14 --------- tests/__init__.py | 2 -- tests/conftest.py | 33 +++++++++++++++++++++ tests/test_core_utils.py | 10 +++---- tests/test_plot_utils.py | 4 +-- tests/test_regrid/test_regridder.py | 8 ++--- tests/test_snap.py | 4 +-- tests/test_ugrid_dataset.py | 23 ++++++++++----- tox.ini | 24 --------------- xugrid/__init__.py | 30 +++++++++++++++++++ xugrid/constants.py | 4 +-- xugrid/conversion.py | 4 +-- xugrid/core/accessorbase.py | 30 +++++++++---------- xugrid/core/dataarray_accessor.py | 14 ++++----- xugrid/core/dataset_accessor.py | 10 +++---- xugrid/core/wrap.py | 8 +++-- xugrid/data/__init__.py | 9 ++++++ xugrid/data/sample_data.py | 19 +++--------- xugrid/data/synthetic.py | 5 +++- xugrid/meshkernel_utils.py | 3 -- xugrid/plot/__init__.py | 11 +++++++ xugrid/plot/plot.py | 43 ++++++++++++++------------- xugrid/plot/utils.py | 37 +++++++++++------------ xugrid/regrid/overlap_1d.py | 3 ++ xugrid/regrid/reduce.py | 4 +-- xugrid/regrid/regridder.py | 44 +++++++++++++-------------- xugrid/regrid/structured.py | 46 ++++++++++++++++++----------- xugrid/regrid/weight_matrix.py | 4 +-- xugrid/ugrid/burn.py | 12 ++++---- xugrid/ugrid/connectivity.py | 16 ++++------ xugrid/ugrid/conventions.py | 5 +--- xugrid/ugrid/partitioning.py | 22 +++++++------- xugrid/ugrid/polygonize.py | 5 ++-- xugrid/ugrid/snapping.py | 14 ++++----- xugrid/ugrid/ugrid1d.py | 10 +++---- xugrid/ugrid/ugrid2d.py | 35 ++++++++-------------- xugrid/ugrid/ugridbase.py | 44 +++++++++++++-------------- xugrid/ugrid/voronoi.py | 6 ++-- 44 files changed, 355 insertions(+), 316 deletions(-) create mode 100644 ruff.toml delete mode 100644 setup.cfg diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 501e7e653..c436f4763 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,15 +1,10 @@ repos: - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.1.5 hooks: - - id: isort - - - repo: https://github.com/psf/black - rev: 23.3.0 - hooks: - - id: black - - - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 - hooks: - - id: flake8 + # Run the linter. + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + # Run the formatter. + - id: ruff-format \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 88f20231c..44db3b507 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,8 +14,6 @@ # import pkg_resources -import xugrid - # -- Project information ----------------------------------------------------- project = "Xugrid" diff --git a/examples/connectivity.py b/examples/connectivity.py index 61399f1a6..0d17b6229 100644 --- a/examples/connectivity.py +++ b/examples/connectivity.py @@ -89,7 +89,7 @@ xr.full_like(ds["face_z"].ugrid.obj, False, dtype=bool), ds.grids[0], ) -uda.values[0] = True +uda[0] = True uda.ugrid.plot() # %% diff --git a/examples/overlap_regridder.py b/examples/overlap_regridder.py index e5ec9bd31..8c1abbd54 100644 --- a/examples/overlap_regridder.py +++ b/examples/overlap_regridder.py @@ -28,9 +28,7 @@ def create_grid(bounds, nx, ny): - """ - Create a simple grid of triangles covering a rectangle. - """ + """Create a simple grid of triangles covering a rectangle.""" import numpy as np from matplotlib.tri import Triangulation diff --git a/examples/regridder_overview.py b/examples/regridder_overview.py index 5071bab0b..d3800e720 100644 --- a/examples/regridder_overview.py +++ b/examples/regridder_overview.py @@ -37,9 +37,7 @@ def create_grid(bounds, nx, ny): - """ - Create a simple grid of triangles covering a rectangle. - """ + """Create a simple grid of triangles covering a rectangle.""" import numpy as np from matplotlib.tri import Triangulation diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000..ea7e57c74 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,19 @@ +# See https://docs.astral.sh/ruff/rules/ +select = ["C4", "D2", "D3", "D4", "E", "F", "I", "NPY", "PD"] +ignore = [ + "D202", + "D205", + "D206", + "D400", + "D404", + "E402", + "E501", + "E703", + "PD002", + "PD901", +] +fixable = ["I"] +ignore-init-module-imports = true + +[pydocstyle] +convention = "numpy" \ No newline at end of file diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index e9c91c89a..000000000 --- a/setup.cfg +++ /dev/null @@ -1,14 +0,0 @@ -[flake8] -ignore = - # whitespace before ':' - doesn't work well with black - E203, - # module level import not at top of file - E402, - # line too long - let black worry about that - E501, - # line break before binary operator - W503, -per-file-ignores = - __init__.py:F401 - ./docs/conf.py:F401 - conftest.py:F401 \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py index bf8a1ee2f..1fcf950f5 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,6 +1,4 @@ # __init__.py for pytest-cov -import importlib - import pytest diff --git a/tests/conftest.py b/tests/conftest.py index 2a7c0089a..9784c882c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,3 +30,36 @@ quads_1, quads_structured, ) + +__all__ = ( + "disk", + "disk_layered", + "expected_results_centroid", + "expected_results_linear", + "expected_results_overlap", + "grid_data_a", + "grid_data_a_1d", + "grid_data_a_2d", + "grid_data_a_layered", + "grid_data_a_layered_1d", + "grid_data_a_layered_2d", + "grid_data_b", + "grid_data_b_1d", + "grid_data_b_2d", + "grid_data_b_flipped_1d", + "grid_data_c", + "grid_data_c_1d", + "grid_data_c_2d", + "grid_data_d", + "grid_data_d_1d", + "grid_data_dask_expected", + "grid_data_dask_expected_layered", + "grid_data_dask_source", + "grid_data_dask_source_layered", + "grid_data_dask_target", + "grid_data_e", + "grid_data_e_1d", + "quads_0_25", + "quads_1", + "quads_structured", +) \ No newline at end of file diff --git a/tests/test_core_utils.py b/tests/test_core_utils.py index bf1284a86..d1ea224f0 100644 --- a/tests/test_core_utils.py +++ b/tests/test_core_utils.py @@ -13,13 +13,13 @@ def test_either_dict_or_kwargs(): - result = either_dict_or_kwargs(dict(a=1), None, "foo") - expected = dict(a=1) + result = either_dict_or_kwargs({"a": 1}, None, "foo") + expected = {"a": 1} assert result == expected - result = either_dict_or_kwargs(None, dict(a=1), "foo") - expected = dict(a=1) + result = either_dict_or_kwargs(None, {"a": 1}, "foo") + expected = {"a": 1} assert result == expected with pytest.raises(ValueError, match=r"foo"): - result = either_dict_or_kwargs(dict(a=1), dict(a=1), "foo") + result = either_dict_or_kwargs({"a": 1}, {"a": 1}, "foo") diff --git a/tests/test_plot_utils.py b/tests/test_plot_utils.py index 52185bb1a..a0b83f0f6 100644 --- a/tests/test_plot_utils.py +++ b/tests/test_plot_utils.py @@ -59,7 +59,7 @@ def _importorskip( @contextlib.contextmanager def figure_context(*args, **kwargs): - """context manager which autocloses a figure (even if the test failed)""" + """Context manager which autocloses a figure (even if the test failed)""" try: yield None @@ -395,5 +395,5 @@ def test_label_from_attrs(self) -> None: # Latex strings can be longer without needing a new line: long_latex_name = r"$Ra_s = \mathrm{mean}(\epsilon_k) / \mu M^2_\infty$" - da.attrs = dict(long_name=long_latex_name) + da.attrs = {"long_name": long_latex_name} assert label_from_attrs(da) == long_latex_name diff --git a/tests/test_regrid/test_regridder.py b/tests/test_regrid/test_regridder.py index 009770c74..2f16b71b9 100644 --- a/tests/test_regrid/test_regridder.py +++ b/tests/test_regrid/test_regridder.py @@ -67,7 +67,7 @@ def test_centroid_locator_regridder(disk, quads_1): regridder = CentroidLocatorRegridder(source=disk, target=square) result = regridder.regrid(disk) assert isinstance(result, xu.UgridDataArray) - assert result.notnull().any() + assert result.notna().any() assert result.min() >= disk.min() assert result.max() <= disk.max() assert result.grid.n_face == square.grid.n_face @@ -76,7 +76,7 @@ def test_centroid_locator_regridder(disk, quads_1): regridder = CentroidLocatorRegridder(source=result, target=disk) back = regridder.regrid(result) assert isinstance(back, xu.UgridDataArray) - assert back.notnull().any() + assert back.notna().any() assert back.min() >= disk.min() assert back.max() <= disk.max() assert back.grid.n_face == disk.grid.n_face @@ -108,7 +108,7 @@ def test_overlap_regridder(disk, quads_1): square = quads_1 regridder = OverlapRegridder(disk, square, method="mean") result = regridder.regrid(disk) - assert result.notnull().any() + assert result.notna().any() assert result.min() >= disk.min() assert result.max() <= disk.max() @@ -142,7 +142,7 @@ def test_barycentric_interpolator(disk, quads_0_25): square = quads_0_25 regridder = BarycentricInterpolator(source=disk, target=square) result = regridder.regrid(disk) - assert result.notnull().any() + assert result.notna().any() assert result.min() >= disk.min() assert result.max() <= disk.max() diff --git a/tests/test_snap.py b/tests/test_snap.py index e47264089..fadb763fc 100644 --- a/tests/test_snap.py +++ b/tests/test_snap.py @@ -154,6 +154,6 @@ def test_snap_to_grid_with_data(): assert isinstance(uds, xu.UgridDataset) assert isinstance(gdf, gpd.GeoDataFrame) assert uds["a"].dims == (uds.ugrid.grid.edge_dimension,) - assert uds["a"].notnull().sum() == 8 - assert uds["line_index"].notnull().sum() == 8 + assert uds["a"].notna().sum() == 8 + assert uds["line_index"].notna().sum() == 8 assert uds["line_index"].sum() == 0 # all values should be 0 diff --git a/tests/test_ugrid_dataset.py b/tests/test_ugrid_dataset.py index af8baa00a..45842e116 100644 --- a/tests/test_ugrid_dataset.py +++ b/tests/test_ugrid_dataset.py @@ -144,7 +144,7 @@ def test_getattr(self): assert self.uda.dims == self.uda.ugrid.obj.dims assert isinstance(self.uda.data, np.ndarray) # So are functions - assert isinstance(self.uda.isnull(), xugrid.UgridDataArray) + assert isinstance(self.uda.isna(), xugrid.UgridDataArray) # obj should be accessible assert isinstance(self.uda.obj, xr.DataArray) @@ -489,7 +489,7 @@ def test_setitem(self): def test_getattr(self): assert tuple(self.uds.dims) == ("mesh2d_nFaces",) assert isinstance(self.uds.a, xugrid.UgridDataArray) - assert isinstance(self.uds.notnull(), xugrid.UgridDataset) + assert isinstance(self.uds.notna(), xugrid.UgridDataset) # obj should be accessible assert isinstance(self.uds.obj, xr.Dataset) @@ -944,11 +944,14 @@ def test_merge(): def get_ugrid_fillvaluem999_startindex1(): """ - this is a very minimal but comparable dataset to Grevelingen_0002_map.nc (FM output) - It contains triangles and squares - the fillvalue of the connectivity arrays is -999 - the start_index of the connectivity arrays is 1 + Return a minimal dataset with a specific fill value. + This is a very minimal but comparable dataset to Grevelingen_0002_map.nc + (FM output). + + * It contains both triangles and squares. + * The fillvalue of the connectivity arrays is -999 + * The start_index of the connectivity arrays is 1 """ ds2 = xr.Dataset() @@ -1113,7 +1116,9 @@ def get_ugrid_fillvaluem999_startindex1(): def test_fm_fillvalue_startindex_isel(): """ - FM data has 1-based starting index and _FillValue -999, this raises several issues. Since it is not possible to generate a Ugrid2d with these attributes, we are testing with raw data + FM data has 1-based starting index and _FillValue -999, this raises several + issues. Since it is not possible to generate a Ugrid2d with these + attributes, we are testing with raw data """ # xugrid 0.5.0 warns "RuntimeWarning: invalid value encountered in cast: cast = data.astype(dtype, copy=True)" @@ -1125,7 +1130,9 @@ def test_fm_fillvalue_startindex_isel(): def test_fm_facenodeconnectivity_fillvalue(): """ - FM data has 1-based starting index and _FillValue -999, this raises several issues. Since it is not possible to generate a Ugrid2d with these attributes, we are testing with raw data + FM data has 1-based starting index and _FillValue -999, this raises several + issues. Since it is not possible to generate a Ugrid2d with these + attributes, we are testing with raw data """ # xugrid 0.5.0 warns "RuntimeWarning: invalid value encountered in cast: cast = data.astype(dtype, copy=True)" diff --git a/tox.ini b/tox.ini index 16ad15978..45e8d3f79 100644 --- a/tox.ini +++ b/tox.ini @@ -12,30 +12,6 @@ isolated_build = True setenv = CONDA_EXE=mamba -[testenv:format] -skip_install = True -basepython = python3.9 -commands = - isort . - black . - blacken-docs README.rst -deps = - black - blacken-docs - isort - -[testenv:lint] -skip_install = True -basepython = python3.9 -commands = - isort --check . - black --check . - flake8 . -deps = - black - flake8 - isort - [testenv:build] description = run pytest and build docs basepython = python3.9 diff --git a/xugrid/__init__.py b/xugrid/__init__.py index 3048e1c60..1c4fdba16 100644 --- a/xugrid/__init__.py +++ b/xugrid/__init__.py @@ -35,3 +35,33 @@ except pkg_resources.DistributionNotFound: # package is not installed pass + + +__all__ = ( + "data", + "concat", + "full_like", + "merge", + "ones_like", + "open_dataarray", + "open_dataset", + "open_mfdataset", + "open_zarr", + "zeros_like", + "UgridDataArrayAccessor", + "UgridDatasetAccessor", + "UgridDataArray", + "UgridDataset", + "plot", + "BarycentricInterpolator", + "CentroidLocatorRegridder", + "OverlapRegridder", + "RelativeOverlapRegridder", + "burn_vector_geometry", + "UgridRolesAccessor", + "merge_partitions", + "polygonize", + "snap_to_grid", + "Ugrid1d", + "Ugrid2d", +) \ No newline at end of file diff --git a/xugrid/constants.py b/xugrid/constants.py index 19952b536..befb2a387 100644 --- a/xugrid/constants.py +++ b/xugrid/constants.py @@ -43,9 +43,7 @@ class Vector(NamedTuple): class MissingOptionalModule: - """ - Presents a clear error for optional modules. - """ + """Presents a clear error for optional modules.""" def __init__(self, name): self.name = name diff --git a/xugrid/conversion.py b/xugrid/conversion.py index f58c5ec41..629fc7ec8 100644 --- a/xugrid/conversion.py +++ b/xugrid/conversion.py @@ -117,7 +117,7 @@ def _scalar_spacing(coords, spacing): diff = coords.diff(dim) spacing_value = abs(spacing.item()) if not np.allclose( - abs(diff.values), spacing_value, atol=abs(1.0e-4 * spacing.item()) + abs(diff.to_numpy()), spacing_value, atol=abs(1.0e-4 * spacing.item()) ): raise ValueError( f"spacing of {coords.name} does not match value of {spacing.name}" @@ -140,7 +140,7 @@ def _implicit_spacing(coords): f"Cannot derive spacing of 1-sized coordinate: {coords.name} \n" f"Set bounds yourself or assign a d{coords.name} variable with spacing" ) - halfdiff = 0.5 * abs(coords.diff(dim)).values + halfdiff = 0.5 * abs(coords.diff(dim)).to_numpy() return np.insert(halfdiff, 0, halfdiff[0]) diff --git a/xugrid/core/accessorbase.py b/xugrid/core/accessorbase.py index 21598f3da..160ae1901 100644 --- a/xugrid/core/accessorbase.py +++ b/xugrid/core/accessorbase.py @@ -10,63 +10,63 @@ class AbstractUgridAccessor(abc.ABC): @abc.abstractmethod def to_dataset(): - """ """ + pass @abc.abstractmethod def assign_node_coords(): - """ """ + pass @abc.abstractmethod def set_node_coords(): - """ """ + pass @abc.abstractproperty def crs(): - """ """ + pass @abc.abstractmethod def set_crs(): - """ """ + pass @abc.abstractmethod def to_crs(): - """ """ + pass @abc.abstractmethod def sel(): - """ """ + pass @abc.abstractmethod def sel_points(): - """ """ + pass @abc.abstractmethod def intersect_line(): - """ """ + pass @abc.abstractmethod def intersect_linestring(): - """ """ + pass @abc.abstractproperty def bounds(): - """ """ + pass @abc.abstractproperty def total_bounds(): - """ """ + pass @abc.abstractproperty def name(): - """ """ + pass @abc.abstractproperty def names(): - """ """ + pass @abc.abstractproperty def topology(): - """ """ + pass @staticmethod def _raster_xy(bounds: Tuple[float, float, float, float], resolution: float): diff --git a/xugrid/core/dataarray_accessor.py b/xugrid/core/dataarray_accessor.py index 76b979cb8..c8d1711d3 100644 --- a/xugrid/core/dataarray_accessor.py +++ b/xugrid/core/dataarray_accessor.py @@ -143,7 +143,7 @@ def set_node_coords(self, node_x: str, node_y: str): def sel(self, x=None, y=None): """ - Returns a new object, a subselection in the UGRID x and y coordinates. + Return a new object, a subselection in the UGRID x and y coordinates. The indexing for x and y always occurs orthogonally, i.e.: ``.sel(x=[0.0, 5.0], y=[10.0, 15.0])`` results in a four points. For @@ -227,8 +227,8 @@ def rasterize_like(self, other: Union[xr.DataArray, xr.Dataset]) -> xr.DataArray rasterized: xr.DataArray """ x, y, index = self.grid.rasterize_like( - x=other["x"].values, - y=other["y"].values, + x=other["x"].to_numpy(), + y=other["y"].to_numpy(), ) return self._raster(x, y, index) @@ -461,13 +461,13 @@ def _binary_iterate(self, iterations: int, mask, value, border_value): else: exterior = None if mask is not None: - mask = mask.values + mask = mask.to_numpy() obj = self.obj if isinstance(obj, xr.DataArray): output = connectivity._binary_iterate( self.grid.face_face_connectivity, - obj.values, + obj.to_numpy(), value, iterations, mask, @@ -636,7 +636,7 @@ def laplace_interpolate( filled = laplace_interpolate( connectivity=connectivity, - data=da.values, + data=da.to_numpy(), use_weights=xy_weights, direct_solve=direct_solve, drop_tol=drop_tol, @@ -651,7 +651,7 @@ def laplace_interpolate( def to_dataset(self, optional_attributes: bool = False): """ - Converts this UgridDataArray or UgridDataset into a standard + Convert this UgridDataArray or UgridDataset into a standard xarray.Dataset. The UGRID topology information is added as standard data variables. diff --git a/xugrid/core/dataset_accessor.py b/xugrid/core/dataset_accessor.py index 0d4d195f8..ce98d972c 100644 --- a/xugrid/core/dataset_accessor.py +++ b/xugrid/core/dataset_accessor.py @@ -74,7 +74,7 @@ def total_bounds(self) -> Tuple: bounds of the dataset as a whole. Currently does not check whether the coordinate reference systems (CRS) of the grids in the dataset match. """ - bounds = np.column_stack([bound for bound in self.bounds.values()]) + bounds = np.column_stack(list(self.bounds.values())) return ( bounds[0].min(), bounds[1].min(), @@ -286,8 +286,8 @@ def rasterize_like(self, other: Union[xr.DataArray, xr.Dataset]) -> xr.Dataset: ------- rasterized: xr.Dataset """ - x = other["x"].values - y = other["y"].values + x = other["x"].to_numpy() + y = other["y"].to_numpy() datasets = [] for grid in self.grids: xx, yy, index = grid.rasterize_like(x, y) @@ -296,7 +296,7 @@ def rasterize_like(self, other: Union[xr.DataArray, xr.Dataset]) -> xr.Dataset: def to_periodic(self): """ - Converts every grid to a periodic grid, where the rightmost boundary + Convert every grid to a periodic grid, where the rightmost boundary shares its nodes with the leftmost boundary. Returns @@ -380,7 +380,7 @@ def intersect_linestring(self, linestring) -> xr.Dataset: def to_dataset(self, optional_attributes: bool = False): """ - Converts this UgridDataset into a standard + Convert this UgridDataset into a standard xarray.Dataset. The UGRID topology information is added as standard data variables. diff --git a/xugrid/core/wrap.py b/xugrid/core/wrap.py index a1eac300f..fa9a9260d 100644 --- a/xugrid/core/wrap.py +++ b/xugrid/core/wrap.py @@ -33,7 +33,7 @@ def maybe_xugrid(obj, topology, old_indexes=None): else: grids = {dim: topology for dim in topology.dimensions} - item_grids = list(set(grids[dim] for dim in obj.dims if dim in grids)) + item_grids = list({grids[dim] for dim in obj.dims if dim in grids}) if len(item_grids) == 0: return obj @@ -288,8 +288,8 @@ def __init__( grids = [grid_from_dataset(obj, topology) for topology in topologies] else: # Make sure it's a new list - if isinstance(grids, (list, tuple, set)): - grids = [grid for grid in grids] + if isinstance(grids, (tuple, set)): + grids = list(grids) else: # not iterable grids = [grids] # Now typecheck @@ -363,9 +363,11 @@ def __setitem__(self, key, value): def from_geodataframe(geodataframe: "geopandas.GeoDataFrame"): # type: ignore # noqa """ Convert a geodataframe into the appropriate Ugrid topology and dataset. + Parameters ---------- geodataframe: gpd.GeoDataFrame + Returns ------- dataset: UGridDataset diff --git a/xugrid/data/__init__.py b/xugrid/data/__init__.py index d15e046e1..ea74be431 100644 --- a/xugrid/data/__init__.py +++ b/xugrid/data/__init__.py @@ -1,2 +1,11 @@ from xugrid.data.sample_data import adh_san_diego, elevation_nl, provinces_nl, xoxo from xugrid.data.synthetic import disk, generate_disk + +__all__ = ( + "adh_san_diego", + "elevation_nl", + "provinces_nl", + "xoxo", + "disk", + "generate_disk", +) \ No newline at end of file diff --git a/xugrid/data/sample_data.py b/xugrid/data/sample_data.py index f8e4c48a4..9225dd7d0 100644 --- a/xugrid/data/sample_data.py +++ b/xugrid/data/sample_data.py @@ -1,6 +1,3 @@ -""" -Functions to load sample data. -""" import numpy as np import pkg_resources import pooch @@ -20,9 +17,7 @@ def xoxo(): - """ - Fetch a simple two part synthetic unstructured grid topology. - """ + """Fetch a simple two part synthetic unstructured grid topology.""" fname_vertices = REGISTRY.fetch("xoxo_vertices.txt") fname_triangles = REGISTRY.fetch("xoxo_triangles.txt") vertices = np.loadtxt(fname_vertices, dtype=float) @@ -37,9 +32,7 @@ def xoxo(): def adh_san_diego(xarray=False): - """ - Fetch time varying output of a hydraulic simulation. - """ + """Fetch time varying output of a hydraulic simulation.""" fname = REGISTRY.fetch("ADH_SanDiego.nc") ds = xr.open_dataset(fname) ds["node_x"].attrs["standard_name"] = "projection_x_coordinate" @@ -52,9 +45,7 @@ def adh_san_diego(xarray=False): def elevation_nl(xarray=False): - """ - Fetch surface elevation dataset for the Netherlands. - """ + """Fetch surface elevation dataset for the Netherlands.""" fname = REGISTRY.fetch("elevation_nl.nc") ds = xr.open_dataset(fname) ds["mesh2d_node_x"].attrs["standard_name"] = "projection_x_coordinate" @@ -69,9 +60,7 @@ def elevation_nl(xarray=False): def provinces_nl(): - """ - Fetch provinces polygons for the Netherlands. - """ + """Fetch provinces polygons for the Netherlands.""" import geopandas as gpd fname = REGISTRY.fetch("provinces-nl.geojson") diff --git a/xugrid/data/synthetic.py b/xugrid/data/synthetic.py index 09fe47d95..27960eeaf 100644 --- a/xugrid/data/synthetic.py +++ b/xugrid/data/synthetic.py @@ -7,6 +7,7 @@ def transform(vertices, minx, maxx, miny): """ Transform vertices to fit within minx to maxx. + Maintains x:y aspect ratio. """ x, y = vertices.T @@ -63,7 +64,9 @@ def generate_disk(partitions: int, depth: int): def disk(): def function_z(x, y): """ - from https://matplotlib.org/stable/gallery/images_contours_and_fields/tricontour_smooth_user.html + Generate a somewhat interesting surface. + + See: https://matplotlib.org/stable/gallery/images_contours_and_fields/tricontour_smooth_user.html """ r1 = np.sqrt((0.5 - x) ** 2 + (0.5 - y) ** 2) theta1 = np.arctan2(0.5 - x, 0.5 - y) diff --git a/xugrid/meshkernel_utils.py b/xugrid/meshkernel_utils.py index d434214bc..50cafeb38 100644 --- a/xugrid/meshkernel_utils.py +++ b/xugrid/meshkernel_utils.py @@ -1,6 +1,3 @@ -""" -Provides a number of utilities for communicating with MeshKernel(Py) -""" from enum import EnumMeta, IntEnum from typing import Union diff --git a/xugrid/plot/__init__.py b/xugrid/plot/__init__.py index 43f1dc367..c90757b77 100644 --- a/xugrid/plot/__init__.py +++ b/xugrid/plot/__init__.py @@ -8,3 +8,14 @@ surface, tripcolor, ) + +__all__ = ( + "contour", + "contourf", + "imshow", + "line", + "pcolormesh", + "scatter", + "surface", + "tripcolor", +) \ No newline at end of file diff --git a/xugrid/plot/plot.py b/xugrid/plot/plot.py index ff2e325e3..162821161 100644 --- a/xugrid/plot/plot.py +++ b/xugrid/plot/plot.py @@ -1,6 +1,4 @@ -""" -This module is strongly inspired by / copied from xarray/plot/plot.py. -""" +"""This module is strongly inspired by / copied from xarray/plot/plot.py.""" import functools import numpy as np @@ -55,7 +53,7 @@ def _plot2d(plotfunc): """ Decorator for common 2d plotting logic Also adds the 2d plot method to class _PlotMethods - """ + """ # noqa: D401 commondoc = """ Parameters ---------- @@ -218,7 +216,7 @@ def newplotfunc( add_colorbar = False if subplot_kws is None: - subplot_kws = dict() + subplot_kws = {} if plotfunc.__name__ == "surface" and not kwargs.get("_is_facetgrid", False): if ax is None: @@ -266,7 +264,7 @@ def newplotfunc( # darray may be None when plotting just edges (the mesh) if darray is not None: - _ensure_plottable(darray.values) + _ensure_plottable(darray.to_numpy()) cmap_params, cbar_kwargs = _process_cmap_cbar_kwargs( plotfunc, darray, @@ -274,8 +272,8 @@ def newplotfunc( _is_facetgrid=kwargs.pop("_is_facetgrid", False), ) else: - cmap_params = dict() - cbar_kwargs = dict() + cmap_params = {} + cbar_kwargs = {} if "contour" in plotfunc.__name__: # extend is a keyword argument only for contour and contourf, but @@ -364,7 +362,7 @@ def newplotfunc( def scatter(grid, da, ax, **kwargs): dim = get_ugrid_dim(grid, da) x, y = getattr(grid, COORDS[dim]).T - primitive = ax.scatter(x, y, c=da.values.ravel(), **kwargs) + primitive = ax.scatter(x, y, c=da.to_numpy().ravel(), **kwargs) return primitive @@ -374,7 +372,7 @@ def tripcolor(grid, da, ax, **kwargs): if dim != NODE: raise ValueError("tripcolor only supports data on nodes") (x, y, triangles), _ = grid.triangulation - primitive = ax.tripcolor(x, y, triangles, da.values.ravel(), **kwargs) + primitive = ax.tripcolor(x, y, triangles, da.to_numpy().ravel(), **kwargs) return primitive @@ -407,7 +405,7 @@ def line(grid, da, ax, **kwargs): collection = LineCollection(edge_coords, **kwargs) if dim == EDGE: - collection.set_array(da.values) + collection.set_array(da.to_numpy()) collection._scale_norm(norm, vmin, vmax) primitive = ax.add_collection(collection, autolim=False) @@ -451,7 +449,7 @@ def imshow(grid, da, ax, **kwargs): resolution = min(dx, dy) / 500 _, _, index = grid.rasterize(resolution) - img = da.values[index].astype(float) + img = da.to_numpy()[index].astype(float) img[index == grid.fill_value] = np.nan primitive = ax.imshow(img, **kwargs) return primitive @@ -460,7 +458,8 @@ def imshow(grid, da, ax, **kwargs): @_plot2d def contour(grid, da, ax, **kwargs): """ - Filled contour plot of 2D UgridDataArray. + Create a contour plot of a 2D UgridDataArray. + Wraps :py:func:`matplotlib:matplotlib.pyplot.tricontour`. """ dim = get_ugrid_dim(grid, da) @@ -473,14 +472,15 @@ def contour(grid, da, ax, **kwargs): else: raise ValueError("contour only supports data on nodes or faces") - primitive = ax.tricontour(x, y, triangles, z.values.ravel(), **kwargs) + primitive = ax.tricontour(x, y, triangles, z.to_numpy().ravel(), **kwargs) return primitive @_plot2d def contourf(grid, da, ax, **kwargs): """ - Filled contour plot of 2D UgridDataArray. + Create a filled contour plot of a 2D UgridDataArray. + Wraps :py:func:`matplotlib:matplotlib.pyplot.tricontourf`. """ dim = get_ugrid_dim(grid, da) @@ -493,14 +493,14 @@ def contourf(grid, da, ax, **kwargs): else: raise ValueError("contourf only supports data on nodes or faces") - primitive = ax.tricontourf(x, y, triangles, z.values.ravel(), **kwargs) + primitive = ax.tricontourf(x, y, triangles, z.to_numpy().ravel(), **kwargs) return primitive @_plot2d def pcolormesh(grid, da, ax, **kwargs): """ - Pseudocolor plot of 2D UgridDataArray. + Create a pseudocolor mesh plot of a 2D UgridDataArray. Wraps matplotlib PolyCollection. """ @@ -525,7 +525,7 @@ def pcolormesh(grid, da, ax, **kwargs): kwargs["edgecolors"] = "face" collection = PolyCollection(vertices, **kwargs) - collection.set_array(da.values.ravel()) + collection.set_array(da.to_numpy().ravel()) collection._scale_norm(norm, vmin, vmax) primitive = ax.add_collection(collection, autolim=False) @@ -542,7 +542,8 @@ def pcolormesh(grid, da, ax, **kwargs): @_plot2d def surface(grid, da, ax, **kwargs): """ - Surface plot of x-y UgridDataArray. + Create a surface plot of a 2D UgridDataArray. + Wraps :py:func:`matplotlib:mplot3d:plot_trisurf`. """ dim = get_ugrid_dim(grid, da) @@ -555,7 +556,7 @@ def surface(grid, da, ax, **kwargs): else: raise ValueError("surface only supports data on nodes or faces") - primitive = ax.plot_trisurf(x, y, triangles, z.values.ravel(), **kwargs) + primitive = ax.plot_trisurf(x, y, triangles, z.to_numpy().ravel(), **kwargs) return primitive @@ -566,6 +567,8 @@ def plot( **kwargs, ): """ + Plot the data of the DataArray on the Ugrid topology. + Default plot of DataArray using :py:mod:`matplotlib:matplotlib.pyplot`. Calls xarray plotting function based on the topology dimension of the data. diff --git a/xugrid/plot/utils.py b/xugrid/plot/utils.py index 5d5c47001..cb168a882 100644 --- a/xugrid/plot/utils.py +++ b/xugrid/plot/utils.py @@ -79,9 +79,7 @@ def _determine_extend(calc_data, vmin, vmax): def _build_discrete_cmap(cmap, levels, extend, filled): - """ - Build a discrete colormap and normalization of the data. - """ + """Build a discrete colormap and normalization of the data.""" import matplotlib as mpl if len(levels) == 1: @@ -328,9 +326,14 @@ def _determine_cmap_params( vmin = None vmax = None - return dict( - vmin=vmin, vmax=vmax, cmap=cmap, extend=extend, levels=levels, norm=norm - ) + return { + "vmin": vmin, + "vmax": vmax, + "cmap": cmap, + "extend": extend, + "levels": levels, + "norm": norm, + } def get_axis( @@ -395,7 +398,7 @@ def _maybe_gca(**subplot_kws: Any) -> Axes: def _get_units_from_attrs(da: DataArray) -> str: - """Extracts and formats the unit/units from a attributes.""" + """Extract and format the unit/units from a attributes.""" # EDIT: removed pint support for now. # pint_array_type = DuckArrayModule("pint").type units = " [{}]" @@ -409,8 +412,10 @@ def _get_units_from_attrs(da: DataArray) -> str: def label_from_attrs(da: DataArray | None, extra: str = "") -> str: - """Makes informative labels if variable metadata (attrs) follows - CF conventions.""" + """ + Make informative labels if variable metadata (attrs) follows CF + conventions. + """ if da is None: return "" @@ -438,16 +443,12 @@ def label_from_attrs(da: DataArray | None, extra: str = "") -> str: def _valid_other_type( x: ArrayLike, types: type[object] | tuple[type[object], ...] ) -> bool: - """ - Do all elements of x have a type from types? - """ + """Do all elements of x have a type from types?""" return all(isinstance(el, types) for el in np.ravel(x)) def _valid_numpy_subdtype(x, numpy_types): - """ - Is any dtype from numpy_types superior to the dtype of x? - """ + """Is any dtype from numpy_types superior to the dtype of x?""" # If any of the types given in numpy_types is understood as numpy.generic, # all possible x will be considered valid. This is probably unwanted. for t in numpy_types: @@ -529,9 +530,7 @@ def _update_axes( xlim: tuple[float, float] | None = None, ylim: tuple[float, float] | None = None, ) -> None: - """ - Update axes with provided parameters - """ + """Update axes with provided parameters""" if xincrease is None: pass elif xincrease and ax.xaxis_inverted(): @@ -661,7 +660,7 @@ def _easy_facetgrid( Convenience method to call xarray.plot.FacetGrid from 2d plotting methods kwargs are the arguments to 2d plotting method - """ + """ # noqa: D401 if ax is not None: raise ValueError("Can't use axes when making faceted plots.") if aspect is None: diff --git a/xugrid/regrid/overlap_1d.py b/xugrid/regrid/overlap_1d.py index 8562e4210..c73347035 100644 --- a/xugrid/regrid/overlap_1d.py +++ b/xugrid/regrid/overlap_1d.py @@ -99,6 +99,9 @@ def find_indices( target_index, ): """ + Find the indices of target in source. Allocate the result in slices of + source_index and target_index. + This is basically a workaround. Numpy searchsorted does not support an axis argument to search one nD array on the other. See: https://github.com/numpy/numpy/issues/4224 diff --git a/xugrid/regrid/reduce.py b/xugrid/regrid/reduce.py index d370a5df4..146d08590 100644 --- a/xugrid/regrid/reduce.py +++ b/xugrid/regrid/reduce.py @@ -1,6 +1,4 @@ -""" -Contains common reduction methods. -""" +"""Contains common reduction methods.""" import numpy as np diff --git a/xugrid/regrid/regridder.py b/xugrid/regrid/regridder.py index 089d6faaf..b2cf5dcc2 100644 --- a/xugrid/regrid/regridder.py +++ b/xugrid/regrid/regridder.py @@ -1,6 +1,4 @@ -""" -This module is heavily inspired by xemsf.frontend.py -""" +"""This module is heavily inspired by xemsf.frontend.py""" import abc from typing import Callable, Optional, Tuple, Union @@ -35,7 +33,7 @@ def make_regrid(func): """ - Uses a closure to capture func, so numba can compile it efficiently without + Use a closure to capture func, so numba can compile it efficiently without function call overhead. """ f = numba.njit(func, inline="always") @@ -96,11 +94,11 @@ def __init__( @abc.abstractproperty def weights(self): - """ """ + pass @abc.abstractmethod def _compute_weights(self, source, target): - """ """ + pass def _setup_regrid(self, func) -> Callable: if isinstance(func, str): @@ -239,9 +237,7 @@ def regrid(self, object) -> UgridDataArray: ) def to_dataset(self) -> xr.Dataset: - """ - Store the computed weights and target in a dataset for re-use. - """ + """Store the computed weights and target in a dataset for re-use.""" weights_ds = xr.Dataset( {f"__regrid_{k}": v for k, v in zip(self._weights._fields, self._weights)} ) @@ -252,35 +248,37 @@ def to_dataset(self) -> xr.Dataset: @staticmethod def _csr_from_dataset(dataset: xr.Dataset) -> WeightMatrixCSR: """ - variable n and nnz are expected to be scalar variable + Create a compressed sparse row matrix from the dataset variables. + + Variables n and nnz are expected to be scalar variables. """ return WeightMatrixCSR( - dataset["__regrid_data"].values, - dataset["__regrid_indices"].values, - dataset["__regrid_indptr"].values, - dataset["__regrid_n"].values[()], - dataset["__regrid_nnz"].values[()], + dataset["__regrid_data"].to_numpy(), + dataset["__regrid_indices"].to_numpy(), + dataset["__regrid_indptr"].to_numpy(), + dataset["__regrid_n"].item(), + dataset["__regrid_nnz"].item(), ) @staticmethod def _coo_from_dataset(dataset: xr.Dataset) -> WeightMatrixCOO: """ - variable nnz is expected to be scalar variable + Create a coordinate/triplet sparse row matrix from the dataset variables. + + Variables n and nnz are expected to be scalar variables. """ return WeightMatrixCOO( - dataset["__regrid_data"].values, - dataset["__regrid_row"].values, - dataset["__regrid_col"].values, - dataset["__regrid_nnz"].values[()], + dataset["__regrid_data"].to_numpy(), + dataset["__regrid_row"].to_numpy(), + dataset["__regrid_col"].to_numpy(), + dataset["__regrid_nnz"].item(), ) @abc.abstractclassmethod def _weights_from_dataset( cls, dataset: xr.Dataset ) -> Union[WeightMatrixCOO, WeightMatrixCSR]: - """ - Return either COO or CSR weights. - """ + """Return either COO or CSR weights.""" @classmethod def from_weights( diff --git a/xugrid/regrid/structured.py b/xugrid/regrid/structured.py index b7b91bc38..4f7a858eb 100644 --- a/xugrid/regrid/structured.py +++ b/xugrid/regrid/structured.py @@ -36,18 +36,18 @@ def __init__(self, obj: Union[xr.DataArray, xr.Dataset], name: str): index = obj.indexes[name] # take care of potentially decreasing coordinate values if index.is_monotonic_decreasing: - midpoints = index.values[::-1] + midpoints = index.to_numpy()[::-1] flipped = True side = "right" elif index.is_monotonic_increasing: - midpoints = index.values + midpoints = index.to_numpy() flipped = False side = "left" else: raise ValueError(f"{name} is not monotonic for array {obj.name}") if bounds_name in obj.coords: - bounds = obj[bounds_name].values + bounds = obj[bounds_name].to_numpy() size = bounds[:, 1] - bounds[:, 0] else: if size_name in obj.coords: @@ -114,7 +114,8 @@ def valid_nodes_within_bounds( self, other: "StructuredGrid1d" ) -> Tuple[IntArray, IntArray]: """ - Retruns nodes when midpoints are within bounding box of overlaying grid. + Return nodes when midpoints are within bounding box of overlaying grid. + In cases that midpoints (and bounding boxes) are flipped, computed indexes are fliped as well. @@ -147,10 +148,11 @@ def valid_nodes_within_bounds_and_extend( self, other: "StructuredGrid1d" ) -> Tuple[IntArray, IntArray]: """ - Returns all valid nodes for linear interpolation. In addition to - valid_nodes_within_bounds() is checked if target midpoints are not - outside outer source boundary midpoints. In that case there is no - interpolation possible. + Return all valid nodes for linear interpolation. + + In addition to valid_nodes_within_bounds() is checked if target + midpoints are not outside outer source boundary midpoints. In that case + there is no interpolation possible. Parameters ---------- @@ -174,7 +176,7 @@ def overlap_1d_structured( self, other: "StructuredGrid1d" ) -> Tuple[IntArray, IntArray, FloatArray]: """ - Returns source and target nodes and overlapping length. It utilises overlap_1d() + Return source and target nodes and overlapping length. It utilises overlap_1d() and does an aditional flip in cases of reversed midpoints Parameters @@ -204,7 +206,7 @@ def centroids_to_linear_sets( neighbour: np.array, ) -> Tuple[IntArray, IntArray, FloatArray]: """ - Returns for every target node an pair of connected source nodes based + Return for every target node an pair of connected source nodes based on centroids connection inputs. Parameters @@ -254,7 +256,7 @@ def compute_linear_weights_to_centroids( self, other: "StructuredGrid1d", source_index: IntArray, target_index: IntArray ) -> Tuple[FloatArray, IntArray]: """ - Computes linear weights bases on centroid indexes. + Compute linear weights bases on centroid indexes. Parameters ---------- @@ -308,7 +310,7 @@ def sorted_output( self, source_index: IntArray, target_index: IntArray, weights: FloatArray ) -> Tuple[IntArray, IntArray, FloatArray]: """ - Returns sorted input based on target index. The regridder needs input + Return sorted input based on target index. The regridder needs input sorted on row index of WeightMatrixCOO (target index). Parameters @@ -334,7 +336,7 @@ def overlap( self, other: "StructuredGrid1d", relative: bool ) -> Tuple[IntArray, IntArray, FloatArray]: """ - Returns source and target indexes and overlapping length + Return source and target indexes and overlapping length Parameters ---------- @@ -358,7 +360,7 @@ def locate_centroids( self, other: "StructuredGrid1d" ) -> Tuple[IntArray, IntArray, FloatArray]: """ - Returns source and target indexes based on nearest neighbor of + Return source and target indexes based on nearest neighbor of centroids. Parameters @@ -379,7 +381,7 @@ def linear_weights( self, other: "StructuredGrid1d" ) -> Tuple[IntArray, IntArray, FloatArray]: """ - Returns linear source and target indexes and corresponding weights. + Return linear source and target indexes and corresponding weights. Parameters ---------- @@ -419,9 +421,7 @@ def to_dataset(self, name: str) -> xr.DataArray: class StructuredGrid2d(StructuredGrid1d): - """ - e.g. (x,y) -> (x,y) - """ + """Represent e.g. raster data topology.""" def __init__( self, @@ -492,6 +492,8 @@ def broadcast_sorted( def overlap(self, other, relative: bool) -> Tuple[IntArray, IntArray, FloatArray]: """ + Compute (relative) overlap with other. + Returns ------- source_index: 1d np.ndarray of int @@ -516,6 +518,8 @@ def overlap(self, other, relative: bool) -> Tuple[IntArray, IntArray, FloatArray def locate_centroids(self, other) -> Tuple[IntArray, IntArray, FloatArray]: """ + Locate centroids of other in self. + Returns ------- source_index: 1d np.ndarray of int @@ -540,6 +544,8 @@ def locate_centroids(self, other) -> Tuple[IntArray, IntArray, FloatArray]: def linear_weights(self, other) -> Tuple[IntArray, IntArray, FloatArray]: """ + Compute linear interpolation weights with other. + Returns ------- source_index: 1d np.ndarray of int @@ -621,6 +627,8 @@ def broadcast_sorted( def overlap(self, other, relative: bool): """ + Compute (relative) overlap with other. + Returns ------- source_index: 1d np.ndarray of int @@ -722,6 +730,8 @@ def volume(self): def overlap(self, other, relative: bool): """ + Compute (relative) overlap with other. + Returns ------- source_index: 1d np.ndarray of int diff --git a/xugrid/regrid/weight_matrix.py b/xugrid/regrid/weight_matrix.py index bc17095a9..995a4d5d4 100644 --- a/xugrid/regrid/weight_matrix.py +++ b/xugrid/regrid/weight_matrix.py @@ -73,9 +73,7 @@ class WeightMatrixCSR(NamedTuple): @numba.njit(inline="always") def nzrange(A: WeightMatrixCSR, column: int) -> Tuple[IntArray, FloatArray]: - """ - Return the indices and values of a single column - """ + """Return the indices and values of a single column.""" start = A.indptr[column] end = A.indptr[column + 1] return A.indices[start:end], A.data[start:end] diff --git a/xugrid/ugrid/burn.py b/xugrid/ugrid/burn.py index 2c1911b10..9e3f0c67b 100644 --- a/xugrid/ugrid/burn.py +++ b/xugrid/ugrid/burn.py @@ -111,10 +111,12 @@ def _locate_polygon( all_touched: bool, ) -> IntArray: """ - This algorithm burns polygon vector geometries in a 2d topology by: + Locate a single polygon. + + This algorithm burns a polygon vector geometry in a 2d topology by: * Extracting the exterior and interiors (holes) coordinates from the - polygons. + polygon. * Breaking every polygon down into a triangles using an "earcut" algorithm. * Searching the grid for these triangles. @@ -185,9 +187,7 @@ def _burn_points( values: np.ndarray, output: FloatArray, ) -> None: - """ - Simply searches the points in the ``like`` 2D topology. - """ + """Simply searches the points in the ``like`` 2D topology.""" xy = shapely.get_coordinates(points) to_burn = like.locate_points(xy) output[to_burn] = values @@ -201,6 +201,8 @@ def _burn_lines( output: FloatArray, ) -> None: """ + Burn the line values into the underlying faces. + This algorithm breaks any linestring down into edges (two x, y points). We search and intersect every edge in the ``like`` grid, the intersections are discarded. diff --git a/xugrid/ugrid/connectivity.py b/xugrid/ugrid/connectivity.py index e70c7a404..ed765daff 100644 --- a/xugrid/ugrid/connectivity.py +++ b/xugrid/ugrid/connectivity.py @@ -17,9 +17,7 @@ def argsort_rows(array: np.ndarray) -> IntArray: def index_like(xy_a: FloatArray, xy_b: FloatArray, tolerance: float): - """ - Return the index that would transform xy_a into xy_b. - """ + """Return the index that would transform xy_a into xy_b.""" if xy_a.shape != xy_b.shape: raise ValueError("coordinates do not match in shape") @@ -135,7 +133,7 @@ def _topological_sort_by_dfs(A: AdjacencyMatrix): def topological_sort_by_dfs(A: sparse.csr_matrix) -> IntArray: """ - Returns an array of vertices in topological order. + Return an array of vertices in topological order. Parameters ---------- @@ -478,6 +476,8 @@ def face_face_connectivity( fill_value: int, ) -> sparse.csr_matrix: """ + Derive face to face connectivity. + An edge can be shared by two faces at most. If this is the case, they are neighbors. """ @@ -787,9 +787,7 @@ def binary_erosion( exterior: IntArray = None, border_value: bool = False, ) -> BoolArray: - """ - By default, erodes inwards from the exterior. - """ + """By default, erodes inwards from the exterior.""" return _binary_iterate( connectivity=connectivity, input=input, @@ -809,9 +807,7 @@ def binary_dilation( exterior: IntArray = None, border_value: bool = False, ) -> BoolArray: - """ - By default, does not dilate inward from the exterior. - """ + """By default, does not dilate inward from the exterior.""" return _binary_iterate( connectivity=connectivity, input=input, diff --git a/xugrid/ugrid/conventions.py b/xugrid/ugrid/conventions.py index 0a3bbd64a..80affca64 100644 --- a/xugrid/ugrid/conventions.py +++ b/xugrid/ugrid/conventions.py @@ -253,9 +253,7 @@ def _infer_dims( coordinates: Dict[str, Dict[str, Tuple[List[str]]]], vardict: Dict[str, str], ) -> Dict[str, str]: - """ - Infer dimensions based on connectivity and coordinates. - """ + """Infer dimensions based on connectivity and coordinates.""" inferred = {} for role, varname in connectivities.items(): expected_dims = _CONNECTIVITY_DIMS[role] @@ -350,7 +348,6 @@ class UgridRolesAccessor: Examples -------- - To get a list of the UGRID dummy variables in the dataset: >>> dataset.ugrid_roles.topology diff --git a/xugrid/ugrid/partitioning.py b/xugrid/ugrid/partitioning.py index 5de6cd132..a14790a66 100644 --- a/xugrid/ugrid/partitioning.py +++ b/xugrid/ugrid/partitioning.py @@ -1,6 +1,4 @@ -""" -Create and merge partitioned UGRID topologies. -""" +"""Create and merge partitioned UGRID topologies.""" from collections import defaultdict from itertools import accumulate from typing import List @@ -29,6 +27,8 @@ def labels_to_indices(labels: IntArray) -> List[IntArray]: def partition_by_label(grid, obj, labels: IntArray): """ + Partition the grid and xarray object by integer labels. + This function is used by UgridDataArray.partition_by_label and UgridDataset.partition_by_label. @@ -153,14 +153,14 @@ def validate_partition_topology(grouped, n_partition: int): ) for name, grids in grouped.items(): - types = set(type(grid) for grid in grids) + types = {type(grid) for grid in grids} if len(types) > 1: raise TypeError( f"All partition topologies with name {name} should be of the " f"same type, received: {types}" ) - griddims = list(set(tuple(grid.dimensions) for grid in grids)) + griddims = list({tuple(grid.dimensions) for grid in grids}) if len(griddims) > 1: raise ValueError( f"Dimension names on UGRID topology {name} do not match " @@ -182,7 +182,7 @@ def group_grids_by_name(partitions): def validate_partition_objects(data_objects): # Check presence of variables. - allvars = list(set(tuple(sorted(ds.data_vars)) for ds in data_objects)) + allvars = list({tuple(sorted(ds.data_vars)) for ds in data_objects}) if len(allvars) > 1: raise ValueError( "These variables are present in some partitions, but not in " @@ -190,7 +190,7 @@ def validate_partition_objects(data_objects): ) # Check dimensions for var in allvars.pop(): - vardims = list(set(ds[var].dims for ds in data_objects)) + vardims = list({ds[var].dims for ds in data_objects}) if len(vardims) > 1: raise ValueError( f"Dimensions for {var} do not match across partitions: " @@ -199,9 +199,7 @@ def validate_partition_objects(data_objects): def separate_variables(data_objects, ugrid_dims): - """ - Separate into UGRID variables grouped by dimension, and other variables. - """ + """Separate into UGRID variables grouped by dimension, and other variables.""" validate_partition_objects(data_objects) def assert_single_dim(intersection): @@ -263,7 +261,7 @@ def merge_partitions(partitions): ------- merged : UgridDataset """ - types = set(type(obj) for obj in partitions) + types = {type(obj) for obj in partitions} msg = "Expected UgridDataArray or UgridDataset, received: {}" if len(types) > 1: type_names = [t.__name__ for t in types] @@ -280,7 +278,7 @@ def merge_partitions(partitions): ] # Collect grids grids = [grid for p in partitions for grid in p.grids] - ugrid_dims = set(dim for grid in grids for dim in grid.dimensions) + ugrid_dims = {dim for grid in grids for dim in grid.dimensions} grids_by_name = group_grids_by_name(partitions) vars_by_dim, other_vars = separate_variables(data_objects, ugrid_dims) diff --git a/xugrid/ugrid/polygonize.py b/xugrid/ugrid/polygonize.py index 6fb4d3b08..8a88b5a69 100644 --- a/xugrid/ugrid/polygonize.py +++ b/xugrid/ugrid/polygonize.py @@ -1,6 +1,3 @@ -""" -The functions in this module serve to polygonize -""" from typing import Tuple import numpy as np @@ -59,6 +56,8 @@ def _classify( def polygonize(uda: "UgridDataArray") -> "gpd.GeoDataFrame": # type: ignore # noqa """ + Polygonize a UgridDataArray. + This function creates vector polygons for all connected regions of cells (faces) in the Ugrid2d topology sharing a common value. diff --git a/xugrid/ugrid/snapping.py b/xugrid/ugrid/snapping.py index d7316c7b0..5999016c7 100644 --- a/xugrid/ugrid/snapping.py +++ b/xugrid/ugrid/snapping.py @@ -1,6 +1,4 @@ -""" -Snapes nodes at an arbitrary distance together. -""" +"""Logic for snapping points and lines to face nodes and face edges.""" from typing import Tuple, TypeVar, Union import numba as nb @@ -112,7 +110,7 @@ def snap_nodes( } ) ) - return inverse, new["x"].values, new["y"].values + return inverse, new["x"].to_numpy(), new["y"].to_numpy() else: return None, x.copy(), y.copy() @@ -178,7 +176,7 @@ def snap_to_nodes( pd.DataFrame({"i": ties.row, "distance": ties.data}, index=ties.col) .groupby("i")["distance"] .idxmin() - .values + .to_numpy() ) xnew[tie] = to_x[j_nearest] ynew[tie] = to_y[j_nearest] @@ -219,7 +217,7 @@ def left_of(a: Point, p: Point, U: Vector) -> bool: def coerce_geometry(lines: GeoDataFrameType) -> LineArray: - geometry = lines.geometry.values + geometry = lines.geometry.to_numpy() geom_type = shapely.get_type_id(geometry) if not ((geom_type == 1) | (geom_type == 2)).all(): raise ValueError("Geometry should contain only LineStrings and/or LinearRings") @@ -237,6 +235,8 @@ def snap_to_edges( segment_index: IntArray, ) -> Tuple[IntArray, IntArray]: """ + Snap the intersected edges to the edges of the surrounding face. + This algorithm works as follows: * It takes the intersected edges; any edge (p to q) to test falls fully @@ -295,7 +295,7 @@ def _find_largest_edges( ) .groupby("edge_index") .idxmax()["length"] - .values + .to_numpy() ) edge_index = edge_index[max_edge_index] diff --git a/xugrid/ugrid/ugrid1d.py b/xugrid/ugrid/ugrid1d.py index 60f7c4c65..210160643 100644 --- a/xugrid/ugrid/ugrid1d.py +++ b/xugrid/ugrid/ugrid1d.py @@ -130,13 +130,13 @@ def from_dataset(cls, dataset: xr.Dataset, topology: str = None): # They can be reset with .set_node_coords() x_index = coordinates["node_coordinates"][0][0] y_index = coordinates["node_coordinates"][1][0] - node_x_coordinates = ds[x_index].astype(FloatDType).values - node_y_coordinates = ds[y_index].astype(FloatDType).values + node_x_coordinates = ds[x_index].astype(FloatDType).to_numpy() + node_y_coordinates = ds[y_index].astype(FloatDType).to_numpy() edge_nodes = connectivity["edge_node_connectivity"] edge_node_connectivity = cls._prepare_connectivity( ds[edge_nodes], fill_value, dtype=IntDType - ).values + ).to_numpy() indexes["node_x"] = x_index indexes["node_y"] = y_index @@ -538,7 +538,7 @@ def to_nonperiodic(self, xmax, obj): def topological_sort_by_dfs(self) -> IntArray: """ - Returns an array of vertices in topological order. + Return an array of vertices in topological order. Returns ------- @@ -550,7 +550,7 @@ def topological_sort_by_dfs(self) -> IntArray: def contract_vertices(self, indices: IntArray) -> "Ugrid1d": """ - Returns a simplified network topology by removing all nodes that are + Return a simplified network topology by removing all nodes that are not listed in ``indices``. Parameters diff --git a/xugrid/ugrid/ugrid2d.py b/xugrid/ugrid/ugrid2d.py index ff11fba1f..087a1cf17 100644 --- a/xugrid/ugrid/ugrid2d.py +++ b/xugrid/ugrid/ugrid2d.py @@ -270,20 +270,20 @@ def from_dataset(cls, dataset: xr.Dataset, topology: str = None): x_index = coordinates["node_coordinates"][0][0] y_index = coordinates["node_coordinates"][1][0] - node_x_coordinates = ds[x_index].astype(FloatDType).values - node_y_coordinates = ds[y_index].astype(FloatDType).values + node_x_coordinates = ds[x_index].astype(FloatDType).to_numpy() + node_y_coordinates = ds[y_index].astype(FloatDType).to_numpy() face_nodes = connectivity["face_node_connectivity"] fill_value = ds[face_nodes].encoding.get("_FillValue", -1) face_node_connectivity = cls._prepare_connectivity( ds[face_nodes], fill_value, dtype=IntDType - ).values + ).to_numpy() edge_nodes = connectivity.get("edge_node_connectivity") if edge_nodes: edge_node_connectivity = cls._prepare_connectivity( ds[edge_nodes], fill_value, dtype=IntDType - ).values + ).to_numpy() else: edge_node_connectivity = None @@ -387,9 +387,7 @@ def to_dataset( # default, only when called upon. @property def n_face(self) -> int: - """ - Return the number of faces in the UGRID2D topology. - """ + """Return the number of faces in the UGRID2D topology.""" return self.face_node_connectivity.shape[0] @property @@ -423,9 +421,7 @@ def topology_dimension(self): @property def face_dimension(self): - """ - Return the name of the face dimension. - """ + """Return the name of the face dimension.""" return self._attrs["face_dimension"] def _edge_connectivity(self): @@ -474,8 +470,8 @@ def boundary_node_connectivity(self) -> IntArray: """ Boundary node connectivity - Returns: - -------- + Returns + ------- connectivity: ndarray of integers with shape ``(n_boundary_edge, 2)`` """ if self._boundary_node_connectivity is None: @@ -521,9 +517,7 @@ def circumcenters(self): @property def area(self) -> FloatArray: - """ - Area of every face. - """ + """Area of every face.""" if self._area is None: self._area = connectivity.area( self.face_node_connectivity, @@ -535,9 +529,7 @@ def area(self) -> FloatArray: @property def perimeter(self) -> FloatArray: - """ - Perimeter length of every face. - """ + """Perimeter length of every face.""" if self._perimeter is None: self._perimeter = connectivity.perimeter( self.face_node_connectivity, @@ -828,7 +820,6 @@ def validate_edge_node_connectivity(self): Examples -------- - To purge invalid edges and associated data from a dataset that contains un-associated or duplicate edges: @@ -1052,7 +1043,7 @@ def topology_subset( else: return self - index = face_index.values + index = face_index.to_numpy() face_subset = self.face_node_connectivity[index] node_index = np.unique(face_subset.ravel()) node_index = node_index[node_index != self.fill_value] @@ -1183,7 +1174,7 @@ def _validate_indexer(self, indexer) -> Union[slice, np.ndarray]: else: # Convert it into a 1d numpy array if isinstance(indexer, xr.DataArray): - indexer = indexer.values + indexer = indexer.to_numpy() if isinstance(indexer, (list, np.ndarray, int, float)): indexer = np.atleast_1d(indexer) else: @@ -1913,7 +1904,7 @@ def from_geodataframe(geodataframe: "geopandas.GeoDataFrame"): # type: ignore # topology: Ugrid2d """ x, y, face_node_connectivity, fill_value = conversion.polygons_to_faces( - geodataframe.geometry.values + geodataframe.geometry.to_numpy() ) return Ugrid2d(x, y, fill_value, face_node_connectivity, crs=geodataframe.crs) diff --git a/xugrid/ugrid/ugridbase.py b/xugrid/ugrid/ugridbase.py index 4f5c8f0a1..096aaa13e 100644 --- a/xugrid/ugrid/ugridbase.py +++ b/xugrid/ugrid/ugridbase.py @@ -87,67 +87,67 @@ def align(obj, grids, old_indexes): class AbstractUgrid(abc.ABC): @abc.abstractproperty def topology_dimension(): - """ """ + pass @abc.abstractproperty def core_dimension(): - """ """ + pass @abc.abstractproperty def dimensions(): - """ """ + pass @abc.abstractproperty def mesh(): - """ """ + pass @abc.abstractproperty def meshkernel(): - """ """ + pass @abc.abstractstaticmethod def from_dataset(): - """ """ + pass @abc.abstractmethod def to_dataset(): - """ """ + pass @abc.abstractmethod def topology_subset(): - """ """ + pass @abc.abstractmethod def clip_box(): - """ """ + pass @abc.abstractmethod def sel_points(): - """ """ + pass @abc.abstractmethod def intersect_line(): - """ """ + pass @abc.abstractmethod def intersect_linestring(): - """ """ + pass @abc.abstractmethod def sel(): - """ """ + pass @abc.abstractmethod def _clear_geometry_properties(): - """ """ + pass @abc.abstractstaticmethod def merge_partitions(): - """ """ + pass @abc.abstractmethod def reindex_like(): - """ """ + pass def _initialize_indexes_attrs(self, name, dataset, indexes, attrs): defaults = conventions.default_topology_attrs(name, self.topology_dimension) @@ -236,9 +236,7 @@ def _single_topology(dataset: xr.Dataset): return topologies[0] def _filtered_attrs(self, dataset: xr.Dataset): - """ - Removes names that are not present in the dataset. - """ + """Remove names that are not present in the dataset.""" topodim = self.topology_dimension attrs = self._attrs.copy() @@ -277,7 +275,7 @@ def equals(self, other): return True def copy(self): - """Creates deepcopy""" + """Create a deepcopy.""" return copy.deepcopy(self) @property @@ -442,8 +440,8 @@ def set_node_coords( if " " in node_x or " " in node_y: raise ValueError("coordinate names may not contain spaces") - x = obj[node_x].values - y = obj[node_y].values + x = obj[node_x].to_numpy() + y = obj[node_y].to_numpy() if (x.ndim != 1) or (x.size != self.n_node): raise ValueError( @@ -694,7 +692,7 @@ def to_crs( def plot(self, **kwargs): """ - Plots the edges of the mesh. + Plot the edges of the mesh. Parameters ---------- diff --git a/xugrid/ugrid/voronoi.py b/xugrid/ugrid/voronoi.py index 3fdf0e3c3..7e7d41bd5 100644 --- a/xugrid/ugrid/voronoi.py +++ b/xugrid/ugrid/voronoi.py @@ -30,8 +30,8 @@ def dot_product2d(U: FloatArray, V: FloatArray): def _centroid_pandas(i: IntArray, x: FloatArray, y: FloatArray): grouped = pd.DataFrame({"i": i, "x": x, "y": y}).groupby("i").mean() - x_centroid = grouped["x"].values - y_centroid = grouped["y"].values + x_centroid = grouped["x"].to_numpy() + y_centroid = grouped["y"].to_numpy() return x_centroid, y_centroid @@ -144,6 +144,8 @@ def exterior_topology( add_vertices: bool, ): """ + Create the exterior topology of the voronoi tesselation. + The exterior topology of this voronoi tesselation consists of three kinds of vertices: