Skip to content

Better chunking strategies for constlat intersections and zonal routines. - #1624

Merged
erogluorhan merged 80 commits into
mainfrom
cmd/accusphere3
Aug 18, 2026
Merged

Better chunking strategies for constlat intersections and zonal routines.#1624
erogluorhan merged 80 commits into
mainfrom
cmd/accusphere3

Conversation

@cmdupuis3

@cmdupuis3cmdupuis3 commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

This PR contains two post-accusphere optimizations, eliminating low-level hard materializations by using a vector-based masking strategy rather than individual conditionals, and reduced zonal_mean peakmem by building only the candidate faces instead of the whole grid.

Partly addresses #1587

Closes#1650

Overview

Phase A eliminated the largest peak-memory amplifier in the zonal path: the port materialized the whole-grid (n_face, n_max, 2, 3) face-edge array up front (a 5.8× blow-up over the node coordinates, with a ~23 MB build peak on a 28k-face grid) even though each latitude only touches ~1% of faces. I added an @njit(parallel=True) subset builder, _get_cartesian_face_edge_nodes_array_subset, that constructs edges only for the candidate faces of a given latitude/band — bit-identical to indexing the full array — and rewired _compute_non_conservative_zonal_mean and _compute_face_band_weights to build per-candidate subsets instead of the whole grid. Peak memory for a 180-latitude zonal_mean dropped from 23.66 MB to 0.27 MB (≈88×) and it ran ~10% faster (no full build, no per-latitude fancy-index copies), verified lossless via a git-stash A/B (the only diff was a pre-existing 4.4e-16 parallel-reduction nondeterminism) with the full zonal/cross-section suite passing.

Phase B moved the six edge/face screeners (constant_lat/lon_intersections_no_extreme, constant_lat/lon_intersections_face_bounds, faces_within_lat/lon_bounds) off @njit and onto plain vectorized NumPy, drawing the boundary so the low-level Numba kernels stay for real geometry while these memory-bound elementwise predicates use NumPy — which is ~2.1× faster here and, unlike an njit kernel that forces a full .values materialization, composes with dask (a single _flatnonzero helper leans on NumPy's array_function protocol so a dask mask reduces block-wise, no explicit dask branch). Call sites pass .data instead of .values so a chunked grid stays lazy, and edge coordinates are gathered positionally (node_z.data[conn.ravel()].reshape(...)) to stay chunk-friendly. Along the way it fixed two real latent bugs: get_edges_at_constant_latitude referenced a nonexistent self.edge_node_z (it raised AttributeError on every call), and both edge paths crashed on chunked grids because xarray can't vindex with a dask indexer. Results are bit-identical to the original per-element loops across 300 randomized trials, dask==numpy, and it's committed as bf0abbe "Lazy intersections".

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

rajeeja added 30 commits June 8, 2026 15:47
…rite intersections, add 241 baseline testsgit status! - most came from accusphere
- benchmarks/geometry_kernels.py: ASV micro-benchmarks for all three
layers of the EFT intersection stack (_accux_gca, _try_gca_gca_intersection,
gca_gca_intersection, _accux_constlat, _try_gca_const_lat_intersection,
gca_const_lat_intersection) plus EFT primitives and point-in-polygon;
all functions warmed before timing so results reflect steady-state cost
- test/test_plot.py: add test_to_raster_auto_extent verifying that the
axis limits change and the raster contains finite data
…rectness fixes
Review comments addressed:
- Remove "near-double precision" / "sufficient" overclaims; say "roughly twice
as accurate" and note the robustness tier boundary clearly
- Explain _lon_bounds_from_vertices is required for UXarray antimeridian
encoding and cannot be removed
- Add block comment before _no_extreme functions clarifying they are
pre-existing edge screeners unrelated to the EFT stack
- Document SoS as explicit future work in _point_in_polygon_sphere docstring
- L2 pos_fin/neg_fin: replace ternary with int(); exploit neg=-pos symmetry
- Label computation: drop dead local*0 term, use integer mask arithmetic
- Remove vertex-lat snap from bounds: _face_location_info already captures
interior arc extrema accurately via the compensated kernel
- _ON_MINOR_ARC_TOL: document intentional 1e-10 vs C++ 1e-8 divergence
Bug fixes:
- on_minor_arc: add antipodal-endpoint guard; a x b = 0 for antipodal inputs
so every point on the great circle passes the collinearity test (false pos)
- bounds.py: replace mask arithmetic use_ext*z_ext + (1-use_ext)*z_edge with
plain if/else; 0*NaN = NaN propagates when norm=0, if/else does not
- _point_in_polygon_sphere: ray-nudge now restarts the loop from i=0 so all
edges are counted with the same ray (mid-loop nudge corrupted crossing parity)
Cleanup:
- Remove _flip_sign, _SIGN_NEG, _SIGN_POS, _SIGN_ZERO dead code from
point_in_face.py; inline literals in _counts_as_crossing
- Remove _SNAP_TOL_DEG constant and snap_tol_deg parameter throughout bounds.py
- Notebook: fix Grid.get_point_on_face -> get_faces_containing_point; remove
incorrect geometry.py row from Section 4 table; add accucross_pair and
acc_sqrt_re to Section 2 building-blocks table
…PI name
- ci/environment.yml: pin tornado<6.5.7 to avoid ssl.SSLError in panel 1.9.3
on Python 3.11 Windows (conda-forge regression, 2026-06-10)
- intersections.py: remove _gca_gca_intersection_cartesian shim (dead code);
add comment explaining _snap_const_lat_endpoint snap_sq constant
- test_intersections.py: update 4 call sites to use gca_gca_intersection directly
- spherical-geometry-accuracy.ipynb: fix stale Grid.get_point_on_face ->
Grid.get_faces_containing_point (2 occurrences)
…rite intersections, add 241 baseline testsgit status! - most came from accusphere
Reconcile diverged accusphere branch. Resolutions:
- intersections.py: restore inline=always on L1 kernels (_accux_constlat,
_accux_gca) for allocation scalar-replacement
- point_in_face.py: keep restart-loop ray casting (consistent parity),
adopt named sign constants, drop unused _flip_sign
- arcs.py: keep antipodal-endpoint guard in on_minor_arc
- bounds.py: keep vertex-latitude snapping (snap_tol_deg) path
- computing.py: keep detailed docstring with SIAM/EGUsphere references
Add an LLVM fma intrinsic and route two_prod through a single fused
multiply-add for its error term on hardware that supports it, selected at
import time and validated to be bit-exact against the Veltkamp split. Falls
back to the portable Veltkamp form otherwise, so there is no hard FMA
dependency.
The FMA path is ~2x faster in the compensated geometry kernels (each
two_prod drops from ~17 flops to one FMADD) and is numerically identical:
all 241 AccuSphGeom baseline cases pass unchanged.
Add _accux_constlat_scalar, which takes the arc endpoints as six scalars and
returns the candidate coordinates as scalars instead of two np.empty(3)
arrays. _accux_constlat now wraps it so the array API is unchanged.
Returning scalars lets Numba keep the candidates in registers, so a batch
loop over many edges does no per-point heap allocation. On a 16M-point
const-lat sweep this is ~2.7x faster than the array-returning path and drops
the AccuX/FP64 cost ratio from ~19x to ~7x. Bit-identical results; all 241
AccuSphGeom baseline cases pass.
@cmdupuis3

Copy link
Copy Markdown
CollaboratorAuthor

pre-commit.ci autofix

@cmdupuis3cmdupuis3 added the benchmarking Related to benchmarks, memory usage, and/or time profiling label Jul 28, 2026
@cmdupuis3cmdupuis3 added the run-benchmark Run ASV benchmark workflow label Jul 29, 2026
@github-actions

github-actionsBot commented Jul 29, 2026

Copy link
Copy Markdown

ASV Benchmarking

Benchmark Comparison Results

Benchmarks that have improved:

ChangeBefore [cbdaa23]After [a007f80]RatioBenchmark (Parameter)
-27.0±0.5ms9.61±0.1ms0.36mpas_ocean.ZonalAverage.time_zonal_average('120km')
-6.16±0.3ms5.32±0.3ms0.86mpas_ocean.ZonalAverage.time_zonal_average('480km')

Benchmarks that have stayed the same:

ChangeBefore [cbdaa23]After [a007f80]RatioBenchmark (Parameter)
202±0.7ms202±2ms1.00bench_connectivity.Connectivity.time_edge_face('120km')
12.9±0.8ms12.3±0.1ms0.95bench_connectivity.Connectivity.time_edge_face('480km')
200±0.7ms202±0.9ms1.01bench_connectivity.Connectivity.time_edge_node('120km')
11.4±0.1ms11.4±0.1ms0.99bench_connectivity.Connectivity.time_edge_node('480km')
219±20ms203±2ms0.93bench_connectivity.Connectivity.time_face_edge('120km')
11.8±0.2ms12.0±0.1ms1.02bench_connectivity.Connectivity.time_face_edge('480km')
893±4ms905±9ms1.01bench_connectivity.Connectivity.time_face_face('120km')
59.5±0.8ms57.5±0.4ms0.97bench_connectivity.Connectivity.time_face_face('480km')
71.7±2μs73.2±2μs1.02bench_connectivity.Connectivity.time_face_node('120km')
71.8±2μs70.8±2μs0.99bench_connectivity.Connectivity.time_face_node('480km')
430±10μs443±9μs1.03bench_connectivity.Connectivity.time_n_nodes_per_face('120km')
378±10μs370±9μs0.98bench_connectivity.Connectivity.time_n_nodes_per_face('480km')
201±1ms202±0.6ms1.00bench_connectivity.Connectivity.time_node_edge('120km')
11.7±0.1ms11.7±0.05ms1.00bench_connectivity.Connectivity.time_node_edge('480km')
91.6±3ms85.6±6ms0.93bench_connectivity.Connectivity.time_node_face('120km')
5.40±0.3ms5.33±0.08ms0.99bench_connectivity.Connectivity.time_node_face('480km')
8.87±0.5ms8.81±0.1ms0.99face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
2.77±0.08ms2.84±0.03ms1.02face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
10.6±10s10.3±10ms~0.00face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
2.22±0.02ms2.18±0.04ms0.98face_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.98M1.98M1.00face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
1.99M1.99M1.00face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
2.15M2.15M1.00face_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'))
336M336M1.00face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
367M367M1.00face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
338M337M1.00face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
337M337M1.00face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
1.23±0.01μs1.24±0.05μs1.01geometry_kernels.AccucrossKernels.time_accucross
2.83±0.07μs2.75±0.04μs0.98geometry_kernels.AccucrossKernels.time_accucross_pair
456±20ns461±20ns1.01geometry_kernels.EFTPrimitives.time_acc_sqrt_re
446±20ns446±20ns1.00geometry_kernels.EFTPrimitives.time_diff_of_products
421±3ns401±20ns0.95geometry_kernels.EFTPrimitives.time_two_prod
400±10ns416±20ns1.04geometry_kernels.EFTPrimitives.time_two_sum
1.56±0.04μs1.64±0.04μs1.05geometry_kernels.GCAConstLatIntersection.time_accux_constlat_kernel
1.24±0.05μs1.15±0.03μs0.93geometry_kernels.GCAConstLatIntersection.time_gca_const_lat_intersection
2.18±0.1μs2.05±0.4μs0.94geometry_kernels.GCAConstLatIntersection.time_try_gca_const_lat_intersection
1.75±0.06μs1.69±0.02μs0.97geometry_kernels.GCAGCAIntersection.time_accux_gca_kernel
1.40±0.02μs1.42±0.05μs1.01geometry_kernels.GCAGCAIntersection.time_gca_gca_intersection
2.25±0.05μs2.22±0.09μs0.99geometry_kernels.GCAGCAIntersection.time_try_gca_gca_intersection
53.7±0.8μs54.0±0.8μs1.00geometry_kernels.OrientPredicates.time_on_minor_arc
1.11±0.04μs1.11±0.02μs1.00geometry_kernels.OrientPredicates.time_orient3d_on_sphere
2.71±0.1ms2.59±0.01ms0.96geometry_samebody.SameBodyConstLat.time_accux_dispatch
1.18±0ms1.17±0ms1.00geometry_samebody.SameBodyConstLat.time_accux_kernel
1.72±0.01ms1.72±0.01ms1.00geometry_samebody.SameBodyConstLat.time_fp64_dispatch
149±1μs148±1μs0.99geometry_samebody.SameBodyConstLat.time_fp64_kernel
32.2±0.04ms32.3±0.02ms1.00geometry_samebody_gcagca.SameBodyGcaGca.time_accux_dispatch
10.3±0.01ms10.4±0.05ms1.01geometry_samebody_gcagca.SameBodyGcaGca.time_accux_kernel
26.4±0.01ms26.7±0.1ms1.01geometry_samebody_gcagca.SameBodyGcaGca.time_fp64_dispatch
4.86±0.02ms4.92±0ms1.01geometry_samebody_gcagca.SameBodyGcaGca.time_fp64_kernel
847±6ms848±6ms1.00import.Imports.timeraw_import_uxarray
293M295M1.01import.Imports.track_peakmem_import_uxarray
2.82±0.03ms2.86±0.02ms1.02mpas_ocean.CheckNorm.time_check_norm('120km')
2.34±0.05ms2.37±0.06ms1.01mpas_ocean.CheckNorm.time_check_norm('480km')
867±10ms867±10ms1.00mpas_ocean.ConnectivityConstruction.time_face_face_connectivity('120km')
56.2±0.4ms55.6±0.6ms0.99mpas_ocean.ConnectivityConstruction.time_face_face_connectivity('480km')
674±10μs690±10μs1.02mpas_ocean.ConnectivityConstruction.time_n_nodes_per_face('120km')
599±9μs607±10μs1.01mpas_ocean.ConnectivityConstruction.time_n_nodes_per_face('480km')
5.49±0.03ms5.54±0.03ms1.01mpas_ocean.ConstructFaceLatLon.time_cartesian_averaging('120km')
4.01±0.02ms4.02±0.02ms1.00mpas_ocean.ConstructFaceLatLon.time_cartesian_averaging('480km')
3.48±0.01s3.46±0s0.99mpas_ocean.ConstructFaceLatLon.time_welzl('120km')
228±2ms223±2ms0.98mpas_ocean.ConstructFaceLatLon.time_welzl('480km')
18.2±0.05ms18.3±0.07ms1.00mpas_ocean.ConstructTreeStructures.time_ball_tree('120km')
1.06±0.01ms1.07±0.01ms1.01mpas_ocean.ConstructTreeStructures.time_ball_tree('480km')
10.6±0.01ms10.7±0.04ms1.00mpas_ocean.ConstructTreeStructures.time_kd_tree('120km')
759±20μs749±10μs0.99mpas_ocean.ConstructTreeStructures.time_kd_tree('480km')
730±10ms743±20ms1.02mpas_ocean.CrossSections.time_const_lat('120km', 1)
377±4ms380±1ms1.01mpas_ocean.CrossSections.time_const_lat('120km', 2)
191±1ms193±3ms1.01mpas_ocean.CrossSections.time_const_lat('120km', 4)
565±10ms568±7ms1.01mpas_ocean.CrossSections.time_const_lat('480km', 1)
286±3ms290±5ms1.01mpas_ocean.CrossSections.time_const_lat('480km', 2)
147±1ms151±0.5ms1.03mpas_ocean.CrossSections.time_const_lat('480km', 4)
355M355M1.00mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 1)
355M355M1.00mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 2)
355M355M1.00mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 4)
339M339M1.00mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 1)
339M339M1.00mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 2)
339M339M1.00mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 4)
24.9±0.05ms25.5±0.4ms1.02mpas_ocean.DualMesh.time_dual_mesh_construction('120km')
3.28±0.05ms3.29±0.08ms1.00mpas_ocean.DualMesh.time_dual_mesh_construction('480km')
61.7±1ms62.5±0.2ms1.01mpas_ocean.FaceAreas.time_face_areas('120km')
5.02±5s7.79±5ms~0.00mpas_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')
823k823k1.00mpas_ocean.FaceAreas.track_peakmem_face_areas('480km')
964±2ms969±3ms1.00mpas_ocean.GeoDataFrame.time_to_geodataframe('120km', False)
52.4±0.8ms56.9±1ms1.08mpas_ocean.GeoDataFrame.time_to_geodataframe('120km', True)
85.3±0.9ms85.1±0.5ms1.00mpas_ocean.GeoDataFrame.time_to_geodataframe('480km', False)
6.08±0.3ms6.33±0.4ms1.04mpas_ocean.GeoDataFrame.time_to_geodataframe('480km', True)
177±0.2ms177±0.8ms1.00mpas_ocean.Gradient.time_gradient('120km')
12.7±0.2ms12.5±0.1ms0.98mpas_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')
354M353M1.00mpas_ocean.GradientColdStartRss.peakmem_gradient('120km')
331M331M1.00mpas_ocean.GradientColdStartRss.peakmem_gradient('480km')
392±8μs392±20μs1.00mpas_ocean.HoleEdgeIndices.time_construct_hole_edge_indices('120km')
214±10μs217±7μs1.01mpas_ocean.HoleEdgeIndices.time_construct_hole_edge_indices('480km')
583±30μs577±30μs0.99mpas_ocean.Integrate.time_integrate('120km')
537±40μs531±40μs0.99mpas_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')
185±0.4ms189±1ms1.02mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'exclude')
186±2ms189±0.9ms1.02mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'include')
185±2ms187±0.9ms1.01mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'split')
14.1±0.06ms14.4±0.2ms1.02mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'exclude')
14.3±0.3ms14.3±0.08ms1.00mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'include')
14.6±0.7ms14.2±0.1ms0.97mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'split')
421±10μs462±30μs1.10mpas_ocean.PointInPolygon.time_face_search_lonlat('120km')
436±10μs426±20μs0.98mpas_ocean.PointInPolygon.time_face_search_lonlat('480km')
391±3μs405±20μs1.04mpas_ocean.PointInPolygon.time_face_search_xyz('120km')
381±10μs433±40μs~1.13mpas_ocean.PointInPolygon.time_face_search_xyz('480km')
243±0.7ms251±2ms1.03mpas_ocean.RemapDownsample.time_bilinear_remapping
291±1ms290±1ms1.00mpas_ocean.RemapDownsample.time_inverse_distance_weighted_remapping
16.1±0.3ms16.3±0.1ms1.02mpas_ocean.RemapDownsample.time_nearest_neighbor_remapping
1.46±0.01s1.47±0.01s1.01mpas_ocean.RemapUpsample.time_bilinear_remapping
37.3±0.7ms38.9±0.5ms1.04mpas_ocean.RemapUpsample.time_inverse_distance_weighted_remapping
12.7±0.2ms13.0±0.3ms1.03mpas_ocean.RemapUpsample.time_nearest_neighbor_remapping
380M357M0.94mpas_ocean.ZonalAveragePeakMem.peakmem_zonal_average('120km')
341M341M1.00mpas_ocean.ZonalAveragePeakMem.peakmem_zonal_average('480km')
7.08±0.3ms8.11±1ms~1.15quad_hexagon.QuadHexagon.time_open_dataset
6.29±0.5ms6.21±0.5ms0.99quad_hexagon.QuadHexagon.time_open_grid
4084081.00quad_hexagon.QuadHexagon.track_nbytes_open_dataset
3923921.00quad_hexagon.QuadHexagon.track_nbytes_open_grid
73.8k73.8k1.00quad_hexagon.QuadHexagon.track_peakmem_open_dataset
73k73k1.00quad_hexagon.QuadHexagon.track_peakmem_open_grid

@cmdupuis3
cmdupuis3 requested a review from rajeejaJuly 29, 2026 22:38
@cmdupuis3
cmdupuis3 marked this pull request as ready for review July 29, 2026 22:38
@rajeeja

Copy link
Copy Markdown
Contributor

Thanks for putting this together. I'll take a look at this and post my comments soon

@rajeejarajeeja left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verified independently: subset builder is bit-identical to whole-grid builder (tested HEALPix + mixed-polygon MPAS), vectorized screeners match brute-force reference, both dask/AttributeError bugs reproduce on main and are fixed here. Two small comments below, non-blocking on correctness but worth addressing before merge.

Comment threaduxarray/core/zonal.py

@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 good to me. Just a couple questions:

  • Should we worry about the couple benchmarks that got worse? I believe, no, they shouldn't be directly related to the changes here, but am curious about your thoughts.
  • Could you add test cases to cover the two latent bugs that this PR fixes?

@cmdupuis3

Copy link
Copy Markdown
CollaboratorAuthor

@erogluorhan Pretty sure those benchmark regressions were just variability, they're gone in the new batch. Regression tests are now added for both bugs.

@rajeeja
rajeeja self-requested a review August 7, 2026 19:26
@rajeeja

Copy link
Copy Markdown
Contributor

let's wait for asv fixes to be merged before we merge this.

@erogluorhan
erogluorhan self-requested a review August 18, 2026 01:17

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

Looks good to me!

@erogluorhan
erogluorhan merged commit 23b8d5c into mainAug 18, 2026
13 checks passed
@github-project-automationgithub-project-automationBot moved this from 👀 In review to ✅ Done in UXarray DevelopmentAug 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

benchmarkingRelated to benchmarks, memory usage, and/or time profilingrun-benchmarkRun ASV benchmark workflowscalabilityRelated to scalability & performance efforts

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

Post-accusphere optimized routines (constlat intersections and zonal)

4 participants

@cmdupuis3@rajeeja@erogluorhan@hongyuchen1030