Skip to content

Apply function to points within circular neighborhood - #941

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

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

Conversation

@ahijevyc

@ahijevycahijevyc 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

fromfunctoolsimportpartialimportnumpyasnpimportuxarraygrid_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 domainlon_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.percentileuxda_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/

@ahijevycahijevyc added the new feature New feature or request label Sep 9, 2024
@ahijevycahijevyc self-assigned this Sep 9, 2024
@ahijevycahijevyc mentioned this pull request Sep 9, 2024
14 tasks
Comment threaduxarray/core/dataarray.py Outdated
philipc2
philipc2 previously requested changes Sep 9, 2024

@philipc2philipc2 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 threaduxarray/core/dataarray.py Outdated
Comment threaduxarray/core/dataarray.py
Comment threaduxarray/core/dataset.py Outdated
Comment threaduxarray/core/dataarray.py Outdated
Kept neighborhood and dual additions
@philipc2

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.25uxds['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 maximumuxds['node_centered_var'].groupby(kernel=ux.FaceNode()).max()
# this is equivalent to the following in the current releaseuxds['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.25uxds['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 maximumuxds['node_centered_var'].groupby(kernel=ux.FaceNode()).max()
# this is equivalent to the following in the current releaseuxds['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!

@philipc2philipc2 mentioned this pull request May 14, 2025
9 tasks
@erogluorhan
erogluorhan removed the request for review from philipc2August 4, 2026 19:37

@erogluorhanerogluorhan 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.

Overall this looks great, but I have a couple concerns in the low-level implementation for which @cmdupuis3 could be helpful as well (added as a reviewer). See below

func: Callable = np.mean,
r: float = 1.0,
) -> UxDataArray:
"""Apply a neighborhood filter, replacing the value at each grid

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.

Might use a little rephrasing: "replace" might be misleading here since function returns a new data array and actually doesn't change the original data?

Comment threaduxarray/grid/neighbors.py Outdated

# Apply func along the last (grid) axis only, so any extra leading
# dimensions (e.g. time) are preserved rather than being collapsed.
for i, idx in enumerate(neighbor_indices):

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.

This will iterate through every single geometric element, e.g. faces, right? It looks very costly for km-scale data. Was there any consideration of vectorization for this?

cc: @cmdupuis3 for your thoughts

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.

Something you could do is make a boolean mask out of neighbor_indices as keys, then mask it to neighbor_masked, then you could delete the whole conditional/try/except block and iterate over only the valid entries. If you really need the full-scale destination_data, you can use the mask to reshape the output.

Comment threaduxarray/grid/neighbors.py Outdated
for i, idx in enumerate(neighbor_indices):
if len(idx):
try:
destination_data[..., i] = func(data[..., idx], axis=-1)

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.

From what I can tell on this line, every single iteration will trigger rebuilding and executing the dask task graph from scratch because this line returns a lazy dask scalar when data is dask-backed and assigning that into a numpy array index.

I believe this could be avoided if uxdataarray.py/neighborhood_filter did an upfront .compute() for data (and actually that function already claims "lazy (dask-backed) data is computed eagerly and the result is always NumPy-backed").

cc: @cmdupuis3 you dealt with bundled .compute()s through xarray API in #1560, it might not directly related to here, but you may still have some thoughts.

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.

Looks like it. Just at a glance, this block seems like it's basically a ufunc implementation.

Comment threaduxarray/core/dataarray.py Outdated
@cmdupuis3

Copy link
Copy Markdown
Collaborator

I'm working on a chunk-friendly optimized version based on gufuncs, I'll add my own PR off this one and y'all can merge it. The one caveat is that we'd have to hand-enumerate the specific reductions we want to be able to run optimally, I think otherwise we can offer a fallback option but the performance would suffer. I don't think that would happen that often in practice though.

@rajeeja

Copy link
Copy Markdown
Contributor

I'm working on a chunk-friendly optimized version based on gufuncs, I'll add my own PR off this one and y'all can merge it. The one caveat is that we'd have to hand-enumerate the specific reductions we want to be able to run optimally, I think otherwise we can offer a fallback option but the performance would suffer. I don't think that would happen that often in practice though.

Makes sense, thanks for finding this out, that code would lose out-of-core/parallel benefits; most of the times this would be one of the steps in the bigger pipeline and if others are nice dask ops that would be a good win. I'll keep an eye on the followup to this.

cmdupuis3and others added 3 commits August 5, 2026 13:40
Follows the gufunc work with the API changes it made possible.
Kernel factory. The buffered kernels (median and friends) share one gather
loop, with the reduction supplied as a numba-compilable callable. numba
supports the NumPy reductions in nopython mode, so np.median is used directly
rather than reimplemented -- it partitions instead of fully sorting, making it
1.6x faster than the hand-written sort it replaces, as well as shorter. Adds
ptp, std, var, quantile and percentile; the parameterized ones carry their
argument as a scalar core dimension so q and ddof vary per call without
recompiling.
Named reductions. `func` now takes a name ("mean", "quantile", ...) with
parameters as ordinary keyword arguments. The previous `axis=-1` contract
could not be jitted -- numba rejects the axis kwarg on mean/median/percentile
-- and dispatching on a function object cannot see through functools.partial,
so `partial(np.percentile, q=90)`, the example in our own docstring, could
never reach a kernel. A name always can. Callables remain accepted on the old
contract as an escape hatch, and np.mean and friends still map to their
kernels so existing code keeps the fast path.
Neighborhoods. Finding the neighbors costs more than reducing over them --
after the kernel work it is ~95% of a call -- and it was repeated on every
call, including once per variable in the dataset path. Grid.neighborhoods()
does the search once and returns an object to reduce over repeatedly: 3.5x for
four reductions at one radius on a 196k-face grid, and a dataset filter now
costs one query per grid location rather than one per variable (8 variables:
1.74s -> 0.23s).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@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.

- 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

@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

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.

@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.

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

github-actionsBot commented Aug 18, 2026

Copy link
Copy Markdown

ASV Benchmarking

Benchmark Comparison Results

Benchmarks that have improved:

ChangeBefore [fda71bb]After [4ca774e]RatioBenchmark (Parameter)
-163±10ms138±0.3ms0.85bench_connectivity.Connectivity.time_node_edge('120km')
-2.00±0.03ms1.77±0.01ms0.89geometry_samebody.SameBodyConstLat.time_accux_dispatch

Benchmarks that have stayed the same:

ChangeBefore [fda71bb]After [4ca774e]RatioBenchmark (Parameter)
149±4ms145±5ms0.97bench_connectivity.Connectivity.time_edge_face('120km')
8.15±0.04ms8.25±0.03ms1.01bench_connectivity.Connectivity.time_edge_face('480km')
167±30ms138±1ms~0.83bench_connectivity.Connectivity.time_edge_node('120km')
7.57±0.02ms7.49±0.06ms0.99bench_connectivity.Connectivity.time_edge_node('480km')
138±0.7ms145±7ms1.05bench_connectivity.Connectivity.time_face_edge('120km')
8.11±0.5ms8.95±0.7ms~1.10bench_connectivity.Connectivity.time_face_edge('480km')
574±10ms560±2ms0.98bench_connectivity.Connectivity.time_face_face('120km')
34.8±0.08ms37.7±2ms1.08bench_connectivity.Connectivity.time_face_face('480km')
44.8±1μs44.1±0.9μs0.98bench_connectivity.Connectivity.time_face_node('120km')
47.4±4μs44.0±1μs0.93bench_connectivity.Connectivity.time_face_node('480km')
310±6μs310±7μs1.00bench_connectivity.Connectivity.time_n_nodes_per_face('120km')
228±10μs228±3μs1.00bench_connectivity.Connectivity.time_n_nodes_per_face('480km')
7.74±0.07ms7.76±0.02ms1.00bench_connectivity.Connectivity.time_node_edge('480km')
43.4±0.2ms44.6±0.5ms1.03bench_connectivity.Connectivity.time_node_face('120km')
3.20±0.1ms2.91±0.02ms0.91bench_connectivity.Connectivity.time_node_face('480km')
5.88±0.02ms5.88±0.05ms1.00face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
1.75±0.01ms1.75±0.02ms1.00face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
6.28±6s8.35±9ms~0.00face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
1.35±0.05ms1.26±0.02ms0.94face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
57.3k57.3k1.00face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
12.3k12.3k1.00face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
123k123k1.00face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
1281281.00face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
1.27M1.27M1.00face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
50.1k50.1k1.00face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
1.48M1.48M1.00face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
7127121.00face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
1.95M1.93M0.99face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
1.95M1.94M0.99face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
2.12M2.1M0.99face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
38.3k38.3k1.00face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
777±30ns785±30ns1.01geometry_kernels.AccucrossKernels.time_accucross
1.72±0.03μs1.80±0.06μs1.05geometry_kernels.AccucrossKernels.time_accucross_pair
296±30ns285±20ns0.96geometry_kernels.EFTPrimitives.time_acc_sqrt_re
253±9ns264±20ns1.05geometry_kernels.EFTPrimitives.time_diff_of_products
242±10ns296±20ns~1.23geometry_kernels.EFTPrimitives.time_two_prod
256±30ns248±6ns0.97geometry_kernels.EFTPrimitives.time_two_sum
1.08±0.02μs1.01±0.04μs0.94geometry_kernels.GCAConstLatIntersection.time_accux_constlat_kernel
750±30ns747±20ns1.00geometry_kernels.GCAConstLatIntersection.time_gca_const_lat_intersection
1.34±0.03μs1.41±0.1μs1.05geometry_kernels.GCAConstLatIntersection.time_try_gca_const_lat_intersection
1.15±0.07μs1.07±0.04μs0.93geometry_kernels.GCAGCAIntersection.time_accux_gca_kernel
998±10ns1.07±0.03μs1.07geometry_kernels.GCAGCAIntersection.time_gca_gca_intersection
1.53±0.05μs1.53±0.07μs1.00geometry_kernels.GCAGCAIntersection.time_try_gca_gca_intersection
36.5±0.9μs36.5±0.5μs1.00geometry_kernels.OrientPredicates.time_on_minor_arc
777±40ns839±60ns1.08geometry_kernels.OrientPredicates.time_orient3d_on_sphere
578±9μs563±7μs0.97geometry_samebody.SameBodyConstLat.time_accux_kernel
1.19±0.01ms1.20±0.02ms1.01geometry_samebody.SameBodyConstLat.time_fp64_dispatch
164±40μs118±20μs~0.72geometry_samebody.SameBodyConstLat.time_fp64_kernel
29.9±1ms27.4±0.2ms0.92geometry_samebody_gcagca.SameBodyGcaGca.time_accux_dispatch
6.37±0.02ms6.52±0.09ms1.02geometry_samebody_gcagca.SameBodyGcaGca.time_accux_kernel
23.9±0.1ms25.4±0.7ms1.06geometry_samebody_gcagca.SameBodyGcaGca.time_fp64_dispatch
4.05±0.2ms3.81±0.04ms0.94geometry_samebody_gcagca.SameBodyGcaGca.time_fp64_kernel
594±40ms7.11±0.2s~11.96import.Imports.timeraw_import_uxarray
2.12±0.02ms2.18±0.01ms1.03mpas_ocean.CheckNorm.time_check_norm('120km')
1.45±0.01ms1.49±0.1ms1.02mpas_ocean.CheckNorm.time_check_norm('480km')
507±0.8ms507±2ms1.00mpas_ocean.ConnectivityConstruction.time_face_face_connectivity('120km')
32.4±0.3ms35.0±3ms1.08mpas_ocean.ConnectivityConstruction.time_face_face_connectivity('480km')
469±20μs468±10μs1.00mpas_ocean.ConnectivityConstruction.time_n_nodes_per_face('120km')
378±1μs399±10μs1.06mpas_ocean.ConnectivityConstruction.time_n_nodes_per_face('480km')
3.25±0.04ms3.26±0.02ms1.00mpas_ocean.ConstructFaceLatLon.time_cartesian_averaging('120km')
2.55±0.01ms2.54±0.01ms1.00mpas_ocean.ConstructFaceLatLon.time_cartesian_averaging('480km')
2.35±0.1s2.36±0s1.00mpas_ocean.ConstructFaceLatLon.time_welzl('120km')
149±0.9ms148±0.5ms0.99mpas_ocean.ConstructFaceLatLon.time_welzl('480km')
9.09±0.02ms9.11±0.01ms1.00mpas_ocean.ConstructTreeStructures.time_ball_tree('120km')
642±30μs613±10μs0.95mpas_ocean.ConstructTreeStructures.time_ball_tree('480km')
6.81±0.02ms6.80±0.01ms1.00mpas_ocean.ConstructTreeStructures.time_kd_tree('120km')
480±5μs474±10μs0.99mpas_ocean.ConstructTreeStructures.time_kd_tree('480km')
378±1ms385±2ms1.02mpas_ocean.CrossSections.time_const_lat('120km', 1)
192±2ms193±2ms1.01mpas_ocean.CrossSections.time_const_lat('120km', 2)
98.2±0.2ms98.1±0.7ms1.00mpas_ocean.CrossSections.time_const_lat('120km', 4)
335±3ms339±3ms1.01mpas_ocean.CrossSections.time_const_lat('480km', 1)
168±0.4ms170±1ms1.01mpas_ocean.CrossSections.time_const_lat('480km', 2)
91.3±5ms86.9±0.4ms0.95mpas_ocean.CrossSections.time_const_lat('480km', 4)
18.3±0.02ms18.3±0.08ms1.00mpas_ocean.DualMesh.time_dual_mesh_construction('120km')
2.02±0.07ms1.99±0.09ms0.99mpas_ocean.DualMesh.time_dual_mesh_construction('480km')
59.4±1ms58.7±0.4ms0.99mpas_ocean.FaceAreas.time_face_areas('120km')
5.88±0.1ms5.82±0.07ms0.99mpas_ocean.FaceAreas.time_face_areas('480km')
229k229k1.00mpas_ocean.FaceAreas.track_nbytes_face_areas('120km')
14.3k14.3k1.00mpas_ocean.FaceAreas.track_nbytes_face_areas('480km')
2.12M2.12M1.00mpas_ocean.FaceAreas.track_peakmem_face_areas('120km')
804k798k0.99mpas_ocean.FaceAreas.track_peakmem_face_areas('480km')
573±10ms570±3ms1.00mpas_ocean.GeoDataFrame.time_to_geodataframe('120km', False)
33.8±0.3ms34.3±0.5ms1.02mpas_ocean.GeoDataFrame.time_to_geodataframe('120km', True)
49.0±0.3ms51.3±0.2ms1.05mpas_ocean.GeoDataFrame.time_to_geodataframe('480km', False)
3.72±0.09ms3.69±0.07ms0.99mpas_ocean.GeoDataFrame.time_to_geodataframe('480km', True)
176±10ms166±0.6ms0.94mpas_ocean.Gradient.time_gradient('120km')
11.2±0.07ms11.1±0.03ms1.00mpas_ocean.Gradient.time_gradient('480km')
457k457k1.00mpas_ocean.Gradient.track_nbytes_gradient('120km')
28.7k28.7k1.00mpas_ocean.Gradient.track_nbytes_gradient('480km')
5.08M5.08M1.00mpas_ocean.Gradient.track_peakmem_gradient('120km')
328k328k1.00mpas_ocean.Gradient.track_peakmem_gradient('480km')
303±20μs300±7μs0.99mpas_ocean.HoleEdgeIndices.time_construct_hole_edge_indices('120km')
152±7μs140±5μs0.92mpas_ocean.HoleEdgeIndices.time_construct_hole_edge_indices('480km')
409±10μs415±9μs1.02mpas_ocean.Integrate.time_integrate('120km')
370±9μs380±10μs1.03mpas_ocean.Integrate.time_integrate('480km')
18.4M18.4M1.00mpas_ocean.Integrate.track_nbytes_integrate('120km')
1.2M1.2M1.00mpas_ocean.Integrate.track_nbytes_integrate('480km')
115±7ms108±2ms0.94mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'exclude')
108±2ms108±1ms1.00mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'include')
114±7ms107±1ms0.94mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'split')
8.47±0.07ms8.48±0.07ms1.00mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'exclude')
8.55±0.1ms8.42±0.1ms0.98mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'include')
8.45±0.06ms8.27±0.08ms0.98mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'split')
266±8μs277±9μs1.04mpas_ocean.PointInPolygon.time_face_search_lonlat('120km')
243±10μs227±4μs0.94mpas_ocean.PointInPolygon.time_face_search_lonlat('480km')
227±2μs229±1μs1.01mpas_ocean.PointInPolygon.time_face_search_xyz('120km')
202±8μs212±6μs1.05mpas_ocean.PointInPolygon.time_face_search_xyz('480km')
139±0.06ms138±0.3ms1.00mpas_ocean.RemapDownsample.time_bilinear_remapping
140±0.3ms141±1ms1.01mpas_ocean.RemapDownsample.time_inverse_distance_weighted_remapping
10.4±0.06ms10.5±0.1ms1.01mpas_ocean.RemapDownsample.time_nearest_neighbor_remapping
745±3ms741±3ms1.00mpas_ocean.RemapUpsample.time_bilinear_remapping
25.4±1ms23.9±0.6ms0.94mpas_ocean.RemapUpsample.time_inverse_distance_weighted_remapping
8.95±0.2ms8.89±0.2ms0.99mpas_ocean.RemapUpsample.time_nearest_neighbor_remapping
5.37±0.02ms5.47±0.09ms1.02mpas_ocean.ZonalAverage.time_zonal_average('120km')
2.75±0.02ms2.72±0.02ms0.99mpas_ocean.ZonalAverage.time_zonal_average('480km')
4.69±0.02ms4.89±0.3ms1.04quad_hexagon.QuadHexagon.time_open_dataset
3.93±0.02ms3.90±0.02ms0.99quad_hexagon.QuadHexagon.time_open_grid
4084081.00quad_hexagon.QuadHexagon.track_nbytes_open_dataset
3923921.00quad_hexagon.QuadHexagon.track_nbytes_open_grid
73.5k73.1k1.00quad_hexagon.QuadHexagon.track_peakmem_open_dataset
72.8k72.2k0.99quad_hexagon.QuadHexagon.track_peakmem_open_grid

Benchmarks that have got worse:

ChangeBefore [fda71bb]After [4ca774e]RatioBenchmark (Parameter)
+336M404M1.2face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
+365M434M1.19face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
+337M405M1.2face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
+337M405M1.2face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
+293M362M1.23import.Imports.track_peakmem_import_uxarray
+356M424M1.19mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 1)
+356M426M1.2mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 2)
+356M424M1.19mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 4)
+339M408M1.2mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 1)
+339M407M1.2mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 2)
+338M407M1.2mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 4)
+352M418M1.19mpas_ocean.GradientColdStartRss.peakmem_gradient('120km')
+331M398M1.2mpas_ocean.GradientColdStartRss.peakmem_gradient('480km')
+358M425M1.19mpas_ocean.ZonalAveragePeakMem.peakmem_zonal_average('120km')
+341M407M1.2mpas_ocean.ZonalAveragePeakMem.peakmem_zonal_average('480km')

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

Labels

new featureNew feature or requestrun-benchmarkRun 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

@ahijevyc@philipc2@aaronzedwick@erogluorhan@rajeeja@cmdupuis3