Skip to content

Apply function to points within circular neighborhood - #941

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

Apply function to points within circular neighborhood #941
ahijevyc wants to merge 57 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

@ahijevyc @rajeeja I have a draft PR sitting in the fork, I'll keep working on it there. I'm not totally satisfied with the level of complexity. Using a functional approach inside vectorized gufuncs makes it hard to get performance and good readability at once.

rajeeja added 2 commits August 6, 2026 02:32
- numba guvectorize kernels for compiled parallel reductions
- Neighborhoods object: one BallTree query reused across variables/reductions
- Named reduction API: func='mean', func='percentile', q=90, etc.
@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 stayed the same:

Change Before [9d5bcdb] After [ff467ec] Ratio Benchmark (Parameter)
201±1ms 202±0.7ms 1.01 bench_connectivity.Connectivity.time_edge_face('120km')
12.4±0.05ms 12.5±0.2ms 1.00 bench_connectivity.Connectivity.time_edge_face('480km')
199±0.6ms 202±1ms 1.02 bench_connectivity.Connectivity.time_edge_node('120km')
11.2±0.1ms 11.3±0.08ms 1.01 bench_connectivity.Connectivity.time_edge_node('480km')
203±3ms 201±0.7ms 0.99 bench_connectivity.Connectivity.time_face_edge('120km')
11.6±0.1ms 12.1±0.7ms 1.05 bench_connectivity.Connectivity.time_face_edge('480km')
917±7ms 916±10ms 1.00 bench_connectivity.Connectivity.time_face_face('120km')
57.3±0.3ms 58.5±1ms 1.02 bench_connectivity.Connectivity.time_face_face('480km')
75.8±5μs 74.5±2μs 0.98 bench_connectivity.Connectivity.time_face_node('120km')
68.9±2μs 68.7±3μs 1.00 bench_connectivity.Connectivity.time_face_node('480km')
429±10μs 413±4μs 0.96 bench_connectivity.Connectivity.time_n_nodes_per_face('120km')
361±10μs 361±8μs 1.00 bench_connectivity.Connectivity.time_n_nodes_per_face('480km')
202±2ms 202±2ms 1.00 bench_connectivity.Connectivity.time_node_edge('120km')
11.7±0.1ms 11.7±0.08ms 1.00 bench_connectivity.Connectivity.time_node_edge('480km')
88.6±3ms 89.9±4ms 1.02 bench_connectivity.Connectivity.time_node_face('120km')
5.23±0.04ms 5.24±0.01ms 1.00 bench_connectivity.Connectivity.time_node_face('480km')
8.70±0.1ms 8.72±0.1ms 1.00 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
2.80±0.06ms 2.76±0.04ms 0.99 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
10.6±10s 10.3±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.23±0.04ms 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.3k 1.00 face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
1.21±0.04μs 1.28±0.04μs 1.05 geometry_kernels.AccucrossKernels.time_accucross
3.06±0.3μs 2.75±0.05μs ~0.90 geometry_kernels.AccucrossKernels.time_accucross_pair
446±20ns 472±40ns 1.06 geometry_kernels.EFTPrimitives.time_acc_sqrt_re
461±100ns 426±30ns 0.92 geometry_kernels.EFTPrimitives.time_diff_of_products
391±5ns 406±10ns 1.04 geometry_kernels.EFTPrimitives.time_two_prod
391±3ns 401±20ns 1.03 geometry_kernels.EFTPrimitives.time_two_sum
1.59±0.04μs 1.53±0.06μs 0.97 geometry_kernels.GCAConstLatIntersection.time_accux_constlat_kernel
1.15±0.02μs 1.19±0.02μs 1.03 geometry_kernels.GCAConstLatIntersection.time_gca_const_lat_intersection
1.95±0.05μs 2.01±0.08μs 1.03 geometry_kernels.GCAConstLatIntersection.time_try_gca_const_lat_intersection
1.71±0.05μs 1.70±0.02μs 0.99 geometry_kernels.GCAGCAIntersection.time_accux_gca_kernel
1.41±0.06μs 1.43±0.04μs 1.01 geometry_kernels.GCAGCAIntersection.time_gca_gca_intersection
2.18±0.05μs 2.26±0.04μs 1.03 geometry_kernels.GCAGCAIntersection.time_try_gca_gca_intersection
54.0±0.5μs 54.7±4μs 1.01 geometry_kernels.OrientPredicates.time_on_minor_arc
1.12±0.03μs 1.15±0.03μs 1.03 geometry_kernels.OrientPredicates.time_orient3d_on_sphere
2.89±0.3ms 2.61±0ms ~0.90 geometry_samebody.SameBodyConstLat.time_accux_dispatch
1.17±0ms 1.17±0ms 1.00 geometry_samebody.SameBodyConstLat.time_accux_kernel
1.73±0.01ms 1.73±0.01ms 1.00 geometry_samebody.SameBodyConstLat.time_fp64_dispatch
147±1μs 147±3μs 1.00 geometry_samebody.SameBodyConstLat.time_fp64_kernel
32.6±0.1ms 32.1±0.04ms 0.98 geometry_samebody_gcagca.SameBodyGcaGca.time_accux_dispatch
10.3±0.02ms 10.9±0.4ms 1.06 geometry_samebody_gcagca.SameBodyGcaGca.time_accux_kernel
26.5±0.04ms 26.4±0.01ms 1.00 geometry_samebody_gcagca.SameBodyGcaGca.time_fp64_dispatch
4.87±0.02ms 4.88±0.04ms 1.00 geometry_samebody_gcagca.SameBodyGcaGca.time_fp64_kernel
837±20ms 12.2±0.2s ~14.56 import.Imports.timeraw_import_uxarray
2.83±0.06ms 2.84±0.03ms 1.00 mpas_ocean.CheckNorm.time_check_norm('120km')
2.34±0.04ms 2.27±0.01ms 0.97 mpas_ocean.CheckNorm.time_check_norm('480km')
856±8ms 878±20ms 1.03 mpas_ocean.ConnectivityConstruction.time_face_face_connectivity('120km')
55.5±0.9ms 55.8±0.5ms 1.01 mpas_ocean.ConnectivityConstruction.time_face_face_connectivity('480km')
681±9μs 698±10μs 1.02 mpas_ocean.ConnectivityConstruction.time_n_nodes_per_face('120km')
599±20μs 620±10μs 1.03 mpas_ocean.ConnectivityConstruction.time_n_nodes_per_face('480km')
5.53±0.05ms 5.58±0.04ms 1.01 mpas_ocean.ConstructFaceLatLon.time_cartesian_averaging('120km')
4.02±0.04ms 4.06±0.02ms 1.01 mpas_ocean.ConstructFaceLatLon.time_cartesian_averaging('480km')
99.7±0.4ms 100±0.2ms 1.01 mpas_ocean.ConstructFaceLatLon.time_welzl('120km')
10.8±0.1ms 10.8±0.2ms 1.00 mpas_ocean.ConstructFaceLatLon.time_welzl('480km')
18.2±0.03ms 18.2±0.02ms 1.00 mpas_ocean.ConstructTreeStructures.time_ball_tree('120km')
1.07±0.04ms 1.09±0.01ms 1.02 mpas_ocean.ConstructTreeStructures.time_ball_tree('480km')
10.6±0.05ms 10.6±0.02ms 1.00 mpas_ocean.ConstructTreeStructures.time_kd_tree('120km')
715±40μs 748±30μs 1.05 mpas_ocean.ConstructTreeStructures.time_kd_tree('480km')
588±8ms 608±20ms 1.03 mpas_ocean.CrossSections.time_const_lat('120km', 1)
302±6ms 311±5ms 1.03 mpas_ocean.CrossSections.time_const_lat('120km', 2)
158±4ms 156±3ms 0.99 mpas_ocean.CrossSections.time_const_lat('120km', 4)
533±3ms 535±6ms 1.01 mpas_ocean.CrossSections.time_const_lat('480km', 1)
267±0.8ms 277±2ms 1.04 mpas_ocean.CrossSections.time_const_lat('480km', 2)
142±2ms 142±3ms 1.00 mpas_ocean.CrossSections.time_const_lat('480km', 4)
24.7±0.2ms 25.4±0.9ms 1.03 mpas_ocean.DualMesh.time_dual_mesh_construction('120km')
3.30±0.1ms 3.25±0.09ms 0.98 mpas_ocean.DualMesh.time_dual_mesh_construction('480km')
61.9±0.5ms 62.2±0.9ms 1.01 mpas_ocean.FaceAreas.time_face_areas('120km')
7.85±5000ms 7.96±5ms 1.01 mpas_ocean.FaceAreas.time_face_areas('480km')
229k 229k 1.00 mpas_ocean.FaceAreas.track_nbytes_face_areas('120km')
14.3k 14.3k 1.00 mpas_ocean.FaceAreas.track_nbytes_face_areas('480km')
2.12M 2.12M 1.00 mpas_ocean.FaceAreas.track_peakmem_face_areas('120km')
823k 817k 0.99 mpas_ocean.FaceAreas.track_peakmem_face_areas('480km')
950±10ms 956±4ms 1.01 mpas_ocean.GeoDataFrame.time_to_geodataframe('120km', False)
53.8±0.2ms 52.6±0.4ms 0.98 mpas_ocean.GeoDataFrame.time_to_geodataframe('120km', True)
86.2±0.8ms 86.0±1ms 1.00 mpas_ocean.GeoDataFrame.time_to_geodataframe('480km', False)
5.84±0.2ms 5.89±0.2ms 1.01 mpas_ocean.GeoDataFrame.time_to_geodataframe('480km', True)
176±2ms 178±6ms 1.01 mpas_ocean.Gradient.time_gradient('120km')
12.5±0.3ms 12.3±0.09ms 0.98 mpas_ocean.Gradient.time_gradient('480km')
457k 457k 1.00 mpas_ocean.Gradient.track_nbytes_gradient('120km')
28.7k 28.7k 1.00 mpas_ocean.Gradient.track_nbytes_gradient('480km')
5.08M 5.08M 1.00 mpas_ocean.Gradient.track_peakmem_gradient('120km')
328k 328k 1.00 mpas_ocean.Gradient.track_peakmem_gradient('480km')
400±10μs 367±20μs 0.92 mpas_ocean.HoleEdgeIndices.time_construct_hole_edge_indices('120km')
214±7μs 201±7μs 0.94 mpas_ocean.HoleEdgeIndices.time_construct_hole_edge_indices('480km')
600±40μs 590±20μs 0.98 mpas_ocean.Integrate.time_integrate('120km')
518±40μs 483±20μs 0.93 mpas_ocean.Integrate.time_integrate('480km')
18.4M 18.4M 1.00 mpas_ocean.Integrate.track_nbytes_integrate('120km')
1.2M 1.2M 1.00 mpas_ocean.Integrate.track_nbytes_integrate('480km')
183±3ms 182±2ms 1.00 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'exclude')
182±1ms 181±0.9ms 1.00 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'include')
181±1ms 182±0.3ms 1.00 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'split')
13.8±0.4ms 13.6±0.3ms 0.98 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'exclude')
13.7±0.6ms 13.5±0.05ms 0.99 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'include')
13.7±0.5ms 13.6±0.3ms 0.99 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'split')
427±30μs 403±10μs 0.94 mpas_ocean.PointInPolygon.time_face_search_lonlat('120km')
405±20μs 390±8μs 0.96 mpas_ocean.PointInPolygon.time_face_search_lonlat('480km')
395±30μs 375±10μs 0.95 mpas_ocean.PointInPolygon.time_face_search_xyz('120km')
360±10μs 373±9μs 1.04 mpas_ocean.PointInPolygon.time_face_search_xyz('480km')
234±1ms 233±2ms 1.00 mpas_ocean.RemapDownsample.time_bilinear_remapping
294±7ms 297±5ms 1.01 mpas_ocean.RemapDownsample.time_inverse_distance_weighted_remapping
15.7±0.1ms 15.8±0.2ms 1.01 mpas_ocean.RemapDownsample.time_nearest_neighbor_remapping
1.39±0.02s 1.38±0s 1.00 mpas_ocean.RemapUpsample.time_bilinear_remapping
36.7±0.9ms 35.3±2ms 0.96 mpas_ocean.RemapUpsample.time_inverse_distance_weighted_remapping
12.4±0.3ms 12.9±0.4ms 1.04 mpas_ocean.RemapUpsample.time_nearest_neighbor_remapping
9.06±0.2ms 9.05±0.1ms 1.00 mpas_ocean.ZonalAverage.time_zonal_average('120km')
4.91±0.2ms 4.88±0.3ms 0.99 mpas_ocean.ZonalAverage.time_zonal_average('480km')
6.92±0.1ms 6.79±0.07ms 0.98 quad_hexagon.QuadHexagon.time_open_dataset
6.06±0.3ms 5.80±0.07ms 0.96 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.1k 1.00 quad_hexagon.QuadHexagon.track_peakmem_open_dataset
72.8k 72.2k 0.99 quad_hexagon.QuadHexagon.track_peakmem_open_grid

Benchmarks that have got worse:

Change Before [9d5bcdb] After [ff467ec] Ratio Benchmark (Parameter)
+ 336M 408M 1.22 face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
+ 367M 438M 1.19 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'))
+ 293M 366M 1.25 import.Imports.track_peakmem_import_uxarray
+ 356M 429M 1.21 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 1)
+ 356M 429M 1.21 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 2)
+ 355M 429M 1.21 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 4)
+ 339M 412M 1.22 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 1)
+ 339M 412M 1.22 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 2)
+ 339M 412M 1.22 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 4)
+ 352M 428M 1.22 mpas_ocean.GradientColdStartRss.peakmem_gradient('120km')
+ 331M 403M 1.22 mpas_ocean.GradientColdStartRss.peakmem_gradient('480km')
+ 357M 430M 1.2 mpas_ocean.ZonalAveragePeakMem.peakmem_zonal_average('120km')
+ 341M 413M 1.21 mpas_ocean.ZonalAveragePeakMem.peakmem_zonal_average('480km')

@cmdupuis3

Copy link
Copy Markdown
Collaborator

pre-commit.ci autofix

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