From 4151d1cb193a0c7afd0c65440cf992d0e8199e05 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Tue, 21 May 2024 08:12:26 -0400 Subject: [PATCH 01/31] ENH: Add cumulative integration function. --- src/metpy/calc/tools.py | 41 +++++++++++++++++++++++++++++++++++ tests/calc/test_calc_tools.py | 28 +++++++++++++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index 091545d8856..87c510ed182 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -1942,3 +1942,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(np.arange(5)) + array([0. , 0.5, 2. , 4.5, 8. ]) + >>> cumulative_integrate(xr.DataArray(np.arange(5), {"x": (("x",), np.ones(5), {"units": "m"})}, ("x",), "specific_humidity", {"units": "kg/kg"}), "x") + Date: Tue, 21 May 2024 08:23:37 -0400 Subject: [PATCH 02/31] STY: Ensure two blank lines between test functions. --- tests/calc/test_calc_tools.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/calc/test_calc_tools.py b/tests/calc/test_calc_tools.py index 0779cb88791..926811a302d 100644 --- a/tests/calc/test_calc_tools.py +++ b/tests/calc/test_calc_tools.py @@ -1565,6 +1565,7 @@ def test_cumulative_integrate_numpy(): integral = cumulative_integrate(field) assert integral == pytest.approx(np.array([0, 0.5, 2, 4.5, 8])) + def test_cumulative_integrate_pint(): field = np.arange(6) * units("kg/m^3") delta = np.array([1, 2, 3, 2, 1]) * units("cm") @@ -1573,6 +1574,7 @@ def test_cumulative_integrate_pint(): np.array([0, 0.5, 3.5, 11, 18, 22.5]) / 100 * units("kg/m^2") ) + def test_cumulative_integrate_xarray(): field = xr.DataArray( np.arange(10) / 100, From 9ca6384afdb7e86ba3d3b9f8fe5491281138d396 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Tue, 21 May 2024 16:28:07 -0400 Subject: [PATCH 03/31] BUG,STY: Fix NameError in test That's what I get for not paying attention when borrowing implementation while changing names. Also fix some lint errors. --- src/metpy/calc/tools.py | 7 +++++-- tests/calc/test_calc_tools.py | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index 87c510ed182..cd296639b8f 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -1968,13 +1968,16 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): -------- >>> cumulative_integrate(np.arange(5)) array([0. , 0.5, 2. , 4.5, 8. ]) - >>> cumulative_integrate(xr.DataArray(np.arange(5), {"x": (("x",), np.ones(5), {"units": "m"})}, ("x",), "specific_humidity", {"units": "kg/kg"}), "x") + >>> data = xr.DataArray( + ... np.arange(5), {"x": (("x",), np.ones(5), {"units": "m"})}, ("x",), "s", {"units": "kg/kg"} + ... ) + >>> cumulative_integrate(data, "x") Date: Thu, 23 May 2024 07:01:44 -0400 Subject: [PATCH 04/31] TST: Expand cumulative_integrate tests. --- tests/calc/test_calc_tools.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/tests/calc/test_calc_tools.py b/tests/calc/test_calc_tools.py index ebaa9f219b6..c00f42e3f70 100644 --- a/tests/calc/test_calc_tools.py +++ b/tests/calc/test_calc_tools.py @@ -1563,8 +1563,9 @@ def test_vector_derivative_return_subset(return_only, length): def test_cumulative_integrate_numpy(): """Test that cumulative_integrate works with numpy arrays.""" field = np.arange(5) - integral = cumulative_integrate(field) - assert integral == pytest.approx(np.array([0, 0.5, 2, 4.5, 8])) + integral = cumulative_integrate(field, delta=1) + assert integral.magnitude == pytest.approx(np.array([0, 0.5, 2, 4.5, 8])) + assert integral.units == units("dimensionless") def test_cumulative_integrate_pint(): @@ -1572,9 +1573,10 @@ def test_cumulative_integrate_pint(): 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 == pytest.approx( - np.array([0, 0.5, 3.5, 11, 18, 22.5]) / 100 * units("kg/m^2") + assert integral.magnitude == pytest.approx( + np.array([0, 0.5, 3.5, 11, 18, 22.5]) / 100 ) + assert integral.units == units("kg/m^2") def test_cumulative_integrate_xarray(): @@ -1584,7 +1586,19 @@ def test_cumulative_integrate_xarray(): {"x": (("x",), np.arange(100, 1001, 100), {"units": "hPa"})}, attrs={"units": "g/kg"} ) - integral = cumulative_integrate(field, "x") + integral = cumulative_integrate(field, axis="x") assert integral.values == pytest.approx( np.array([0, 0.5, 2, 4.5, 8, 12.5, 18, 24.5, 32, 40.5, 50]) ) + +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)), + {"x": ("x", arr, {"units": "m"}), "y": ("y", arr, {"units": "m"})}, + ("y", "x"), + "height", + {"units": "m"} + ) + integral = cumulative_integrate(field, axis="x") From 8ab2549fb1795c743a8586bf762765f12c15cc0c Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Thu, 23 May 2024 07:05:20 -0400 Subject: [PATCH 05/31] STY: Change double quotes to single. Docstrings are double quotes in the rest of the file. --- tests/calc/test_calc_tools.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/calc/test_calc_tools.py b/tests/calc/test_calc_tools.py index c00f42e3f70..6738373a819 100644 --- a/tests/calc/test_calc_tools.py +++ b/tests/calc/test_calc_tools.py @@ -1565,28 +1565,28 @@ def test_cumulative_integrate_numpy(): field = np.arange(5) integral = cumulative_integrate(field, delta=1) assert integral.magnitude == pytest.approx(np.array([0, 0.5, 2, 4.5, 8])) - assert integral.units == units("dimensionless") + assert integral.units == units('dimensionless') 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") + 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]) / 100 ) - assert integral.units == units("kg/m^2") + assert integral.units == units('kg/m^2') 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"} + {'x': (('x',), np.arange(100, 1001, 100), {'units': 'hPa'})}, + attrs={'units': 'g/kg'} ) - integral = cumulative_integrate(field, axis="x") + integral = cumulative_integrate(field, axis='x') assert integral.values == pytest.approx( np.array([0, 0.5, 2, 4.5, 8, 12.5, 18, 24.5, 32, 40.5, 50]) ) @@ -1596,9 +1596,9 @@ def test_cumulative_integrate_xr_2d(): arr = np.arange(5) data_xr = xr.DataArray( np.ones((5, 5)), - {"x": ("x", arr, {"units": "m"}), "y": ("y", arr, {"units": "m"})}, - ("y", "x"), - "height", - {"units": "m"} + {'x': ('x', arr, {'units': 'm'}), 'y': ('y', arr, {'units': 'm'})}, + ('y', 'x'), + 'height', + {'units': 'm'} ) - integral = cumulative_integrate(field, axis="x") + integral = cumulative_integrate(field, axis='x') From 6c1f2a76f39b0f2ea2df2f96204492a3f294be26 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Thu, 23 May 2024 07:14:59 -0400 Subject: [PATCH 06/31] BUG: Fix cumulative_integrate axis handling. --- src/metpy/calc/tools.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index cd296639b8f..f436ee4bdfd 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -1981,8 +1981,10 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): take = make_take(n, axis) right = np.cumsum( - 0.5 * (field[take(slice(1, None))] + field[take(slice(None, -1))]) * delta + 0.5 * (field[take(slice(1, None))] + field[take(slice(None, -1))]) * delta, + axis=axis ) - left = np.zeros_like(field[take(0)]) + left = np.zeros_like(field[take(slice(1))]) - return concatenate((left, right), axis=axis) + result = concatenate([left, right], axis=axis) + return result From 27f9fc555eca8f2caaaddfd8f878280c7869f217 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Thu, 23 May 2024 08:50:22 -0400 Subject: [PATCH 07/31] TST,BUG: Fix 2D XArray test for cumulative_integral --- tests/calc/test_calc_tools.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/calc/test_calc_tools.py b/tests/calc/test_calc_tools.py index 6738373a819..390e0a24977 100644 --- a/tests/calc/test_calc_tools.py +++ b/tests/calc/test_calc_tools.py @@ -1590,15 +1590,19 @@ def test_cumulative_integrate_xarray(): assert integral.values == pytest.approx( np.array([0, 0.5, 2, 4.5, 8, 12.5, 18, 24.5, 32, 40.5, 50]) ) + assert integral.metpy.units = units('g/(m s^2)') 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)), - {'x': ('x', arr, {'units': 'm'}), 'y': ('y', arr, {'units': 'm'})}, + {'y': ('y', arr, {'units': 'm'}), 'x': ('x', arr, {'units': 'm'})}, ('y', 'x'), 'height', {'units': 'm'} ) - integral = cumulative_integrate(field, axis='x') + integral = cumulative_integrate(data_xr, axis='x') + assert integral.dims == data_xr.dims + assert integral.coords == data_xr.coords + assert integral.metpy.units == units('m^2') From 08ed4ea6395211815c1b72d6db515cf323f44c53 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Fri, 24 May 2024 10:33:23 -0400 Subject: [PATCH 08/31] BUG: Fix syntax error in units check --- tests/calc/test_calc_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/calc/test_calc_tools.py b/tests/calc/test_calc_tools.py index 390e0a24977..736c4610e61 100644 --- a/tests/calc/test_calc_tools.py +++ b/tests/calc/test_calc_tools.py @@ -1590,7 +1590,7 @@ def test_cumulative_integrate_xarray(): assert integral.values == pytest.approx( np.array([0, 0.5, 2, 4.5, 8, 12.5, 18, 24.5, 32, 40.5, 50]) ) - assert integral.metpy.units = units('g/(m s^2)') + assert integral.metpy.units == units('g/(m s^2)') def test_cumulative_integrate_xr_2d(): """Test that cumulative_integrate works with 2D DataArrays.""" From 6ae4f6606d8b54ff28d8dd76158f35a837a075ef Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Fri, 24 May 2024 11:11:58 -0400 Subject: [PATCH 09/31] STY: wrap lines and fix tests. --- src/metpy/calc/tools.py | 4 +++- tests/calc/test_calc_tools.py | 7 ++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index f436ee4bdfd..1dc319a70ec 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -1969,7 +1969,9 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): >>> cumulative_integrate(np.arange(5)) array([0. , 0.5, 2. , 4.5, 8. ]) >>> data = xr.DataArray( - ... np.arange(5), {"x": (("x",), np.ones(5), {"units": "m"})}, ("x",), "s", {"units": "kg/kg"} + ... np.arange(5), + ... {"x": (("x",), np.ones(5), {"units": "m"})}, ("x",), + ... "s", {"units": "kg/kg"} ... ) >>> cumulative_integrate(data, "x") Date: Fri, 24 May 2024 11:24:57 -0400 Subject: [PATCH 10/31] TST: Fix test mismatches. --- tests/calc/test_calc_tools.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/calc/test_calc_tools.py b/tests/calc/test_calc_tools.py index 39ff2804fa0..bde1102f14c 100644 --- a/tests/calc/test_calc_tools.py +++ b/tests/calc/test_calc_tools.py @@ -16,12 +16,12 @@ 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, - resample_nn_1d, second_derivative, vector_derivative, - cumulative_integrate) + resample_nn_1d, second_derivative, vector_derivative) from metpy.calc.tools import (_delete_masked_points, _get_bound_pressure_height, _greater_or_close, _less_or_close, _next_non_masked_element, _remove_nans, azimuth_range_to_lat_lon, BASE_DEGREE_MULTIPLIER, - DIR_STRS, nominal_lat_lon_grid_deltas, parse_grid_arguments, UND) + DIR_STRS, nominal_lat_lon_grid_deltas, parse_grid_arguments, UND, + cumulative_integrate) from metpy.testing import (assert_almost_equal, assert_array_almost_equal, assert_array_equal, get_test_data) from metpy.units import units @@ -1576,7 +1576,7 @@ def test_cumulative_integrate_pint(): assert integral.magnitude == pytest.approx( np.array([0, 0.5, 3.5, 11, 18, 22.5]) ) - assert integral.units == units('kg/m^2') + assert integral.units == units('kg/m^2').units def test_cumulative_integrate_xarray(): @@ -1588,9 +1588,9 @@ def test_cumulative_integrate_xarray(): ) 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, 50]) + np.array([0, 0.5, 2, 4.5, 8, 12.5, 18, 24.5, 32, 40.5]) ) - assert integral.metpy.units == units('g/(m s^2)') + assert integral.metpy.units == units('g/(m s^2)').units def test_cumulative_integrate_xr_2d(): @@ -1606,4 +1606,4 @@ def test_cumulative_integrate_xr_2d(): integral = cumulative_integrate(data_xr, axis='x') assert integral.dims == data_xr.dims assert integral.coords.keys() == data_xr.coords.keys() - assert integral.metpy.units == units('m^2') + assert integral.metpy.units == units('m^2').units From c996909a2f9a351e862a2a96ea1d89258b017356 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Fri, 24 May 2024 11:29:51 -0400 Subject: [PATCH 11/31] STY: Sort import ordering. --- tests/calc/test_calc_tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/calc/test_calc_tools.py b/tests/calc/test_calc_tools.py index bde1102f14c..e9c51d57ebd 100644 --- a/tests/calc/test_calc_tools.py +++ b/tests/calc/test_calc_tools.py @@ -20,8 +20,8 @@ from metpy.calc.tools import (_delete_masked_points, _get_bound_pressure_height, _greater_or_close, _less_or_close, _next_non_masked_element, _remove_nans, azimuth_range_to_lat_lon, BASE_DEGREE_MULTIPLIER, - DIR_STRS, nominal_lat_lon_grid_deltas, parse_grid_arguments, UND, - cumulative_integrate) + cumulative_integrate, + DIR_STRS, nominal_lat_lon_grid_deltas, parse_grid_arguments, UND) from metpy.testing import (assert_almost_equal, assert_array_almost_equal, assert_array_equal, get_test_data) from metpy.units import units From cf5951d39ced77d5832832e16de2f02944d28056 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Sun, 26 May 2024 09:11:08 -0400 Subject: [PATCH 12/31] DOC: Add cumulative_integrate to metpy.calc Mathematical Funtions list. --- docs/_templates/overrides/metpy.calc.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_templates/overrides/metpy.calc.rst b/docs/_templates/overrides/metpy.calc.rst index e5e7a4f787f..a3be725bc3e 100644 --- a/docs/_templates/overrides/metpy.calc.rst +++ b/docs/_templates/overrides/metpy.calc.rst @@ -164,6 +164,7 @@ Mathematical Functions tangential_component unit_vectors_from_cross_section vector_derivative + cumulative_integrate Apparent Temperature From 4af2eb1b95f328936555490deda0ef6053a6d28f Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Mon, 27 May 2024 08:51:07 -0400 Subject: [PATCH 13/31] ENH: Switch to using scipy.integrate.cumulative_trapezoid for calculations. I might need to pull out the derivation of x when I add integrate. --- src/metpy/calc/tools.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index 1dc319a70ec..5672c9e2af3 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -18,6 +18,7 @@ import numpy.ma as ma from pyproj import CRS, Geod, Proj from scipy.spatial import cKDTree +from scipy.integrate import cumulative_trapezoid import xarray as xr from .. import _warnings @@ -1981,12 +1982,8 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): """ n, axis, delta = _process_deriv_args(field, axis, x, delta) take = make_take(n, axis) - - right = np.cumsum( - 0.5 * (field[take(slice(1, None))] + field[take(slice(None, -1))]) * delta, - axis=axis - ) - left = np.zeros_like(field[take(slice(1))]) - - result = concatenate([left, right], axis=axis) - return result + right = np.cumsum(delta, axis=axis) + left = np.zeros_like(right[take(slice(1))]) + x = concatenate([left, right], axis=axis) + result = cumulative_trapezoid(field, x=x, axis=axis, initial=0) + return units.Quantity(result, field.units * x.units) From 5eb45ccfd94332a57c7e6bffa569a05160977057 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Mon, 27 May 2024 09:06:20 -0400 Subject: [PATCH 14/31] FIX: Pass magnitudes to unit-unaware functions. --- src/metpy/calc/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index 5672c9e2af3..e2c2afcf59f 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -1985,5 +1985,5 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): right = np.cumsum(delta, axis=axis) left = np.zeros_like(right[take(slice(1))]) x = concatenate([left, right], axis=axis) - result = cumulative_trapezoid(field, x=x, axis=axis, initial=0) + result = cumulative_trapezoid(field.magnitude, x=x.magnitude, axis=axis, initial=0) return units.Quantity(result, field.units * x.units) From 609c44a2c7d6649723f58a1b66c1dc50e83c774e Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Mon, 27 May 2024 09:36:32 -0400 Subject: [PATCH 15/31] TST: Fix unit handling. --- src/metpy/calc/tools.py | 5 ++++- tests/calc/test_calc_tools.py | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index e2c2afcf59f..d78d4e7d263 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -1986,4 +1986,7 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): left = np.zeros_like(right[take(slice(1))]) x = concatenate([left, right], axis=axis) result = cumulative_trapezoid(field.magnitude, x=x.magnitude, axis=axis, initial=0) - return units.Quantity(result, field.units * x.units) + try: + return units.Quantity(result, field.units * x.units) + except AttributeError: + return units.Quantity(result, '') diff --git a/tests/calc/test_calc_tools.py b/tests/calc/test_calc_tools.py index e9c51d57ebd..99b11257fd1 100644 --- a/tests/calc/test_calc_tools.py +++ b/tests/calc/test_calc_tools.py @@ -1576,7 +1576,7 @@ def test_cumulative_integrate_pint(): assert integral.magnitude == pytest.approx( np.array([0, 0.5, 3.5, 11, 18, 22.5]) ) - assert integral.units == units('kg/m^2').units + assert units.Quantity(1, integral.units).to('kg/m^2').magnitude == 1 def test_cumulative_integrate_xarray(): @@ -1590,7 +1590,7 @@ def test_cumulative_integrate_xarray(): assert integral.metpy.magnitude == pytest.approx( np.array([0, 0.5, 2, 4.5, 8, 12.5, 18, 24.5, 32, 40.5]) ) - assert integral.metpy.units == units('g/(m s^2)').units + assert units.Quantity(1, integral.metpy.units).to('hg/(m s^2)').magnitude == 1 def test_cumulative_integrate_xr_2d(): @@ -1606,4 +1606,4 @@ def test_cumulative_integrate_xr_2d(): integral = cumulative_integrate(data_xr, axis='x') assert integral.dims == data_xr.dims assert integral.coords.keys() == data_xr.coords.keys() - assert integral.metpy.units == units('m^2').units + assert units.Quantity(1, integral.metpy.units).to('m^2').magnitude == 1 From 3b6ac919cab8266f81a4b54a3c08dc478ca42d40 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Mon, 27 May 2024 09:53:59 -0400 Subject: [PATCH 16/31] TST: Fix unit tests. --- src/metpy/calc/tools.py | 2 +- tests/calc/test_calc_tools.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index d78d4e7d263..fdbdb9542ea 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -1989,4 +1989,4 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): try: return units.Quantity(result, field.units * x.units) except AttributeError: - return units.Quantity(result, '') + return units.Quantity(result, 'dimensionless') diff --git a/tests/calc/test_calc_tools.py b/tests/calc/test_calc_tools.py index 99b11257fd1..cccdabaed14 100644 --- a/tests/calc/test_calc_tools.py +++ b/tests/calc/test_calc_tools.py @@ -1565,7 +1565,7 @@ def test_cumulative_integrate_numpy(): field = np.arange(5) integral = cumulative_integrate(field, delta=1) assert integral.magnitude == pytest.approx(np.array([0, 0.5, 2, 4.5, 8])) - assert integral.units == units('dimensionless') + assert str(integral.units) == 'dimensionless' def test_cumulative_integrate_pint(): @@ -1576,7 +1576,7 @@ def test_cumulative_integrate_pint(): assert integral.magnitude == pytest.approx( np.array([0, 0.5, 3.5, 11, 18, 22.5]) ) - assert units.Quantity(1, integral.units).to('kg/m^2').magnitude == 1 + assert units.Quantity(1, integral.units).to('dg/m^2').magnitude == 1 def test_cumulative_integrate_xarray(): From c15f4bb2290713422d550381be6126687cd9e702 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Mon, 27 May 2024 10:07:39 -0400 Subject: [PATCH 17/31] TST,FIX: Fix units in test, the right way this time. --- tests/calc/test_calc_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/calc/test_calc_tools.py b/tests/calc/test_calc_tools.py index cccdabaed14..191caedac9f 100644 --- a/tests/calc/test_calc_tools.py +++ b/tests/calc/test_calc_tools.py @@ -1576,7 +1576,7 @@ def test_cumulative_integrate_pint(): assert integral.magnitude == pytest.approx( np.array([0, 0.5, 3.5, 11, 18, 22.5]) ) - assert units.Quantity(1, integral.units).to('dg/m^2').magnitude == 1 + assert units.Quantity(1, integral.units).to('dag/m^2').magnitude == 1 def test_cumulative_integrate_xarray(): From 480d5d1a9555de2015d2a2f5eb308c892692772f Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Mon, 27 May 2024 12:30:00 -0400 Subject: [PATCH 18/31] BUG: Add back-up for unitless arguments. Possibly avoidable, given cumulative_trapezoid already handles that case. Should I raise a ValueError to add units or re-direct to cumulative_trapezoid? --- src/metpy/calc/tools.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index fdbdb9542ea..6d4c0e5b590 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -1985,8 +1985,10 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): right = np.cumsum(delta, axis=axis) left = np.zeros_like(right[take(slice(1))]) x = concatenate([left, right], axis=axis) - result = cumulative_trapezoid(field.magnitude, x=x.magnitude, axis=axis, initial=0) try: + result = cumulative_trapezoid(field.magnitude, x=x.magnitude, axis=axis, initial=0) return units.Quantity(result, field.units * x.units) except AttributeError: + # NumPy arrays without units + result = cumulative_trapezoid(field, x, axis=axis, initial=0) return units.Quantity(result, 'dimensionless') From c7e721e865d27af28f0e2b4b630f242833e58df8 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Mon, 27 May 2024 13:22:07 -0400 Subject: [PATCH 19/31] Stop trying to handle unitless NumPy arrays. scipy.integrate.cumulative_integrate already handles them. --- src/metpy/calc/tools.py | 6 ++++-- tests/calc/test_calc_tools.py | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index 6d4c0e5b590..be3efb759db 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -1990,5 +1990,7 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): return units.Quantity(result, field.units * x.units) except AttributeError: # NumPy arrays without units - result = cumulative_trapezoid(field, x, axis=axis, initial=0) - return units.Quantity(result, 'dimensionless') + raise ValueError( + "cumulative_integrate called with unitless arguments\n" + "Either add units or use scipy.integrate.cumulative_trapezoid" + ) diff --git a/tests/calc/test_calc_tools.py b/tests/calc/test_calc_tools.py index 191caedac9f..96c1c83c15d 100644 --- a/tests/calc/test_calc_tools.py +++ b/tests/calc/test_calc_tools.py @@ -1563,9 +1563,10 @@ def test_vector_derivative_return_subset(return_only, length): def test_cumulative_integrate_numpy(): """Test that cumulative_integrate works with numpy arrays.""" field = np.arange(5) - integral = cumulative_integrate(field, delta=1) - assert integral.magnitude == pytest.approx(np.array([0, 0.5, 2, 4.5, 8])) - assert str(integral.units) == 'dimensionless' + with pytest.raises( + ValueError, "cumulative_integrate called with unitless arguments" + ): + integral = cumulative_integrate(field, delta=1) def test_cumulative_integrate_pint(): From 25f6bf1e7e17cdec513bd19be37a0e3425746ab5 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Mon, 27 May 2024 14:23:50 -0400 Subject: [PATCH 20/31] STY: Drop assignment to unused variable --- tests/calc/test_calc_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/calc/test_calc_tools.py b/tests/calc/test_calc_tools.py index 96c1c83c15d..1da8a86d0a4 100644 --- a/tests/calc/test_calc_tools.py +++ b/tests/calc/test_calc_tools.py @@ -1566,7 +1566,7 @@ def test_cumulative_integrate_numpy(): with pytest.raises( ValueError, "cumulative_integrate called with unitless arguments" ): - integral = cumulative_integrate(field, delta=1) + cumulative_integrate(field, delta=1) def test_cumulative_integrate_pint(): From 2388547d49306f2971119ed87c8fcb1b0b107742 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Mon, 27 May 2024 14:41:14 -0400 Subject: [PATCH 21/31] TST,BUG: Fix pytest.raises decorator --- tests/calc/test_calc_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/calc/test_calc_tools.py b/tests/calc/test_calc_tools.py index 1da8a86d0a4..cbf15303a5a 100644 --- a/tests/calc/test_calc_tools.py +++ b/tests/calc/test_calc_tools.py @@ -1564,7 +1564,7 @@ def test_cumulative_integrate_numpy(): """Test that cumulative_integrate works with numpy arrays.""" field = np.arange(5) with pytest.raises( - ValueError, "cumulative_integrate called with unitless arguments" + ValueError, match="cumulative_integrate called with unitless arguments" ): cumulative_integrate(field, delta=1) From 0935278e034dd015de80716f49078aa8ca84bc7b Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Mon, 27 May 2024 15:40:49 -0400 Subject: [PATCH 22/31] TST,BUG: Fix type of error in test. --- tests/calc/test_calc_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/calc/test_calc_tools.py b/tests/calc/test_calc_tools.py index cbf15303a5a..ae1247f47a8 100644 --- a/tests/calc/test_calc_tools.py +++ b/tests/calc/test_calc_tools.py @@ -1564,7 +1564,7 @@ def test_cumulative_integrate_numpy(): """Test that cumulative_integrate works with numpy arrays.""" field = np.arange(5) with pytest.raises( - ValueError, match="cumulative_integrate called with unitless arguments" + TypeError, match="cumulative_integrate called with unitless arguments" ): cumulative_integrate(field, delta=1) From 1b5d30bdd52a6b1ce00d0271f101585621ddae34 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Mon, 27 May 2024 15:57:03 -0400 Subject: [PATCH 23/31] BUG: Fix exception type to match test. --- src/metpy/calc/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index be3efb759db..51203323d6f 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -1990,7 +1990,7 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): return units.Quantity(result, field.units * x.units) except AttributeError: # NumPy arrays without units - raise ValueError( + raise TypeError( "cumulative_integrate called with unitless arguments\n" "Either add units or use scipy.integrate.cumulative_trapezoid" ) From a5d95e4594d4a00187661aa697fa8c7ceed5f64e Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Mon, 27 May 2024 16:13:32 -0400 Subject: [PATCH 24/31] DOC: Fix example for cumulative_integrate. --- src/metpy/calc/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index 51203323d6f..9eee41704bf 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -1967,7 +1967,7 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): Examples -------- - >>> cumulative_integrate(np.arange(5)) + >>> cumulative_integrate(units.Quantity(np.arange(5), "m"), delta=units("1 m")) array([0. , 0.5, 2. , 4.5, 8. ]) >>> data = xr.DataArray( ... np.arange(5), From 40a738601f53675ee17c09798afd5f86284c3b38 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Mon, 27 May 2024 16:28:51 -0400 Subject: [PATCH 25/31] DOC,FIX: Fix example output formatting. --- src/metpy/calc/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index 9eee41704bf..eb3b28a82d5 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -1968,7 +1968,7 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): Examples -------- >>> cumulative_integrate(units.Quantity(np.arange(5), "m"), delta=units("1 m")) - array([0. , 0.5, 2. , 4.5, 8. ]) + >>> data = xr.DataArray( ... np.arange(5), ... {"x": (("x",), np.ones(5), {"units": "m"})}, ("x",), From a9de5755c20dc7c1449784a669d268297f533d44 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Mon, 27 May 2024 16:46:51 -0400 Subject: [PATCH 26/31] DOC,FIX: Import XArray for that example. --- src/metpy/calc/tools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index eb3b28a82d5..d3cc2e0f5d2 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -1969,6 +1969,7 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): -------- >>> cumulative_integrate(units.Quantity(np.arange(5), "m"), delta=units("1 m")) + >>> import xarray as xr >>> data = xr.DataArray( ... np.arange(5), ... {"x": (("x",), np.ones(5), {"units": "m"})}, ("x",), From 4d0379ba7c1b67307d94348c94e66ac45c96432f Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Mon, 27 May 2024 17:05:02 -0400 Subject: [PATCH 27/31] DOC,FIX: Fix call to cumulative_integrate. --- src/metpy/calc/tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index d3cc2e0f5d2..abe4b328bde 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -1975,9 +1975,9 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): ... {"x": (("x",), np.ones(5), {"units": "m"})}, ("x",), ... "s", {"units": "kg/kg"} ... ) - >>> cumulative_integrate(data, "x") + >>> cumulative_integrate(data, axis="x") Date: Mon, 27 May 2024 19:25:34 -0400 Subject: [PATCH 28/31] Use units for delta, not x, for finding final units. --- src/metpy/calc/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index abe4b328bde..a9dc93bf9e4 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -1988,7 +1988,7 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): 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 * x.units) + return units.Quantity(result, field.units * delta.units) except AttributeError: # NumPy arrays without units raise TypeError( From 9b82a80f53b2afd0558c368e716d3aa200bd6585 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Mon, 27 May 2024 19:26:39 -0400 Subject: [PATCH 29/31] DOC: Remove example with results differing by OS. Windows is fine with commas, Mac wants decimal points and skips the commas. --- src/metpy/calc/tools.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index a9dc93bf9e4..9ba1b52429e 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -1969,17 +1969,6 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): -------- >>> cumulative_integrate(units.Quantity(np.arange(5), "m"), delta=units("1 m")) - >>> import xarray as xr - >>> data = xr.DataArray( - ... np.arange(5), - ... {"x": (("x",), np.ones(5), {"units": "m"})}, ("x",), - ... "s", {"units": "kg/kg"} - ... ) - >>> cumulative_integrate(data, axis="x") - Date: Mon, 27 May 2024 21:09:18 -0400 Subject: [PATCH 30/31] STY: Fix ruff errors. --- src/metpy/calc/tools.py | 8 ++++---- tests/calc/test_calc_tools.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index 9ba1b52429e..fc18e4d54b3 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -1967,7 +1967,7 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): Examples -------- - >>> cumulative_integrate(units.Quantity(np.arange(5), "m"), delta=units("1 m")) + >>> cumulative_integrate(units.Quantity(np.arange(5), 'm'), delta=units('1 m')) """ n, axis, delta = _process_deriv_args(field, axis, x, delta) @@ -1981,6 +1981,6 @@ def cumulative_integrate(field, axis=None, x=None, delta=None): except AttributeError: # NumPy arrays without units raise TypeError( - "cumulative_integrate called with unitless arguments\n" - "Either add units or use scipy.integrate.cumulative_trapezoid" - ) + 'cumulative_integrate called with unitless arguments\n' + 'Either add units or use scipy.integrate.cumulative_trapezoid' + ) from None diff --git a/tests/calc/test_calc_tools.py b/tests/calc/test_calc_tools.py index ae1247f47a8..0b7acd47bee 100644 --- a/tests/calc/test_calc_tools.py +++ b/tests/calc/test_calc_tools.py @@ -1564,7 +1564,7 @@ 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" + TypeError, match='cumulative_integrate called with unitless arguments' ): cumulative_integrate(field, delta=1) From 5d72db3d51d31fc09cf0893bea5f05c8954c004e Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Mon, 27 May 2024 21:14:35 -0400 Subject: [PATCH 31/31] STY: Fix flake8 errors. --- src/metpy/calc/tools.py | 2 +- tests/calc/test_calc_tools.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/metpy/calc/tools.py b/src/metpy/calc/tools.py index fc18e4d54b3..323fb91eb9a 100644 --- a/src/metpy/calc/tools.py +++ b/src/metpy/calc/tools.py @@ -17,8 +17,8 @@ import numpy.ma as ma from pyproj import CRS, Geod, Proj -from scipy.spatial import cKDTree from scipy.integrate import cumulative_trapezoid +from scipy.spatial import cKDTree import xarray as xr from .. import _warnings diff --git a/tests/calc/test_calc_tools.py b/tests/calc/test_calc_tools.py index 0b7acd47bee..df640d86e2c 100644 --- a/tests/calc/test_calc_tools.py +++ b/tests/calc/test_calc_tools.py @@ -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, @@ -20,7 +21,6 @@ from metpy.calc.tools import (_delete_masked_points, _get_bound_pressure_height, _greater_or_close, _less_or_close, _next_non_masked_element, _remove_nans, azimuth_range_to_lat_lon, BASE_DEGREE_MULTIPLIER, - cumulative_integrate, DIR_STRS, nominal_lat_lon_grid_deltas, parse_grid_arguments, UND) from metpy.testing import (assert_almost_equal, assert_array_almost_equal, assert_array_equal, get_test_data)