Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switching to Quantity-in-DataArray/NEP 18 Support over DataArray-with-units-attribute #1325

Merged
merged 12 commits into from
Apr 14, 2020
Merged
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.DS_Store
*.swp
.idea/
.vscode/

*.pyc

Expand Down
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ matrix:
include:
- python: 3.6
env:
- VERSIONS="numpy==1.13.0 matplotlib==2.1.0 scipy==1.0.0 pint==0.8 xarray==0.12.3 pandas==0.22.0"
- VERSIONS="numpy==1.16.0 matplotlib==2.1.0 scipy==1.0.0 pint==0.10.1 xarray==0.13.0 pandas==0.22.0"
- TASK="coverage"
- TEST_OUTPUT_CONTROL=""
- python: "3.8-dev"
Expand Down Expand Up @@ -126,6 +126,7 @@ before_script:

script:
- export TEST_DATA_DIR=${TRAVIS_BUILD_DIR}/staticdata;
- export NUMPY_EXPERIMENTAL_ARRAY_FUNCTION=1;
- if [[ $TASK == "docs" ]]; then
pushd docs;
make clean overridecheck html linkcheck O=-W;
Expand Down
6 changes: 3 additions & 3 deletions docs/installguide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ In general, MetPy tries to support minor versions of dependencies released withi
years. For Python itself, that means supporting the last two minor releases.

* matplotlib >= 2.1.0
* numpy >= 1.13.0
* numpy >= 1.16.0
* scipy >= 1.0.0
* pint >= 0.8
* pint >= 0.10.1
* pandas >= 0.22.0
* xarray >= 0.12.3
* xarray >= 0.13.0
* traitlets >= 4.3.0
* pooch >= 0.1

Expand Down
6 changes: 3 additions & 3 deletions examples/Four_Panel_Map.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ def plot_background(ax):

# Do unit conversions to what we wish to plot
vort_500 = vort_500 * 1e5
surface_temp.metpy.convert_units('degF')
precip_water.metpy.convert_units('inches')
winds_300.metpy.convert_units('knots')
surface_temp = surface_temp.metpy.convert_units('degF')
precip_water = precip_water.metpy.convert_units('inches')
winds_300 = winds_300.metpy.convert_units('knots')

###########################################

Expand Down
4 changes: 2 additions & 2 deletions examples/cross_section.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@
dims=specific_humidity.dims,
attrs={'units': rh.units})

cross['u_wind'].metpy.convert_units('knots')
cross['v_wind'].metpy.convert_units('knots')
cross['u_wind'] = cross['u_wind'].metpy.convert_units('knots')
cross['v_wind'] = cross['v_wind'].metpy.convert_units('knots')
cross['t_wind'], cross['n_wind'] = mpcalc.cross_section_components(cross['u_wind'],
cross['v_wind'])

Expand Down
6 changes: 3 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ setup_requires = setuptools_scm
python_requires = >=3.6
install_requires =
matplotlib>=2.1.0
numpy>=1.13.0
numpy>=1.16.0
scipy>=1.0
pint>=0.8
xarray>=0.12.3
pint>=0.10.1
xarray>=0.13.0
pooch>=0.1
traitlets>=4.3.0
pandas>=0.22.0
Expand Down
16 changes: 8 additions & 8 deletions src/metpy/calc/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from .tools import wrap_output_like
from .. import constants as mpconsts
from ..package_tools import Exporter
from ..units import atleast_1d, check_units, masked_array, units
from ..units import check_units, masked_array, units
from ..xarray import preprocess_xarray

exporter = Exporter(globals())
Expand Down Expand Up @@ -90,7 +90,7 @@ def wind_direction(u, v, convention='from'):
"""
wdir = 90. * units.deg - np.arctan2(-v, -u)
origshape = wdir.shape
wdir = atleast_1d(wdir)
wdir = np.atleast_1d(wdir)

# Handle oceanographic convection
if convention == 'to':
Expand Down Expand Up @@ -244,8 +244,8 @@ def heat_index(temperature, relative_humidity, mask_undefined=True):
windchill

"""
temperature = atleast_1d(temperature)
relative_humidity = atleast_1d(relative_humidity)
temperature = np.atleast_1d(temperature)
relative_humidity = np.atleast_1d(relative_humidity)
# assign units to relative_humidity if they currently are not present
if not hasattr(relative_humidity, 'units'):
relative_humidity = relative_humidity * units.dimensionless
Expand Down Expand Up @@ -357,9 +357,9 @@ def apparent_temperature(temperature, relative_humidity, speed, face_level_winds
"""
is_not_scalar = isinstance(temperature.m, (list, tuple, np.ndarray))

temperature = atleast_1d(temperature)
relative_humidity = atleast_1d(relative_humidity)
speed = atleast_1d(speed)
temperature = np.atleast_1d(temperature)
relative_humidity = np.atleast_1d(relative_humidity)
speed = np.atleast_1d(speed)

# NB: mask_defined=True is needed to know where computed values exist
wind_chill_temperature = windchill(temperature, speed, face_level_winds=face_level_winds,
Expand Down Expand Up @@ -388,7 +388,7 @@ def apparent_temperature(temperature, relative_humidity, speed, face_level_winds
if is_not_scalar:
return app_temperature
else:
return atleast_1d(app_temperature)[0]
return np.atleast_1d(app_temperature)[0]


@exporter.export
Expand Down
40 changes: 15 additions & 25 deletions src/metpy/calc/cross_sections.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .basic import coriolis_parameter
from .tools import first_derivative
from ..package_tools import Exporter
from ..units import units
from ..xarray import check_axis, check_matching_coordinates

exporter = Exporter(globals())
Expand Down Expand Up @@ -49,8 +50,8 @@ def distances_from_cross_section(cross):
y = distance * np.cos(np.deg2rad(forward_az))

# Build into DataArrays
x = xr.DataArray(x, coords=lon.coords, dims=lon.dims, attrs={'units': 'meters'})
y = xr.DataArray(y, coords=lat.coords, dims=lat.dims, attrs={'units': 'meters'})
x = xr.DataArray(x * units.meter, coords=lon.coords, dims=lon.dims)
y = xr.DataArray(y * units.meter, coords=lat.coords, dims=lat.dims)

elif check_axis(cross.metpy.x, 'x') and check_axis(cross.metpy.y, 'y'):

Expand Down Expand Up @@ -86,8 +87,7 @@ def latitude_from_cross_section(cross):
latitude = ccrs.Geodetic().transform_points(cross.metpy.cartopy_crs,
cross.metpy.x.values,
y.values)[..., 1]
latitude = xr.DataArray(latitude, coords=y.coords, dims=y.dims,
attrs={'units': 'degrees_north'})
latitude = xr.DataArray(latitude * units.degrees_north, coords=y.coords, dims=y.dims)
return latitude


Expand Down Expand Up @@ -165,10 +165,6 @@ def cross_section_components(data_x, data_y, index='index'):
component_tang = data_x * unit_tang[0] + data_y * unit_tang[1]
component_norm = data_x * unit_norm[0] + data_y * unit_norm[1]

# Reattach units (only reliable attribute after operation)
component_tang.attrs = {'units': data_x.attrs['units']}
component_norm.attrs = {'units': data_x.attrs['units']}

return component_tang, component_norm


Expand Down Expand Up @@ -207,9 +203,8 @@ def normal_component(data_x, data_y, index='index'):
component_norm = data_x * unit_norm[0] + data_y * unit_norm[1]

# Reattach only reliable attributes after operation
for attr in ('units', 'grid_mapping'):
if attr in data_x.attrs:
component_norm.attrs[attr] = data_x.attrs[attr]
if 'grid_mapping' in data_x.attrs:
component_norm.attrs['grid_mapping'] = data_x.attrs['grid_mapping']

return component_norm

Expand Down Expand Up @@ -249,9 +244,8 @@ def tangential_component(data_x, data_y, index='index'):
component_tang = data_x * unit_tang[0] + data_y * unit_tang[1]

# Reattach only reliable attributes after operation
for attr in ('units', 'grid_mapping'):
if attr in data_x.attrs:
component_tang.attrs[attr] = data_x.attrs[attr]
if 'grid_mapping' in data_x.attrs:
component_tang.attrs['grid_mapping'] = data_x.attrs['grid_mapping']

return component_tang

Expand Down Expand Up @@ -292,20 +286,16 @@ def absolute_momentum(u, v, index='index'):

"""
# Get the normal component of the wind
norm_wind = normal_component(u, v, index=index)
norm_wind.metpy.convert_units('m/s')
norm_wind = normal_component(u, v, index=index).metpy.convert_units('m/s')

# Get other pieces of calculation (all as ndarrays matching shape of norm_wind)
latitude = latitude_from_cross_section(norm_wind) # in degrees_north
latitude = latitude_from_cross_section(norm_wind)
_, latitude = xr.broadcast(norm_wind, latitude)
f = coriolis_parameter(np.deg2rad(latitude.values)).magnitude # in 1/s
f = coriolis_parameter(np.deg2rad(latitude.values))
x, y = distances_from_cross_section(norm_wind)
x.metpy.convert_units('meters')
y.metpy.convert_units('meters')
x = x.metpy.convert_units('meters')
y = y.metpy.convert_units('meters')
_, x, y = xr.broadcast(norm_wind, x, y)
distance = np.hypot(x, y).values # in meters

m = norm_wind + f * distance
m.attrs = {'units': norm_wind.attrs['units']}
distance = np.hypot(x.metpy.quantify(), y.metpy.quantify())

return m
return norm_wind + f * distance
8 changes: 4 additions & 4 deletions src/metpy/calc/indices.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .tools import _remove_nans, get_layer
from .. import constants as mpconsts
from ..package_tools import Exporter
from ..units import atleast_1d, check_units, concatenate, units
from ..units import check_units, concatenate, units
from ..xarray import preprocess_xarray

exporter = Exporter(globals())
Expand Down Expand Up @@ -258,7 +258,7 @@ def supercell_composite(mucape, effective_storm_helicity, effective_shear):
supercell composite

"""
effective_shear = np.clip(atleast_1d(effective_shear), None, 20 * units('m/s'))
effective_shear = np.clip(np.atleast_1d(effective_shear), None, 20 * units('m/s'))
effective_shear[effective_shear < 10 * units('m/s')] = 0 * units('m/s')
effective_shear = effective_shear / (20 * units('m/s'))

Expand Down Expand Up @@ -306,12 +306,12 @@ def significant_tornado(sbcape, surface_based_lcl_height, storm_helicity_1km, sh
significant tornado parameter

"""
surface_based_lcl_height = np.clip(atleast_1d(surface_based_lcl_height),
surface_based_lcl_height = np.clip(np.atleast_1d(surface_based_lcl_height),
1000 * units.m, 2000 * units.m)
surface_based_lcl_height[surface_based_lcl_height > 2000 * units.m] = 0 * units.m
surface_based_lcl_height = ((2000. * units.m - surface_based_lcl_height)
/ (1000. * units.m))
shear_6km = np.clip(atleast_1d(shear_6km), None, 30 * units('m/s'))
shear_6km = np.clip(np.atleast_1d(shear_6km), None, 30 * units('m/s'))
shear_6km[shear_6km < 12.5 * units('m/s')] = 0 * units('m/s')
shear_6km /= 20 * units('m/s')

Expand Down
4 changes: 2 additions & 2 deletions src/metpy/calc/kinematics.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .. import constants as mpconsts
from ..cbook import iterable
from ..package_tools import Exporter
from ..units import atleast_2d, check_units, concatenate, units
from ..units import check_units, concatenate, units
from ..xarray import preprocess_xarray

exporter = Exporter(globals())
Expand Down Expand Up @@ -260,7 +260,7 @@ def advection(scalar, wind, deltas):

# Make them be at least 2D (handling the 1D case) so that we can do the
# multiply and sum below
grad, wind = atleast_2d(grad, wind)
grad, wind = np.atleast_2d(grad, wind)

return (-grad * wind).sum(axis=0)

Expand Down
10 changes: 5 additions & 5 deletions src/metpy/calc/thermo.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from ..cbook import broadcast_indices
from ..interpolate.one_dimension import interpolate_1d
from ..package_tools import Exporter
from ..units import atleast_1d, check_units, concatenate, units
from ..units import check_units, concatenate, units
from ..xarray import preprocess_xarray

exporter = Exporter(globals())
Expand Down Expand Up @@ -267,7 +267,7 @@ def dt(t, p):

pressure = pressure.to('mbar')
reference_pressure = reference_pressure.to('mbar')
temperature = atleast_1d(temperature)
temperature = np.atleast_1d(temperature)

side = 'left'

Expand Down Expand Up @@ -2374,9 +2374,9 @@ def wet_bulb_temperature(pressure, temperature, dewpoint):

"""
if not hasattr(pressure, 'shape'):
pressure = atleast_1d(pressure)
temperature = atleast_1d(temperature)
dewpoint = atleast_1d(dewpoint)
pressure = np.atleast_1d(pressure)
temperature = np.atleast_1d(temperature)
dewpoint = np.atleast_1d(dewpoint)

it = np.nditer([pressure, temperature, dewpoint, None],
op_dtypes=['float', 'float', 'float', 'float'],
Expand Down
40 changes: 27 additions & 13 deletions src/metpy/calc/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from ..cbook import broadcast_indices, result_type
from ..interpolate import interpolate_1d, log_interpolate_1d
from ..package_tools import Exporter
from ..units import atleast_1d, check_units, concatenate, diff, units
from ..units import check_units, concatenate, units
from ..xarray import check_axis, preprocess_xarray

exporter = Exporter(globals())
Expand Down Expand Up @@ -1276,7 +1276,7 @@ def _process_deriv_args(f, kwargs):
if 'x' in kwargs:
raise ValueError('Cannot specify both "x" and "delta".')

delta = atleast_1d(kwargs['delta'])
delta = np.atleast_1d(kwargs['delta'])
if delta.size == 1:
diff_size = list(f.shape)
diff_size[axis] -= 1
Expand All @@ -1288,7 +1288,7 @@ def _process_deriv_args(f, kwargs):
delta = _broadcast_to_axis(delta, axis, n)
elif 'x' in kwargs:
x = _broadcast_to_axis(kwargs['x'], axis, n)
delta = diff(x, axis=axis)
delta = np.diff(x, axis=axis)
else:
raise ValueError('Must specify either "x" or "delta" for value positions.')

Expand Down Expand Up @@ -1492,7 +1492,7 @@ def wrap_output_like(**wrap_kwargs):
- As matched output (final returned value):

* ``pint.Quantity``
* ``xarray.DataArray``
* ``xarray.DataArray`` wrapping a ``pint.Quantity``

(if matched output is not one of these types, we instead treat the match as if it was a
dimenionless Quantity.)
Expand Down Expand Up @@ -1572,16 +1572,27 @@ def wrapper(*args, **kwargs):

def _wrap_output_like_matching_units(result, match):
"""Convert result to be like match with matching units for output wrapper."""
match_units = str(getattr(match, 'units', ''))
output_xarray = isinstance(match, xr.DataArray)
if isinstance(match, xr.DataArray):
output_xarray = True
match_units = match.metpy.units
elif isinstance(match, units.Quantity):
output_xarray = False
match_units = match.units
else:
output_xarray = False
match_units = ''

if isinstance(result, xr.DataArray):
result.metpy.convert_units(match_units)
return result if output_xarray else result.metpy.unit_array
result = result.metpy.convert_units(match_units)
return result.metpy.quantify() if output_xarray else result.metpy.unit_array
else:
result = result.m_as(match_units) if isinstance(result, units.Quantity) else result
if output_xarray:
return xr.DataArray(result, dims=match.dims, coords=match.coords,
attrs={'units': match_units})
return xr.DataArray(
units.Quantity(result, match_units),
dims=match.dims,
coords=match.coords
)
else:
return units.Quantity(result, match_units)

Expand All @@ -1590,7 +1601,7 @@ def _wrap_output_like_not_matching_units(result, match):
"""Convert result to be like match without matching units for output wrapper."""
output_xarray = isinstance(match, xr.DataArray)
if isinstance(result, xr.DataArray):
return result if output_xarray else result.metpy.unit_array
return result.metpy.quantify() if output_xarray else result.metpy.unit_array
else:
if isinstance(result, units.Quantity):
result_magnitude = result.magnitude
Expand All @@ -1600,7 +1611,10 @@ def _wrap_output_like_not_matching_units(result, match):
result_units = ''

if output_xarray:
return xr.DataArray(result_magnitude, dims=match.dims, coords=match.coords,
attrs={'units': result_units})
return xr.DataArray(
units.Quantity(result_magnitude, result_units),
dims=match.dims,
coords=match.coords
)
else:
return units.Quantity(result_magnitude, result_units)
Loading