Skip to content

Avoid converting chunked data to Numpy arrays (i.e. .values calls) - #1588

Open
cmdupuis3 wants to merge 31 commits into
UXARRAY:mainfrom
cmdupuis3:cmd/devalue
Open

Avoid converting chunked data to Numpy arrays (i.e. .values calls)#1588
cmdupuis3 wants to merge 31 commits into
UXARRAY:mainfrom
cmdupuis3:cmd/devalue

Conversation

@cmdupuis3

@cmdupuis3 cmdupuis3 commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Partly addresses #1583

Overview

This PR is to resolve suboptimal usage of .values throughout the repo, primarily by deferring to lazy xarray and dask operations. See the table of usage sites in Issue #1583.

This specific PR is for the first two tables of usage sites; dead code and bugfixes aren't really included here. Aside from simple cases, some cases were solved by branching on whether the data type was already chunked, so scalability for some parts will depend on if you're using numpy or xarray/dask arrays at those points.

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

@cmdupuis3 cmdupuis3 self-assigned this Jul 18, 2026
@cmdupuis3 cmdupuis3 added scalability Related to scalability & performance efforts run-benchmark Run ASV benchmark workflow labels Jul 18, 2026
@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown

ASV Benchmarking

Benchmark Comparison Results

Benchmarks that have improved:

Change Before [b85b131] After [1a097c6] Ratio Benchmark (Parameter)
- 519M 337M 0.65 face_bounds.FaceBounds.peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
- 636M 337M 0.53 face_bounds.FaceBounds.peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
- 670±2ms 516±3ms 0.77 mpas_ocean.CrossSections.time_const_lat('120km', 1)
- 337±4ms 262±1ms 0.78 mpas_ocean.CrossSections.time_const_lat('120km', 2)
- 173±0.9ms 137±0.6ms 0.79 mpas_ocean.CrossSections.time_const_lat('120km', 4)
- 443M 332M 0.75 mpas_ocean.FaceAreas.peakmem_compute_face_areas('480km')
- 466M 331M 0.71 mpas_ocean.Gradient.peakmem_gradient('480km')

Benchmarks that have stayed the same:

Change Before [b85b131] After [1a097c6] Ratio Benchmark (Parameter)
233±0.8ms 234±2ms 1.00 bench_connectivity.Connectivity.time_edge_face('120km')
13.5±0.02ms 13.7±0.1ms 1.02 bench_connectivity.Connectivity.time_edge_face('480km')
262±30ms 235±5ms ~0.90 bench_connectivity.Connectivity.time_edge_node('120km')
12.7±0.05ms 12.7±0.05ms 1.00 bench_connectivity.Connectivity.time_edge_node('480km')
231±0.1ms 232±0.4ms 1.00 bench_connectivity.Connectivity.time_face_edge('120km')
13.2±0.2ms 13.0±0.07ms 0.99 bench_connectivity.Connectivity.time_face_edge('480km')
848±10ms 841±7ms 0.99 bench_connectivity.Connectivity.time_face_face('120km')
50.6±0.3ms 51.4±0.3ms 1.02 bench_connectivity.Connectivity.time_face_face('480km')
63.2±2μs 65.8±5μs 1.04 bench_connectivity.Connectivity.time_face_node('120km')
62.0±0.7μs 62.8±0.9μs 1.01 bench_connectivity.Connectivity.time_face_node('480km')
393±5μs 392±1μs 1.00 bench_connectivity.Connectivity.time_n_nodes_per_face('120km')
313±9μs 316±9μs 1.01 bench_connectivity.Connectivity.time_n_nodes_per_face('480km')
232±0.4ms 233±2ms 1.00 bench_connectivity.Connectivity.time_node_edge('120km')
13.0±0.02ms 13.0±0.2ms 1.00 bench_connectivity.Connectivity.time_node_edge('480km')
90.0±20ms 86.1±6ms 0.96 bench_connectivity.Connectivity.time_node_face('120km')
4.86±0.01ms 4.92±0.04ms 1.01 bench_connectivity.Connectivity.time_node_face('480km')
336M 336M 1.00 face_bounds.FaceBounds.peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
367M 365M 0.99 face_bounds.FaceBounds.peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
7.46±0.02ms 7.53±0.04ms 1.01 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
2.52±0.01ms 2.52±0.05ms 1.00 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
9.45±0.03ms 9.61±0.02ms 1.02 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
1.97±0.01ms 2.03±0ms 1.03 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
1.18±0.04μs 1.17±0.03μs 0.99 geometry_kernels.AccucrossKernels.time_accucross
2.55±0.03μs 2.56±0.02μs 1.00 geometry_kernels.AccucrossKernels.time_accucross_pair
420±20ns 386±5ns 0.92 geometry_kernels.EFTPrimitives.time_acc_sqrt_re
406±10ns 406±80ns 1.00 geometry_kernels.EFTPrimitives.time_diff_of_products
341±5ns 350±10ns 1.03 geometry_kernels.EFTPrimitives.time_two_prod
346±9ns 340±9ns 0.98 geometry_kernels.EFTPrimitives.time_two_sum
1.36±0.02μs 1.39±0.01μs 1.02 geometry_kernels.GCAConstLatIntersection.time_accux_constlat_kernel
1.01±0.02μs 1.03±0.02μs 1.02 geometry_kernels.GCAConstLatIntersection.time_gca_const_lat_intersection
1.75±0.07μs 1.77±0.01μs 1.01 geometry_kernels.GCAConstLatIntersection.time_try_gca_const_lat_intersection
1.55±0.03μs 1.55±0.03μs 1.00 geometry_kernels.GCAGCAIntersection.time_accux_gca_kernel
1.25±0.02μs 1.26±0.02μs 1.00 geometry_kernels.GCAGCAIntersection.time_gca_gca_intersection
2.03±0.03μs 2.01±0.03μs 0.99 geometry_kernels.GCAGCAIntersection.time_try_gca_gca_intersection
38.4±0.3μs 37.7±1μs 0.98 geometry_kernels.OrientPredicates.time_on_minor_arc
716±9ns 706±10ns 0.99 geometry_kernels.OrientPredicates.time_orient3d_on_sphere
2.91±0.1ms 2.80±0ms 0.96 geometry_samebody.SameBodyConstLat.time_accux_dispatch
1.33±0.01ms 1.33±0.01ms 1.00 geometry_samebody.SameBodyConstLat.time_accux_kernel
1.80±0ms 1.80±0.01ms 1.00 geometry_samebody.SameBodyConstLat.time_fp64_dispatch
154±0.8μs 153±0.5μs 0.99 geometry_samebody.SameBodyConstLat.time_fp64_kernel
32.5±0.06ms 32.4±0.02ms 1.00 geometry_samebody_gcagca.SameBodyGcaGca.time_accux_dispatch
10.6±0ms 10.7±0.01ms 1.00 geometry_samebody_gcagca.SameBodyGcaGca.time_accux_kernel
28.4±0.06ms 28.3±0.6ms 1.00 geometry_samebody_gcagca.SameBodyGcaGca.time_fp64_dispatch
4.39±0.01ms 4.43±0.01ms 1.01 geometry_samebody_gcagca.SameBodyGcaGca.time_fp64_kernel
799±3ms 801±8ms 1.00 import.Imports.timeraw_import_uxarray
2.24±0.02ms 2.27±0.02ms 1.01 mpas_ocean.CheckNorm.time_check_norm('120km')
1.87±0.02ms 1.85±0.02ms 0.99 mpas_ocean.CheckNorm.time_check_norm('480km')
714±5ms 726±8ms 1.02 mpas_ocean.ConnectivityConstruction.time_face_face_connectivity('120km')
45.4±0.3ms 45.4±0.2ms 1.00 mpas_ocean.ConnectivityConstruction.time_face_face_connectivity('480km')
623±4μs 633±10μs 1.02 mpas_ocean.ConnectivityConstruction.time_n_nodes_per_face('120km')
558±4μs 565±8μs 1.01 mpas_ocean.ConnectivityConstruction.time_n_nodes_per_face('480km')
5.14±0.05ms 5.06±0.02ms 0.98 mpas_ocean.ConstructFaceLatLon.time_cartesian_averaging('120km')
3.59±0.03ms 3.55±0.04ms 0.99 mpas_ocean.ConstructFaceLatLon.time_cartesian_averaging('480km')
3.29±0.01s 3.30±0.01s 1.00 mpas_ocean.ConstructFaceLatLon.time_welzl('120km')
212±1ms 213±0.6ms 1.00 mpas_ocean.ConstructFaceLatLon.time_welzl('480km')
19.7±0.02ms 19.7±0.02ms 1.00 mpas_ocean.ConstructTreeStructures.time_ball_tree('120km')
1.13±0.01ms 1.12±0.02ms 0.99 mpas_ocean.ConstructTreeStructures.time_ball_tree('480km')
10.6±0.02ms 10.6±0.02ms 1.00 mpas_ocean.ConstructTreeStructures.time_kd_tree('120km')
756±10μs 763±9μs 1.01 mpas_ocean.ConstructTreeStructures.time_kd_tree('480km')
489±2ms 467±7ms 0.96 mpas_ocean.CrossSections.time_const_lat('480km', 1)
247±1ms 231±0.5ms 0.93 mpas_ocean.CrossSections.time_const_lat('480km', 2)
129±1ms 120±1ms 0.93 mpas_ocean.CrossSections.time_const_lat('480km', 4)
22.4±0.2ms 22.1±0.05ms 0.99 mpas_ocean.DualMesh.time_dual_mesh_construction('120km')
2.64±0.02ms 2.59±0.03ms 0.98 mpas_ocean.DualMesh.time_dual_mesh_construction('480km')
350M 350M 1.00 mpas_ocean.FaceAreas.peakmem_compute_face_areas('120km')
57.1±0.08ms 57.0±0.04ms 1.00 mpas_ocean.FaceAreas.time_compute_face_areas('120km')
5.98±0.03ms 5.95±0.02ms 0.99 mpas_ocean.FaceAreas.time_compute_face_areas('480km')
841±2ms 841±5ms 1.00 mpas_ocean.GeoDataFrame.time_to_geodataframe('120km', False)
51.9±0.8ms 52.1±0.9ms 1.01 mpas_ocean.GeoDataFrame.time_to_geodataframe('120km', True)
72.5±0.4ms 73.4±0.8ms 1.01 mpas_ocean.GeoDataFrame.time_to_geodataframe('480km', False)
5.72±0.06ms 5.72±0.07ms 1.00 mpas_ocean.GeoDataFrame.time_to_geodataframe('480km', True)
351M 355M 1.01 mpas_ocean.Gradient.peakmem_gradient('120km')
167±3ms 165±0.5ms 0.99 mpas_ocean.Gradient.time_gradient('120km')
11.5±0.09ms 12.1±0.4ms 1.05 mpas_ocean.Gradient.time_gradient('480km')
373±9μs 363±4μs 0.97 mpas_ocean.HoleEdgeIndices.time_construct_hole_edge_indices('120km')
201±5μs 209±10μs 1.04 mpas_ocean.HoleEdgeIndices.time_construct_hole_edge_indices('480km')
350M 351M 1.00 mpas_ocean.Integrate.peakmem_integrate('120km')
330M 330M 1.00 mpas_ocean.Integrate.peakmem_integrate('480km')
609±30μs 591±20μs 0.97 mpas_ocean.Integrate.time_integrate('120km')
484±20μs 502±10μs 1.04 mpas_ocean.Integrate.time_integrate('480km')
189±1ms 186±1ms 0.98 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'exclude')
188±1ms 187±0.8ms 0.99 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'include')
189±1ms 189±2ms 1.00 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'split')
14.2±0.1ms 13.9±0.2ms 0.98 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'exclude')
14.1±0.2ms 14.0±0.2ms 1.00 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'include')
14.3±0.3ms 14.1±0.06ms 0.98 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'split')
393±20μs 366±10μs 0.93 mpas_ocean.PointInPolygon.time_face_search_lonlat('120km')
352±6μs 351±3μs 1.00 mpas_ocean.PointInPolygon.time_face_search_lonlat('480km')
338±7μs 343±10μs 1.01 mpas_ocean.PointInPolygon.time_face_search_xyz('120km')
346±20μs 328±9μs 0.95 mpas_ocean.PointInPolygon.time_face_search_xyz('480km')
211±1ms 209±0.8ms 0.99 mpas_ocean.RemapDownsample.time_bilinear_remapping
224±0.9ms 227±2ms 1.02 mpas_ocean.RemapDownsample.time_inverse_distance_weighted_remapping
15.5±0.07ms 15.6±0.2ms 1.01 mpas_ocean.RemapDownsample.time_nearest_neighbor_remapping
1.18±0s 1.19±0s 1.01 mpas_ocean.RemapUpsample.time_bilinear_remapping
36.0±0.3ms 36.7±0.5ms 1.02 mpas_ocean.RemapUpsample.time_inverse_distance_weighted_remapping
11.3±0.4ms 11.3±0.1ms 1.00 mpas_ocean.RemapUpsample.time_nearest_neighbor_remapping
26.7±0.07ms 26.2±0.2ms 0.98 mpas_ocean.ZonalAverage.time_zonal_average('120km')
5.22±0.06ms 5.39±0.07ms 1.03 mpas_ocean.ZonalAverage.time_zonal_average('480km')
325M 326M 1.00 quad_hexagon.QuadHexagon.peakmem_open_dataset
325M 325M 1.00 quad_hexagon.QuadHexagon.peakmem_open_grid
6.71±0.1ms 6.65±0.2ms 0.99 quad_hexagon.QuadHexagon.time_open_dataset
5.67±0.2ms 5.82±0.3ms 1.03 quad_hexagon.QuadHexagon.time_open_grid

@cmdupuis3

Copy link
Copy Markdown
Collaborator Author

pre-commit.ci autofix

@cmdupuis3

Copy link
Copy Markdown
Collaborator Author

There's some spurious asv results from machine variability, but the benchmarks consistenly show peak-mem reductions and some speedups for cross-sections.

@cmdupuis3
cmdupuis3 requested a review from erogluorhan July 20, 2026 23:25
@cmdupuis3
cmdupuis3 marked this pull request as ready for review July 20, 2026 23:26
@cmdupuis3
cmdupuis3 requested a review from rajeeja July 23, 2026 23:26
@cmdupuis3

Copy link
Copy Markdown
Collaborator Author

pre-commit.ci autofix

@Sevans711
Sevans711 self-requested a review July 27, 2026 16:18

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

Hi @cmdupuis3, thank you for proposing these changes! Overall these look like good clean changes which should help improve the scalability of uxarray.

I have some notes/suggestions/requested changes. Primarily, I noticed that there are missing regression tests. This may be especially important for places with significant changes (more significant than just replacing obj.values with obj.data):

  • UxDataArray.integrate()
  • UxDataArrayCrossSectionAccessor.__call__()
  • uxarray.plot.matplotlib._nearest_neighbor_resample()
  • RemapAccessor.apply_weights()

It may be nice to add a regression test for UxDataset.to_xarray() too, even though the changes here are minor, since that feels like a very core part of the functionality.

The tests could be similar to what you added already in test_topological_agg.py (plus my additional request on that file): ensure that numpy and dask inputs ultimately give the same values (plus assert that the dask inputs lead to dask outputs).

Other parts of the code changes here might benefit from similar tests, but I don't know if that should be necessary. For example, the changes in _geos just replace obj.values with obj.data; does a regression test need to be added for that or no? Curious to hear from @erogluorhan and/or @rajeeja on this question in particular.

Misc. note: I'm not sure if I fully understand the changes to uxarray/cross_sections files. I need to make sure to take a closer look at those during a subsequent review.

Comment thread uxarray/core/aggregation.py
Comment thread uxarray/core/aggregation.py Outdated
Comment thread uxarray/core/aggregation.py
Comment thread uxarray/core/aggregation.py Outdated
Comment thread uxarray/core/dataarray.py Outdated
Comment thread uxarray/core/dataarray.py
Comment thread test/core/test_topological_agg.py
@erogluorhan erogluorhan changed the title Devalue uxarray to make it less expensive Avoid converting chunked data to Numpy arrays (i.e. .values calls) in the code Jul 27, 2026
@erogluorhan erogluorhan changed the title Avoid converting chunked data to Numpy arrays (i.e. .values calls) in the code Avoid converting chunked data to Numpy arrays (i.e. .values calls) Jul 27, 2026
@erogluorhan

Copy link
Copy Markdown
Member

@erogluorhan Sam and I were wondering about your philosophy with respect to dask versus numpy routines. I was thinking that if we have a case where the numpy routine could be faster than a dask routine in some situations, we might want to keep both, or maybe you'd want to go all in on dask for simplicity.

if isinstance(uxda.data, np.ndarray):
    aggregated_var = _apply_node_to_face_aggregation_numpy(uxda, ...)
elif isinstance(uxda.data, da.Array):
    # apply aggregation on dask array, TODO:
    aggregated_var = _apply_node_to_face_aggregation_numpy(uxda, ...)
else:
    raise ValueError

This is something in main's aggregate.py. I fleshed out the dask branch, but now we still have this logic, and I left the numpy routine in on this branch.

If I am understanding this correctly, question is about the use of routines, not data structures. If so, using standard NumPy functions directly on chunked Dask-backed Xarray objects should be completely safe with chunks. Let me know if this helps.

@github-project-automation github-project-automation Bot moved this to 👀 In review in UXarray Development Aug 7, 2026
@cmdupuis3 cmdupuis3 moved this from 👀 In review to 🏗 In progress in UXarray Development Aug 7, 2026
@cmdupuis3 cmdupuis3 moved this from 🏗 In progress to 👀 In review in UXarray Development Aug 7, 2026
cmdupuis3 and others added 6 commits August 10, 2026 09:38
A stash pop was committed in 9cfd53e with its markers intact, making the
module a syntax error. Both sides were additive, so keep both.

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

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

This looks almost ready to merge, I just have a few minor comments remaining!

Requires response:

  • I noticed the mpas_ocean.Integrate.time_integrate('480km') benchmark has worsened slightly. Is this expected behavior? Do you think this is real, a rounding error, or something else?
  • I would suggest to remove all of the pytest.importorskip calls; I left an inline comment with more details.

The rest of this comment does not require response. It includes the notes I wrote down, for future reference.

Other Benchmarking:

  • The cross sections benchmarks seem to have improved, which is great!
  • I'm ignoring peakmem "improvements" (as per #1605)
  • Even though other benchmarks didn't show notable improvements, I think that is fine, because they didn't get worse, either. Also, this PR isn't fully closing the original issue, anyway, it is just addressing part of it.

Testing:

I checked all the cases where I previously requested tests for correctness (numpy & dask give same result) and regression tests (check dask inputs cause dask outputs); here is what I found:

  • (Looks done to me! Confirmed tests pass here, fail on main.) UxDataArray.integrate()
  • (Looks done to me! Confirmed tests pass here, fail on main. Also, the tests do a good job covering a variety of possible failure cases, by including multiple dimensions and some nan / some non-nan values.) UxDataArrayCrossSectionAccessor.__call__()
  • (Not done, but now I'm less sure if it's necessary; left an inline comment to discuss.) uxarray.plot.matplotlib._nearest_neighbor_resample()
  • (Looks done to me! Confirmed tests pass here, fail on main. Also, the tests do a good job covering nontrivial cases, like handling multiple dimensions.) RemapAccessor.apply_weights()
  • (Not done, but optional; no need to block merging for this.) UxDataset.to_xarray()

Previously I noted I need to look closer at cross sections. After doing so and considering the new cross sections tests, I am feeling convinced that the cross sections changes should be working as intended. Noting that the extra dims included in those tests might also help with #1461.

Comment thread uxarray/plot/matplotlib.py
Comment thread test/core/test_topological_agg.py Outdated
@cmdupuis3

Copy link
Copy Markdown
Collaborator Author

I noticed the mpas_ocean.Integrate.time_integrate('480km') benchmark has worsened slightly. Is this expected behavior? Do you think this is real, a rounding error, or something else?

It isn't real, you can rerun the benchmarks and it'll probably disappear. There's always a chance with the smaller benchmarks that they'll trigger due to machine variability. 1.1x is pretty suspect.

@rajeeja

rajeeja commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

While reviewing this I noticed the numpy aggregation paths drop coords and attrs while your new dask paths preserve them via apply_ufunc, so on this branch .chunk() decides whether .sel(time=...) works on the result. That turned out to be a pre-existing bug on main, so I filed #1665 and opened #1666 against main rather than adding it here. Merging main once that lands should bring your two paths back into agreement.

@cmdupuis3

Copy link
Copy Markdown
Collaborator Author

pre-commit.ci autofix

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

Looks to me like this is ready to merge into main!

Any guesses as to why the mpas_ocean.CrossSections.time_const_lat benchmarks show improvements now, but didn't show improvements in prior iterations of this PR? (This question is non-blocking, since it's an improvement! Just curious if you have insights about this.)

@cmdupuis3

Copy link
Copy Markdown
Collaborator Author

@Sevans711 They've shown improvements for a while now, that's not recent. There are some changes to how cross-sections are caluculated on chunked grids that served as the .values fix, they weren't as trivial as most other changes here.

@Sevans711

Copy link
Copy Markdown
Collaborator

They've shown improvements for a while now, that's not recent. There are some changes to how cross-sections are caluculated on chunked grids that served as the .values fix, they weren't as trivial as most other changes here.

Ah, woops! I confused myself by looking at my prior comment mentioning the benchmarks. Thank you for clarifying!

@rljacob
rljacob requested a balanced review from Copilot August 14, 2026 18:15
@rljacob

rljacob commented Aug 14, 2026

Copy link
Copy Markdown
Member

I have copilot access on github and will ask it to do a review.

Copilot AI 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.

Pull request overview

Improves lazy execution by replacing eager .values conversions with Dask/xarray-compatible operations.

Changes:

  • Adds lazy remapping, aggregation, integration, and cross-section paths.
  • Preserves chunked backing arrays during grid and format conversions.
  • Adds Dask parity tests for key operations.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
uxarray/remap/structured.py Preserves lazy arrays during reshaping.
uxarray/remap/apply_weights.py Applies sparse weights blockwise.
uxarray/remap/accessor.py Documents lazy weight application.
uxarray/plot/matplotlib.py Loads only sampled faces.
uxarray/io/_scrip.py Preserves lazy SCRIP coordinates.
uxarray/io/_geos.py Preserves GEOS backing arrays.
uxarray/grid/slice.py Adds lazy connectivity remapping.
uxarray/grid/grid.py Avoids unnecessary grid materialization.
uxarray/cross_sections/sample.py Removes eager Numba gathering.
uxarray/cross_sections/dataarray_accessor.py Implements lazy cross-section gathering.
uxarray/core/dataset.py Preserves backing arrays in duals.
uxarray/core/dataarray.py Adds lazy integration and transformations.
uxarray/core/aggregation.py Implements Dask topological aggregation.
test/test_cross_sections.py Tests lazy cross-sections.
test/precomputed_weights_test.py Tests lazy sparse remapping.
test/grid/integrate/test_basic.py Tests lazy integration.
test/core/test_topological_agg.py Tests Dask aggregations.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread uxarray/grid/slice.py
Comment on lines +136 to +139
def _build_remap(orig_indices, size):
remap = np.full(size, INT_FILL_VALUE, dtype=INT_DTYPE)
remap[orig_indices] = np.arange(len(orig_indices), dtype=INT_DTYPE)
return remap
Comment thread uxarray/grid/slice.py
Comment on lines +170 to +176
# Apply remapping (vectorized; stays lazy when the connectivity is dask)
ds[conn_name] = xr.apply_ufunc(
_remap_indices,
ds[conn_name],
kwargs={"remap": remap},
dask="parallelized",
output_dtypes=[INT_DTYPE],
Comment thread uxarray/core/aggregation.py Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-benchmark Run ASV benchmark workflow scalability Related to scalability & performance efforts

Projects

Status: 👀 In review

Development

Successfully merging this pull request may close these issues.

6 participants