Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4151d1c
ENH: Add cumulative integration function.
DWesl May 21, 2024
115415d
STY: Ensure two blank lines between test functions.
DWesl May 21, 2024
9ca6384
BUG,STY: Fix NameError in test
DWesl May 21, 2024
9d9dd0d
TST: Expand cumulative_integrate tests.
DWesl May 23, 2024
8ab2549
STY: Change double quotes to single.
DWesl May 23, 2024
6c1f2a7
BUG: Fix cumulative_integrate axis handling.
DWesl May 23, 2024
27f9fc5
TST,BUG: Fix 2D XArray test for cumulative_integral
DWesl May 23, 2024
08ed4ea
BUG: Fix syntax error in units check
DWesl May 24, 2024
6ae4f66
STY: wrap lines and fix tests.
DWesl May 24, 2024
f5bf47b
TST: Fix test mismatches.
DWesl May 24, 2024
c996909
STY: Sort import ordering.
DWesl May 24, 2024
cf5951d
DOC: Add cumulative_integrate to metpy.calc Mathematical Funtions list.
DWesl May 26, 2024
4af2eb1
ENH: Switch to using scipy.integrate.cumulative_trapezoid for calcula…
DWesl May 27, 2024
5eb45cc
FIX: Pass magnitudes to unit-unaware functions.
DWesl May 27, 2024
609c44a
TST: Fix unit handling.
DWesl May 27, 2024
3b6ac91
TST: Fix unit tests.
DWesl May 27, 2024
c15f4bb
TST,FIX: Fix units in test, the right way this time.
DWesl May 27, 2024
480d5d1
BUG: Add back-up for unitless arguments.
DWesl May 27, 2024
c7e721e
Stop trying to handle unitless NumPy arrays.
DWesl May 27, 2024
25f6bf1
STY: Drop assignment to unused variable
DWesl May 27, 2024
2388547
TST,BUG: Fix pytest.raises decorator
DWesl May 27, 2024
0935278
TST,BUG: Fix type of error in test.
DWesl May 27, 2024
1b5d30b
BUG: Fix exception type to match test.
DWesl May 27, 2024
a5d95e4
DOC: Fix example for cumulative_integrate.
DWesl May 27, 2024
40a7386
DOC,FIX: Fix example output formatting.
DWesl May 27, 2024
a9de575
DOC,FIX: Import XArray for that example.
DWesl May 27, 2024
4d0379b
DOC,FIX: Fix call to cumulative_integrate.
DWesl May 27, 2024
b51682b
Use units for delta, not x, for finding final units.
DWesl May 27, 2024
9b82a80
DOC: Remove example with results differing by OS.
DWesl May 27, 2024
5a663e1
STY: Fix ruff errors.
DWesl May 28, 2024
5d72db3
STY: Fix flake8 errors.
DWesl May 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/_templates/overrides/metpy.calc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ Mathematical Functions
tangential_component
unit_vectors_from_cross_section
vector_derivative
cumulative_integrate


Apparent Temperature
Expand Down
42 changes: 42 additions & 0 deletions src/metpy/calc/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import numpy.ma as ma
from pyproj import CRS, Geod, Proj
from scipy.integrate import cumulative_trapezoid
from scipy.spatial import cKDTree
import xarray as xr

Expand Down Expand Up @@ -1942,3 +1943,44 @@ def _remove_nans(*variables):
for v in variables:
ret.append(v[~mask])
return ret


@exporter.export
@xarray_derivative_wrap
def cumulative_integrate(field, axis=None, x=None, delta=None):
"""Return cumulative integral of field along axis.

Uses trapezoidal rule for integration.

Parameters
----------
field : array-like
Array of values for which to calculate the integral
axis : int or str
The axis along which to integrate. If `field` is an
`np.ndarray` or `pint.Quantity`, must be an integer. Defaults
to zero.
x : array-like, optional
The coordinate values along which to integrate
delta : array-like, optional
Spacing between grid points in `field`.

Examples
--------
>>> cumulative_integrate(units.Quantity(np.arange(5), 'm'), delta=units('1 m'))
<Quantity([0. 0.5 2. 4.5 8. ], 'meter ** 2')>
"""
n, axis, delta = _process_deriv_args(field, axis, x, delta)
take = make_take(n, axis)
right = np.cumsum(delta, axis=axis)
left = np.zeros_like(right[take(slice(1))])
x = concatenate([left, right], axis=axis)
try:
result = cumulative_trapezoid(field.magnitude, x=x.magnitude, axis=axis, initial=0)
return units.Quantity(result, field.units * delta.units)
except AttributeError:
# NumPy arrays without units
raise TypeError(
'cumulative_integrate called with unitless arguments\n'
'Either add units or use scipy.integrate.cumulative_trapezoid'
) from None
53 changes: 52 additions & 1 deletion tests/calc/test_calc_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
import pytest
import xarray as xr

from metpy.calc import (angle_to_direction, find_bounding_indices, find_intersections,
from metpy.calc import (angle_to_direction, cumulative_integrate,
find_bounding_indices, find_intersections,
first_derivative, geospatial_gradient, get_layer, get_layer_heights,
gradient, laplacian, lat_lon_grid_deltas, nearest_intersection_idx,
parse_angle, pressure_to_height_std, reduce_point_density,
Expand Down Expand Up @@ -1557,3 +1558,53 @@ def test_vector_derivative_return_subset(return_only, length):
u, v, longitude=lons, latitude=lats, crs=crs, return_only=return_only)

assert len(ddx) == length


def test_cumulative_integrate_numpy():
"""Test that cumulative_integrate works with numpy arrays."""
field = np.arange(5)
with pytest.raises(
TypeError, match='cumulative_integrate called with unitless arguments'
):
cumulative_integrate(field, delta=1)


def test_cumulative_integrate_pint():
"""Test that cumulative_integrate works with pint Quantities."""
field = np.arange(6) * units('kg/m^3')
delta = np.array([1, 2, 3, 2, 1]) * units('cm')
integral = cumulative_integrate(field, delta=delta)
assert integral.magnitude == pytest.approx(
np.array([0, 0.5, 3.5, 11, 18, 22.5])
)
assert units.Quantity(1, integral.units).to('dag/m^2').magnitude == 1


def test_cumulative_integrate_xarray():
"""Test that cumulative_integrate works with XArray DataArrays."""
field = xr.DataArray(
np.arange(10) / 100,
{'x': (('x',), np.arange(100, 1001, 100), {'units': 'hPa'})},
attrs={'units': 'g/kg'}
)
integral = cumulative_integrate(field, axis='x')
assert integral.metpy.magnitude == pytest.approx(
np.array([0, 0.5, 2, 4.5, 8, 12.5, 18, 24.5, 32, 40.5])
)
assert units.Quantity(1, integral.metpy.units).to('hg/(m s^2)').magnitude == 1


def test_cumulative_integrate_xr_2d():
"""Test that cumulative_integrate works with 2D DataArrays."""
arr = np.arange(5)
data_xr = xr.DataArray(
np.ones((5, 5)),
{'y': ('y', arr, {'units': 'm'}), 'x': ('x', arr, {'units': 'm'})},
('y', 'x'),
'height',
{'units': 'm'}
)
integral = cumulative_integrate(data_xr, axis='x')
assert integral.dims == data_xr.dims
assert integral.coords.keys() == data_xr.coords.keys()
assert units.Quantity(1, integral.metpy.units).to('m^2').magnitude == 1