Skip to content

Apply function to points within circular neighborhood - #941

Open
ahijevyc wants to merge 58 commits into
UXARRAY:mainfrom
ahijevyc:ahijevyc/neighborhood_filter
Open

Apply function to points within circular neighborhood #941
ahijevyc wants to merge 58 commits into
UXARRAY:mainfrom
ahijevyc:ahijevyc/neighborhood_filter

Conversation

@ahijevyc

@ahijevyc ahijevyc commented Sep 9, 2024

Copy link
Copy Markdown
Collaborator

Apply a neighborhood filter within a circular radius r to a UxDataset or UxDataArray.

Closes #930

Overview

This is kind of like uxarray.UxDataArray.inverse_distance_weighted_remap , but the neighborhood is defined by distance, not a number of nearest neighbors. This is ideally suited for a variable resolution mesh, in which a constant of neighbors doesn't have a constant sized neighborhood. Another difference is that this neighborhood filter does not weight data by inverse distance.

Just like uxarray.UxDataArray.subset.bounding_circle this function uses ball_tree.query_radius to select grid elements in a circular neighborhood, but this function finds the neighborhood for all elements in grid, not just one center_coordinate.

The filter function func may be a user-defined function, but uses np.mean by default. It could be min, max, np.median. It can even use functions that require additional arguments, like np.percentile if you supply the argument(s) with functools.partial (see below)

Expected Usage

from functools import partial
import numpy as np
import uxarray

grid_path = "/glade/campaign/mmm/wmr/weiwang/cps/irma3/2020/tk707_conus/init.nc"
data_path = "/glade/campaign/mmm/wmr/weiwang/cps/irma3/mp6/tk707/diag.2017-09-07_09.00.00.nc"
uxds = uxarray.open_mfdataset(
    grid_path,
    data_path
)

# Trim domain
lon_bounds = (-74, -64)
lat_bounds = (18, 24)
uxda = uxds["refl10cm_max"].isel(Time=0).subset.bounding_box(lon_bounds, lat_bounds)

# this is how you use this function to smooth with 0.25-deg filter.
uxda_mean = uxda.neighborhood_filter(func=np.mean, r=0.25)


# this is another way to use this function with np.percentile
uxda_max = uxda.neighborhood_filter(func=partial(np.percentile, q=90), r=0.25)

(uxda.plot.rasterize() + uxda_mean.plot.rasterize() + uxda_max.plot.rasterize()).cols(1)

PR Checklist

General

  • An issue is linked created and linked
  • Add appropriate labels
  • Filled out Overview and Expected Usage (if applicable) sections

Testing

  • Adequate tests are created if there is new functionality
  • Tests cover all possible logical paths in your function
  • Tests are not too basic (such as simply calling a function and nothing else)

Documentation

  • Docstrings have been added to all new functions
  • Docstrings have updated with any function changes
  • Internal functions have a preceding underscore (_); _neighborhood_filter is internal to uxarray/grid/neighbors.py
  • User functions added to docs/api.rst (the split user/internal api files no longer exist)

Examples

  • Any new notebook examples added to docs/examples/ folder
  • Clear the output of all cells before committing
  • New notebook files added to docs/examples.rst toctree
  • New notebook files added to new entry in docs/gallery.yml with appropriate thumbnail photo in docs/_static/thumbnails/

@ahijevyc ahijevyc added the new feature New user-facing functionality label Sep 9, 2024
@ahijevyc ahijevyc self-assigned this Sep 9, 2024
@ahijevyc ahijevyc mentioned this pull request Sep 9, 2024
14 tasks
Comment thread uxarray/core/dataarray.py Outdated

@philipc2 philipc2 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few initial comments:

Comment thread uxarray/core/dataarray.py Outdated
Comment thread uxarray/core/dataarray.py Outdated
Comment thread uxarray/core/dataset.py Outdated
Comment thread uxarray/core/dataarray.py Outdated
Kept neighborhood and dual additions
@philipc2

philipc2 commented Mar 7, 2025

Copy link
Copy Markdown
Member

HI @ahijevyc

Apologies for not getting to this PR earlier.

Looking at the implementation here, it looks great. It does however bring to light a possible need for us to consider a better, more streamlined, approach to handling these types of groupings and then applying some function on the result.

I mention this because of our Topological Aggregations. For this family of functions, we have distinct methods (i.e. topological_mean()), which looking back at doesn't seem like the preferred approach, especially if we plan to implement groupings like the neighborhood one and perhaps other spatial ones.

Very generally speaking, these functions essentially:

  1. Group unstructured grid elements based on some condition/algorithm. Here we use the KD/BallTree to determine the candidate elements, while in the topological aggregations we use the connectivity information
  2. Apply some function to the grouping (i.e. mean())
  3. Store the results back on the unstructured grid element (node, edge, or face)

I wonder if this would be a good opportunity to extend the inherited .groupby() method from Xarray to support these spatial groupings.

I'm not sure of calling these approaches "kernels" is appropriate, but for the sake of this example, we could provide spatial kernels the user could pass into groupby() and then perform aggregations directly on the result. This feels much more in line with Xarray's design philosophy.

# radial neighborhood of r=0.25
uxds['t2m'].groupby(kernel=ux.BoundingCircle(r=0.25)).mean()

# 2 deg by 2 deg bounding box 
uxds['t2m'].groupby(kernel=ux.BoundingBox(dlon=2, dlat=2))

# group the nodes that surround each face and find the maximum
uxds['node_centered_var'].groupby(kernel=ux.FaceNode()).max()

# this is equivalent to the following in the current release
uxds['node_centered_var'].topological_max(destination='face')

I'll ping @aaronzedwick and @erogluorhan for their thoughts on this. I personally really like the design above and think that it aligns well with the overall design.

@aaronzedwick

aaronzedwick commented Mar 10, 2025

Copy link
Copy Markdown
Member

HI @ahijevyc

Apologies for not getting to this PR earlier.

Looking at the implementation here, it looks great. It does however bring to light a possible need for us to consider a better, more streamlined, approach to handling these types of groupings and then applying some function on the result.

I mention this because of our Topological Aggregations. For this family of functions, we have distinct methods (i.e. topological_mean()), which looking back at doesn't seem like the preferred approach, especially if we plan to implement groupings like the neighborhood one and perhaps other spatial ones.

Very generally speaking, these functions essentially:

  1. Group unstructured grid elements based on some condition/algorithm. Here we use the KD/BallTree to determine the candidate elements, while in the topological aggregations we use the connectivity information
  2. Apply some function to the grouping (i.e. mean())
  3. Store the results back on the unstructured grid element (node, edge, or face)

I wonder if this would be a good opportunity to extend the inherited .groupby() method from Xarray to support these spatial groupings.

I'm not sure of calling these approaches "kernels" is appropriate, but for the sake of this example, we could provide spatial kernels the user could pass into groupby() and then perform aggregations directly on the result. This feels much more in line with Xarray's design philosophy.

# radial neighborhood of r=0.25
uxds['t2m'].groupby(kernel=ux.BoundingCircle(r=0.25)).mean()

# 2 deg by 2 deg bounding box 
uxds['t2m'].groupby(kernel=ux.BoundingBox(dlon=2, dlat=2))

# group the nodes that surround each face and find the maximum
uxds['node_centered_var'].groupby(kernel=ux.FaceNode()).max()

# this is equivalent to the following in the current release
uxds['node_centered_var'].topological_max(destination='face')

I'll ping @aaronzedwick and @erogluorhan for their thoughts on this. I personally really like the design above and think that it aligns well with the overall design.

That is interesting. You suggesting changing the way we do aggregations entirely? Then this would affect the reduction PR I am working on then. Perhaps this PR could implement that change if you wish. I am fine with this, if you want to, it sounds like it would be intuitive.

@philipc2

Copy link
Copy Markdown
Member

You suggesting changing the way we do aggregations entirely?

We should be changing the API for it, since the current one is not sustainable if we plan to implement many times of spatial grouping methods.

The underlying functionality would remain mostly unchanged.

Perhaps this PR could implement that change if you wish.

If we do decide to do this, I'll open up a separate PR starting with the topological aggregations.

@aaronzedwick

Copy link
Copy Markdown
Member

You suggesting changing the way we do aggregations entirely?

We should be changing the API for it, since the current one is not sustainable if we plan to implement many times of spatial grouping methods.

The underlying functionality would remain mostly unchanged.

Perhaps this PR could implement that change if you wish.

If we do decide to do this, I'll open up a separate PR starting with the topological aggregations.

So would the reductions PR be obsolete?

@philipc2

Copy link
Copy Markdown
Member

You suggesting changing the way we do aggregations entirely?

We should be changing the API for it, since the current one is not sustainable if we plan to implement many times of spatial grouping methods.
The underlying functionality would remain mostly unchanged.

Perhaps this PR could implement that change if you wish.

If we do decide to do this, I'll open up a separate PR starting with the topological aggregations.

So would the reductions PR be obsolete?

No. The underlying implementation would remain the same, since we would still need those implemented.

This would just provide a different interface for it, with a more "Xarray-like" interface.

@aaronzedwick

Copy link
Copy Markdown
Member

You suggesting changing the way we do aggregations entirely?

We should be changing the API for it, since the current one is not sustainable if we plan to implement many times of spatial grouping methods.
The underlying functionality would remain mostly unchanged.

Perhaps this PR could implement that change if you wish.

If we do decide to do this, I'll open up a separate PR starting with the topological aggregations.

So would the reductions PR be obsolete?

No. The underlying implementation would remain the same, since we would still need those implemented.

This would just provide a different interface for it, with a more "Xarray-like" interface.

Ah, okay, I see. That makes sense, thanks for the clarification!

@philipc2 philipc2 mentioned this pull request May 14, 2025
9 tasks
@cmdupuis3

Copy link
Copy Markdown
Collaborator

pre-commit.ci autofix

Renames the neighborhood classes and accessors to the singular
`Neighborhood`, `DataArrayNeighborhood`, and `DatasetNeighborhood`. The
plural read as a list or array of neighborhoods rather than one object
describing the neighborhood of every element, which would have been
confusing as soon as anything held several of them.

Adds `_BoundNeighborhoodReductions`, an abstract base carrying the
eleven reductions once for both data-bound classes. Each method names
the `Neighborhood` reduction it stands for and hands it to `_map`, which
subclasses implement to say which data it runs on -- the only thing that
differs between a neighborhood bound to one variable and one bound to a
whole dataset.

This drops the eleven method bodies each bound class used to define, and
removes the `getattr(neighborhood, method)` string dispatch in
`DatasetNeighborhood`, which had reintroduced exactly the lookup table
the kernels are documented as not needing. Choosing a kernel and
preparing its parameter now happens in `Neighborhood` alone, so a bound
reduction cannot reach a different kernel, or a different ddof, than the
unbound one it names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cmdupuis3

cmdupuis3 commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

I have some more changes to consider on my cmd/941 branch. The gist is that I wanted to get away from passing numpy functions to the neighborhood filter, because in order to get the vectorized kernels working, you'd basically have to have a dictionary of numpy functions to vectorized kernels, and the API would be sort of a lie.

Instead, my API proposal is that we have all the named kernels be methods. So, we can call the vectorized kernels by name without mystifying what's actually running, and have nb.reduce(func) be the catch-all for external kernels.

This has the added advantage that the kernels are now separable from the neighborhood construction, so you can store a neighborhood and call multiple kernels on it rather than constructing a new neighborhood each time.

On the other hand, it raises the possibility of having multiple neighborhoodsesesssses, so I renamed them to be singular as objects.

@rajeeja

rajeeja commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

I have some more changes to consider on my cmd/941 branch. The gist is that I wanted to get away from passing numpy functions to the neighborhood filter, because in order to get the vectorized kernels working, you'd basically have to have a dictionary of numpy functions to vectorized kernels, and the API would be sort of a lie.

Instead, my API proposal is that we have all the named kernels be methods. So, we can call the vectorized kernels by name without mystifying what's actually running, and have nb.reduce(func) be the catch-all for external kernels.

This has the added advantage that the kernels are now separable from the neighborhood construction, so you can store a neighborhood and call multiple kernels on it rather than constructing a new neighborhood each time.

On the other hand, it raises the possibility of having multiple neighborhoodsesesssses, so I renamed them to be singular as objects.

I like this design, it is simpler , less duplication and more pythonic. The whole _filter wasn't really needed. One question - Do we really need _BoundNeighborhoodReductions as an ABC, or can the common reduction logic be expressed through a simpler composition/delegation pattern?

@cmdupuis3

Copy link
Copy Markdown
Collaborator

I kind of think there should be a way to unify all three classes somehow, but I haven't found it yet. I can try some more things and let you know.

`_BoundNeighborhoodReductions` becomes a plain class with a documented
`_map` stub. Neither subclass is ever instantiated without `_map`, and
both live in this module, so `abc` was buying an instantiation-time
error nobody could hit.

Each bound reduction now hands `_map` the `Neighborhood` method itself
rather than a lambda that looks it up:

    return self._map(Neighborhood.median)

The reference resolves when the class body runs, so a reduction that
`Neighborhood` does not define cannot be spelled here at all. That
completes a progression: `getattr(nb, "median")` failed at call time,
`lambda nb, uxda: nb.median(uxda)` also failed at call time, and this
fails at import.

The vocabulary is still spelled twice in all, once per signature -- data
taking on `Neighborhood`, data bound here. Collapsing that further would
mean generating the methods, which would break the signatures
`test_invalid_reduction_arguments` pins, and would sit badly beside the
explicit style of the tree classes above.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cmdupuis3

Copy link
Copy Markdown
Collaborator

@rajeeja Alright, I refactored it a bit and got rid of the ABC (although spiritually it still basically is one). I attempted taking a compositional approach, but there's no nice solution that doesn't clutter up the API or duplicate all the reduction methods, or have some other drawbacks.

@erogluorhan erogluorhan added the run-benchmark Run ASV benchmark workflow label Aug 18, 2026
@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown

ASV Benchmarking

Benchmark Comparison Results

Benchmarks that have improved:

Change Before [9d5bcdb] After [3cb3309] Ratio Benchmark (Parameter)
* failed 2.74±0.1ms n/a mpas_ocean.CheckNorm.time_check_norm('120km')
* failed 2.34±0.06ms n/a mpas_ocean.CheckNorm.time_check_norm('480km')
* failed 868±10ms n/a mpas_ocean.ConnectivityConstruction.time_face_face_connectivity('120km')
* failed 55.1±1ms n/a mpas_ocean.ConnectivityConstruction.time_face_face_connectivity('480km')
* failed 694±10μs n/a mpas_ocean.ConnectivityConstruction.time_n_nodes_per_face('120km')
* failed 639±20μs n/a mpas_ocean.ConnectivityConstruction.time_n_nodes_per_face('480km')
* failed 5.57±0.03ms n/a mpas_ocean.ConstructFaceLatLon.time_cartesian_averaging('120km')
* failed 4.06±0.04ms n/a mpas_ocean.ConstructFaceLatLon.time_cartesian_averaging('480km')
* failed 100±0.4ms n/a mpas_ocean.ConstructFaceLatLon.time_welzl('120km')
* failed 10.5±0.4ms n/a mpas_ocean.ConstructFaceLatLon.time_welzl('480km')
* failed 18.2±0.02ms n/a mpas_ocean.ConstructTreeStructures.time_ball_tree('120km')
* failed 1.08±0.01ms n/a mpas_ocean.ConstructTreeStructures.time_ball_tree('480km')
* failed 10.6±0.03ms n/a mpas_ocean.ConstructTreeStructures.time_kd_tree('120km')
* failed 764±10μs n/a mpas_ocean.ConstructTreeStructures.time_kd_tree('480km')
* failed 592±3ms n/a mpas_ocean.CrossSections.time_const_lat('120km', 1)
* failed 297±3ms n/a mpas_ocean.CrossSections.time_const_lat('120km', 2)
* failed 158±3ms n/a mpas_ocean.CrossSections.time_const_lat('120km', 4)
* failed 540±3ms n/a mpas_ocean.CrossSections.time_const_lat('480km', 1)
* failed 269±0.8ms n/a mpas_ocean.CrossSections.time_const_lat('480km', 2)
* failed 138±1ms n/a mpas_ocean.CrossSections.time_const_lat('480km', 4)
* failed 429M n/a mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 1)
* failed 429M n/a mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 2)
* failed 429M n/a mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 4)
* failed 412M n/a mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 1)
* failed 412M n/a mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 2)
* failed 412M n/a mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 4)
* failed 25.0±0.1ms n/a mpas_ocean.DualMesh.time_dual_mesh_construction('120km')
* failed 3.35±0.1ms n/a mpas_ocean.DualMesh.time_dual_mesh_construction('480km')
* failed 62.0±0.5ms n/a mpas_ocean.FaceAreas.time_face_areas('120km')
* failed 8.09±5ms n/a mpas_ocean.FaceAreas.time_face_areas('480km')
* failed 229k n/a mpas_ocean.FaceAreas.track_nbytes_face_areas('120km')
* failed 14.3k n/a mpas_ocean.FaceAreas.track_nbytes_face_areas('480km')
* failed 2.12M n/a mpas_ocean.FaceAreas.track_peakmem_face_areas('120km')
* failed 817k n/a mpas_ocean.FaceAreas.track_peakmem_face_areas('480km')
* failed 940±10ms n/a mpas_ocean.GeoDataFrame.time_to_geodataframe('120km', False)
* failed 51.9±1ms n/a mpas_ocean.GeoDataFrame.time_to_geodataframe('120km', True)
* failed 84.9±1ms n/a mpas_ocean.GeoDataFrame.time_to_geodataframe('480km', False)
* failed 5.74±0.2ms n/a mpas_ocean.GeoDataFrame.time_to_geodataframe('480km', True)
* failed 177±3ms n/a mpas_ocean.Gradient.time_gradient('120km')
* failed 12.5±0.3ms n/a mpas_ocean.Gradient.time_gradient('480km')
* failed 457k n/a mpas_ocean.Gradient.track_nbytes_gradient('120km')
* failed 28.7k n/a mpas_ocean.Gradient.track_nbytes_gradient('480km')
* failed 5.08M n/a mpas_ocean.Gradient.track_peakmem_gradient('120km')
* failed 328k n/a mpas_ocean.Gradient.track_peakmem_gradient('480km')
* failed 423M n/a mpas_ocean.GradientColdStartRss.peakmem_gradient('120km')
* failed 403M n/a mpas_ocean.GradientColdStartRss.peakmem_gradient('480km')
* failed 382±10μs n/a mpas_ocean.HoleEdgeIndices.time_construct_hole_edge_indices('120km')
* failed 201±10μs n/a mpas_ocean.HoleEdgeIndices.time_construct_hole_edge_indices('480km')
* failed 549±10μs n/a mpas_ocean.Integrate.time_integrate('120km')
* failed 492±20μs n/a mpas_ocean.Integrate.time_integrate('480km')
* failed 18.4M n/a mpas_ocean.Integrate.track_nbytes_integrate('120km')
* failed 1.2M n/a mpas_ocean.Integrate.track_nbytes_integrate('480km')
* failed 181±1ms n/a mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'exclude')
* failed 181±0.9ms n/a mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'include')
* failed 179±1ms n/a mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'split')
* failed 13.7±0.07ms n/a mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'exclude')
* failed 13.7±0.2ms n/a mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'include')
* failed 13.9±0.3ms n/a mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'split')
* failed 238±0.8ms n/a mpas_ocean.NeighborhoodBuild.time_build('120km', 1.0)
* failed 1.27±0s n/a mpas_ocean.NeighborhoodBuild.time_build('120km', 15.0)
* failed 491±1ms n/a mpas_ocean.NeighborhoodBuild.time_build('120km', 5.0)
* failed 13.0±0.1ms n/a mpas_ocean.NeighborhoodBuild.time_build('480km', 1.0)
* failed 24.6±0.06ms n/a mpas_ocean.NeighborhoodBuild.time_build('480km', 15.0)
* failed 16.1±0.08ms n/a mpas_ocean.NeighborhoodBuild.time_build('480km', 5.0)
* failed 233±0.4ms n/a mpas_ocean.NeighborhoodBuild.time_query_radius('120km', 1.0)
* failed 1.25±0s n/a mpas_ocean.NeighborhoodBuild.time_query_radius('120km', 15.0)
* failed 484±1ms n/a mpas_ocean.NeighborhoodBuild.time_query_radius('120km', 5.0)
* failed 12.6±0.04ms n/a mpas_ocean.NeighborhoodBuild.time_query_radius('480km', 1.0)
* failed 24.2±0.1ms n/a mpas_ocean.NeighborhoodBuild.time_query_radius('480km', 15.0)
* failed 15.6±0.04ms n/a mpas_ocean.NeighborhoodBuild.time_query_radius('480km', 5.0)
* failed 1.19 n/a mpas_ocean.NeighborhoodBuild.track_mean_neighbors('120km', 1.0)
* failed 612.76 n/a mpas_ocean.NeighborhoodBuild.track_mean_neighbors('120km', 15.0)
* failed 74.17 n/a mpas_ocean.NeighborhoodBuild.track_mean_neighbors('120km', 5.0)
* failed 1.0 n/a mpas_ocean.NeighborhoodBuild.track_mean_neighbors('480km', 1.0)
* failed 37.29 n/a mpas_ocean.NeighborhoodBuild.track_mean_neighbors('480km', 15.0)
* failed 6.57 n/a mpas_ocean.NeighborhoodBuild.track_mean_neighbors('480km', 5.0)
* failed 728k n/a mpas_ocean.NeighborhoodBuild.track_nbytes_neighbors('120km', 1.0)
* failed 141M n/a mpas_ocean.NeighborhoodBuild.track_nbytes_neighbors('120km', 15.0)
* failed 17.4M n/a mpas_ocean.NeighborhoodBuild.track_nbytes_neighbors('120km', 5.0)
* failed 43k n/a mpas_ocean.NeighborhoodBuild.track_nbytes_neighbors('480km', 1.0)
* failed 563k n/a mpas_ocean.NeighborhoodBuild.track_nbytes_neighbors('480km', 15.0)
* failed 123k n/a mpas_ocean.NeighborhoodBuild.track_nbytes_neighbors('480km', 5.0)
* failed 5.72M n/a mpas_ocean.NeighborhoodBuild.track_peakmem_build('120km', 1.0)
* failed 145M n/a mpas_ocean.NeighborhoodBuild.track_peakmem_build('120km', 15.0)
* failed 21.5M n/a mpas_ocean.NeighborhoodBuild.track_peakmem_build('120km', 5.0)
* failed 362k n/a mpas_ocean.NeighborhoodBuild.track_peakmem_build('480km', 1.0)
* failed 824k n/a mpas_ocean.NeighborhoodBuild.track_peakmem_build('480km', 15.0)
* failed 384k n/a mpas_ocean.NeighborhoodBuild.track_peakmem_build('480km', 5.0)
* failed 47.5±0.8ms n/a mpas_ocean.NeighborhoodDask.time_mean('120km', 'grid_chunks')
* failed 22.6±0.07ms n/a mpas_ocean.NeighborhoodDask.time_mean('120km', 'numpy')
* failed 44.1±0.5ms n/a mpas_ocean.NeighborhoodDask.time_mean('120km', 'time_chunks')
* failed 15.2±0.2ms n/a mpas_ocean.NeighborhoodDask.time_mean('480km', 'grid_chunks')
* failed 701±10μs n/a mpas_ocean.NeighborhoodDask.time_mean('480km', 'numpy')
* failed 12.0±0.2ms n/a mpas_ocean.NeighborhoodDask.time_mean('480km', 'time_chunks')
* failed 5.84M n/a mpas_ocean.NeighborhoodDask.track_peakmem_mean('120km', 'grid_chunks')
* failed 2.75M n/a mpas_ocean.NeighborhoodDask.track_peakmem_mean('120km', 'numpy')
* failed 5.69M n/a mpas_ocean.NeighborhoodDask.track_peakmem_mean('120km', 'time_chunks')
* failed 686k n/a mpas_ocean.NeighborhoodDask.track_peakmem_mean('480km', 'grid_chunks')
* failed 177k n/a mpas_ocean.NeighborhoodDask.track_peakmem_mean('480km', 'numpy')
* failed 545k n/a mpas_ocean.NeighborhoodDask.track_peakmem_mean('480km', 'time_chunks')
* failed 12.3±0.04s n/a mpas_ocean.NeighborhoodReduce.time_dataset_reduce('120km', 'mean')
* failed 13.1±0.06s n/a mpas_ocean.NeighborhoodReduce.time_dataset_reduce('120km', 'median')
* failed 227±0.2ms n/a mpas_ocean.NeighborhoodReduce.time_dataset_reduce('480km', 'mean')
* failed 233±0.5ms n/a mpas_ocean.NeighborhoodReduce.time_dataset_reduce('480km', 'median')
* failed 1.30±0s n/a mpas_ocean.NeighborhoodReduce.time_neighborhood_reduce('120km', 'mean')
* failed 1.50±0s n/a mpas_ocean.NeighborhoodReduce.time_neighborhood_reduce('120km', 'median')
* failed 25.3±0.3ms n/a mpas_ocean.NeighborhoodReduce.time_neighborhood_reduce('480km', 'mean')
* failed 26.6±0.1ms n/a mpas_ocean.NeighborhoodReduce.time_neighborhood_reduce('480km', 'median')
* failed 40.2±0.04ms n/a mpas_ocean.NeighborhoodReduce.time_reduce('120km', 'mean')
* failed 233±0.2ms n/a mpas_ocean.NeighborhoodReduce.time_reduce('120km', 'median')
* failed 563±30μs n/a mpas_ocean.NeighborhoodReduce.time_reduce('480km', 'mean')
* failed 2.08±0.05ms n/a mpas_ocean.NeighborhoodReduce.time_reduce('480km', 'median')
* failed 239k n/a mpas_ocean.NeighborhoodReduce.track_peakmem_reduce('120km', 'mean')
* failed 245k n/a mpas_ocean.NeighborhoodReduce.track_peakmem_reduce('120km', 'median')
* failed 19.7k n/a mpas_ocean.NeighborhoodReduce.track_peakmem_reduce('480km', 'mean')
* failed 20.2k n/a mpas_ocean.NeighborhoodReduce.track_peakmem_reduce('480km', 'median')
* failed 393±8μs n/a mpas_ocean.PointInPolygon.time_face_search_lonlat('120km')
* failed 431±8μs n/a mpas_ocean.PointInPolygon.time_face_search_lonlat('480km')
* failed 375±10μs n/a mpas_ocean.PointInPolygon.time_face_search_xyz('120km')
* failed 378±10μs n/a mpas_ocean.PointInPolygon.time_face_search_xyz('480km')
* failed 244±2ms n/a mpas_ocean.RemapDownsample.time_bilinear_remapping
* failed 293±6ms n/a mpas_ocean.RemapDownsample.time_inverse_distance_weighted_remapping
* failed 15.9±0.2ms n/a mpas_ocean.RemapDownsample.time_nearest_neighbor_remapping
* failed 1.39±0.01s n/a mpas_ocean.RemapUpsample.time_bilinear_remapping
* failed 36.5±1ms n/a mpas_ocean.RemapUpsample.time_inverse_distance_weighted_remapping
* failed 12.8±0.2ms n/a mpas_ocean.RemapUpsample.time_nearest_neighbor_remapping
* failed 9.36±0.2ms n/a mpas_ocean.ZonalAverage.time_zonal_average('120km')
* failed 5.40±2ms n/a mpas_ocean.ZonalAverage.time_zonal_average('480km')
* failed 429M n/a mpas_ocean.ZonalAveragePeakMem.peakmem_zonal_average('120km')
* failed 413M n/a mpas_ocean.ZonalAveragePeakMem.peakmem_zonal_average('480km')

Benchmarks that have stayed the same:

Change Before [9d5bcdb] After [3cb3309] Ratio Benchmark (Parameter)
215±10ms 201±3ms 0.94 bench_connectivity.Connectivity.time_edge_face('120km')
12.4±0.07ms 12.4±0.3ms 1.00 bench_connectivity.Connectivity.time_edge_face('480km')
222±10ms 200±3ms ~0.90 bench_connectivity.Connectivity.time_edge_node('120km')
11.1±0.07ms 11.5±0.05ms 1.03 bench_connectivity.Connectivity.time_edge_node('480km')
202±3ms 200±2ms 0.99 bench_connectivity.Connectivity.time_face_edge('120km')
11.5±0.07ms 11.6±0.06ms 1.01 bench_connectivity.Connectivity.time_face_edge('480km')
899±7ms 898±10ms 1.00 bench_connectivity.Connectivity.time_face_face('120km')
56.9±0.6ms 58.7±0.7ms 1.03 bench_connectivity.Connectivity.time_face_face('480km')
69.3±0.7μs 71.7±1μs 1.04 bench_connectivity.Connectivity.time_face_node('120km')
66.2±3μs 68.9±1μs 1.04 bench_connectivity.Connectivity.time_face_node('480km')
428±10μs 423±7μs 0.99 bench_connectivity.Connectivity.time_n_nodes_per_face('120km')
363±10μs 368±10μs 1.01 bench_connectivity.Connectivity.time_n_nodes_per_face('480km')
200±0.2ms 204±3ms 1.02 bench_connectivity.Connectivity.time_node_edge('120km')
11.4±0.05ms 11.8±0.1ms 1.04 bench_connectivity.Connectivity.time_node_edge('480km')
5.22±0.07ms 5.57±0.6ms 1.07 bench_connectivity.Connectivity.time_node_face('480km')
8.63±0.1ms 8.65±0.07ms 1.00 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
2.77±0.04ms 2.79±0.04ms 1.01 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
10.4±10s 10.4±10ms ~0.00 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
2.18±0.02ms 2.22±0.03ms 1.02 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
57.3k 57.3k 1.00 face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
12.3k 12.3k 1.00 face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
123k 123k 1.00 face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
128 128 1.00 face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
1.27M 1.27M 1.00 face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
50.1k 50.1k 1.00 face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
1.48M 1.48M 1.00 face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
712 712 1.00 face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
1.98M 1.96M 0.99 face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
1.98M 1.97M 0.99 face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
2.15M 2.13M 0.99 face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
38.3k 38.2k 1.00 face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
1.24±0.05μs 1.27±0.06μs 1.02 geometry_kernels.AccucrossKernels.time_accucross
2.74±0.03μs 2.78±0.07μs 1.01 geometry_kernels.AccucrossKernels.time_accucross_pair
456±10ns 466±10ns 1.02 geometry_kernels.EFTPrimitives.time_acc_sqrt_re
446±20ns 431±20ns 0.97 geometry_kernels.EFTPrimitives.time_diff_of_products
386±20ns 395±10ns 1.02 geometry_kernels.EFTPrimitives.time_two_prod
386±10ns 391±20ns 1.01 geometry_kernels.EFTPrimitives.time_two_sum
1.65±0.03μs 1.63±0.08μs 0.99 geometry_kernels.GCAConstLatIntersection.time_accux_constlat_kernel
1.14±0.04μs 1.17±0.02μs 1.03 geometry_kernels.GCAConstLatIntersection.time_gca_const_lat_intersection
2.03±0.05μs 2.03±0.05μs 1.00 geometry_kernels.GCAConstLatIntersection.time_try_gca_const_lat_intersection
1.76±0.05μs 1.72±0.02μs 0.98 geometry_kernels.GCAGCAIntersection.time_accux_gca_kernel
1.42±0.01μs 1.46±0.05μs 1.03 geometry_kernels.GCAGCAIntersection.time_gca_gca_intersection
2.28±0.05μs 2.29±0.06μs 1.00 geometry_kernels.GCAGCAIntersection.time_try_gca_gca_intersection
53.2±0.7μs 54.0±0.6μs 1.02 geometry_kernels.OrientPredicates.time_on_minor_arc
1.08±0.03μs 1.16±0.03μs 1.07 geometry_kernels.OrientPredicates.time_orient3d_on_sphere
2.72±0.1ms 2.62±0.01ms 0.96 geometry_samebody.SameBodyConstLat.time_accux_dispatch
1.17±0.01ms 1.17±0ms 1.00 geometry_samebody.SameBodyConstLat.time_accux_kernel
1.73±0.01ms 1.73±0.02ms 1.00 geometry_samebody.SameBodyConstLat.time_fp64_dispatch
147±1μs 159±10μs 1.08 geometry_samebody.SameBodyConstLat.time_fp64_kernel
32.3±0.05ms 32.5±0.2ms 1.01 geometry_samebody_gcagca.SameBodyGcaGca.time_accux_dispatch
10.3±0.01ms 10.2±0.02ms 1.00 geometry_samebody_gcagca.SameBodyGcaGca.time_accux_kernel
26.5±0.02ms 26.5±0.05ms 1.00 geometry_samebody_gcagca.SameBodyGcaGca.time_fp64_dispatch
4.89±0.02ms 4.92±0.01ms 1.01 geometry_samebody_gcagca.SameBodyGcaGca.time_fp64_kernel
7.45±0.7ms 6.86±0.08ms 0.92 quad_hexagon.QuadHexagon.time_open_dataset
6.10±0.5ms 5.78±0.03ms 0.95 quad_hexagon.QuadHexagon.time_open_grid
408 408 1.00 quad_hexagon.QuadHexagon.track_nbytes_open_dataset
392 392 1.00 quad_hexagon.QuadHexagon.track_nbytes_open_grid
73.5k 73.8k 1.00 quad_hexagon.QuadHexagon.track_peakmem_open_dataset
73k 73k 1.00 quad_hexagon.QuadHexagon.track_peakmem_open_grid

Benchmarks that have got worse:

Change Before [9d5bcdb] After [3cb3309] Ratio Benchmark (Parameter)
+ 81.3±2ms 92.5±6ms 1.14 bench_connectivity.Connectivity.time_node_face('120km')
+ 336M 409M 1.22 face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
+ 365M 440M 1.21 face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
+ 337M 411M 1.22 face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
+ 337M 410M 1.22 face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
+ 873±50ms 12.5±0.02s 14.33 import.Imports.timeraw_import_uxarray
+ 293M 367M 1.25 import.Imports.track_peakmem_import_uxarray

@cmdupuis3

Copy link
Copy Markdown
Collaborator

pre-commit.ci autofix

@cmdupuis3 cmdupuis3 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving, but a good amount now has my fingers on it, so I'd like to defer for another approval.

@erogluorhan ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new feature New user-facing functionality run-benchmark Run ASV benchmark workflow

Projects

Status: 👀 In review

Development

Successfully merging this pull request may close these issues.

Apply a neighborhood filter with radius r to all elements of UxDataArray

6 participants