Skip to content

Conversation

@rajeeja
Copy link
Contributor

@rajeeja rajeeja commented Dec 8, 2025

Fixes #98

This PR simplifies our cumulative integration story and documents how to call cumulative_integrate:

  • Removes the custom UxDataArray.cumulative_integrate override and the cross-section unit tests that depended on it.
  • Adds a cross-section notebook example showing cumulative integration along a great-circle arc (with a normalized distance
    coordinate).
  • Adds a general UxDataArray example using psi (outCSne30) to demonstrate cumulative integration over time, preserving uxgrid, and
    plotting the cumulative field over all faces at a single time slice with the Ux plot accessor.

Note: integrate vs cumulative_integrate

uxds.integrate(): Calculates total area/volume (definite integral) using grid geometry (Jacobians/face areas).
uxds.cumulative_integrate(): Calculates running accumulation (indefinite integral) along a specific coordinate path.

@rajeeja rajeeja requested a review from erogluorhan December 8, 2025 13:36
Copy link
Member

@erogluorhan erogluorhan left a comment

Choose a reason for hiding this comment

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

Thanks for putting this together! I think I am envisioning the cumulative_integrate user API a little differently than how it currently wraps the xarray one, i.e. actually a bit closer to the way the test case you've written around cross_sections handles it.

In other words, having the function signature similar to its xarray counterpart may not be intuitive since coords maybe quite different in the unstructured grids' case (e.g. node_lon, face_lat, etc. in contrast to structured lat/lon in xarray's case). Instead, shouldn't it be something like this (which is similar to our cross-sections):

def cumulative_integrate(self, lat=lat)
def cumulative_integrate(self, lon=lon)
def cumulative_integrate(self, start=(...), end=(...))

which should under the hood call our cross-section on them and then xarray
s cumulative-integrate on that result, i.e. the way the current test-case is implemented.

Or, instead of these, just do:

def cumulative_integrate(self, direction=direction)

where direction can get values of either "lon" or "lat". Not calling it coord intentionally because there is no "lat", 'lon' coords in uxarray datarrays.

Thoughts?

@rajeeja
Copy link
Contributor Author

rajeeja commented Dec 13, 2025

Thanks for putting this together! I think I am envisioning the cumulative_integrate user API a little differently than how it currently wraps the xarray one, i.e. actually a bit closer to the way the test case you've written around cross_sections handles it.

In other words, having the function signature similar to its xarray counterpart may not be intuitive since coords maybe quite different in the unstructured grids' case (e.g. node_lon, face_lat, etc. in contrast to structured lat/lon in xarray's case). Instead, shouldn't it be something like this (which is similar to our cross-sections):

def cumulative_integrate(self, lat=lat) def cumulative_integrate(self, lon=lon) def cumulative_integrate(self, start=(...), end=(...))

which should under the hood call our cross-section on them and then xarray s cumulative-integrate on that result, i.e. the way the current test-case is implemented.

Or, instead of these, just do:

def cumulative_integrate(self, direction=direction)

where direction can get values of either "lon" or "lat". Not calling it coord intentionally because there is no "lat", 'lon' coords in uxarray datarrays.

Thoughts?

Consider a time‑series UxDataArray on faces with dims ("time", "n_face"), we may want cumulative precipitation along time. With the proposed lat/lon/start-end/direction signature, there’s no way to specify the time coord—forcing a cross_section... I think the current xarray-like coord=... preserves these valid uses (time, level, custom distance) without forcing a cross_section that doesn’t apply.

@erogluorhan
Copy link
Member

erogluorhan commented Dec 15, 2025

Consider a time‑series UxDataArray on faces with dims ("time", "n_face"), we may want cumulative precipitation along time. With the proposed lat/lon/start-end/direction signature, there’s no way to specify the time coord—forcing a cross_section... I think the current xarray-like coord=... preserves these valid uses (time, level, custom distance) without forcing a cross_section that doesn’t apply.

Ok that makes sense to me.

However, I still guess that the cumulative integration after cross sections for lat/lon coords might be (probably the most) common use case though. Therefore:

  1. How it is currently realized may feel a bit counter-intutitive to user:

    cs = uxda.cross_section(...)
    cs = cs.assign_coords(distance=("steps", np.linspace(0.0, 1.0, cs.sizes["steps"])))
    
    cs_ux = ux.UxDataArray(cs, uxgrid=uxda.uxgrid)
    
    result = cs_ux.cumulative_integrate(coord="distance")
    

    and with current implementation, constructing a UxDataArray from cs maybe redundant, i.e. there is nothing grid-informed we are doing under our cumulative_integrate, so one can call xarray's func directly without needing a UxDataArray.

  2. Rather than asking user to handle the case (1) above manually themselves, should we consider wrapping it into our implementation as a convenience (i.e. maybe through wrapper functions or optional args in the single function)?

  3. If not (2), then we'd need to better document how to best use this func for most common scenarios like (1), not only in a user guide, but also in docstrings I think. Also, if not (2), do we really need this cumulative_integrate implementation on our end, i.e. the current implementation seems like unnecessary and this function could be used from xarray without any issues?

  4. Also, putting the test cases for this into test_cross_sections.py does not make complete sense to me, maybe better to put it into something under test/core?

@rajeeja
Copy link
Contributor Author

rajeeja commented Dec 15, 2025

  1. How it is currently realized may feel a bit counter-intutitive to user:
    cs = uxda.cross_section(...)
    cs = cs.assign_coords(distance=("steps", np.linspace(0.0, 1.0, cs.sizes["steps"])))
    
    cs_ux = ux.UxDataArray(cs, uxgrid=uxda.uxgrid)
    
    result = cs_ux.cumulative_integrate(coord="distance")
    
    and with current implementation, constructing a UxDataArray from cs maybe redundant, i.e. there is nothing grid-informed we are doing under our cumulative_integrate, so one can call xarray's func directly without needing a UxDataArray.
  1. Rather than asking user to handle the case (1) above manually themselves, should we consider wrapping it into our implementation as a convenience (i.e. maybe through wrapper functions or optional args in the single function)?

You're correct that wrapping the cross-section inside cumulative_integrate() feels tempting, but I think the current design is actually cleaner. The reason is that cumulative_integrate() is meant to be grid-agnostic—it just integrates along any coordinate. Once you have the cross-section extracted and a distance coordinate created, there's nothing uxarray-specific left to do.

  1. If not (2), then we'd need to better document how to best use this func for most common scenarios like (1), not only in a user guide, but also in docstrings I think.
  1. Also, putting the test cases for this into test_cross_sections.py does not make complete sense to me, maybe better to put it into something under test/core?

Strongly agree. I will make changes to make docstring examples showing the cross-section + integrate pattern
User guide section on "Integration workflows" with concrete examples (spatial, temporal, vertical)
Move tests from test_cross_sections.py to test/core/test_dataarray.py where core integration functionality belongs

@review-notebook-app
Copy link

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@rajeeja rajeeja requested a review from erogluorhan December 16, 2025 00:08
Copy link
Member

@erogluorhan erogluorhan left a comment

Choose a reason for hiding this comment

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

Looks great! Please don't forget to change the description of this PR accordingly for future reference.

@rajeeja rajeeja merged commit 3f05df9 into main Dec 16, 2025
13 checks passed
@iuryt
Copy link

iuryt commented Dec 16, 2025

This is great!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add integrate and some cumulative_integrate

4 participants