diff --git a/ci/test_requirements.txt b/ci/test_requirements.txt index e8a07062276..8adbd73afbd 100644 --- a/ci/test_requirements.txt +++ b/ci/test_requirements.txt @@ -2,3 +2,4 @@ pytest==6.2.4 pytest-mpl==0.13 netCDF4==1.5.7 coverage==5.5 +dask==2021.2.0 diff --git a/conftest.py b/conftest.py index 4131ebc4399..199d9257ce9 100644 --- a/conftest.py +++ b/conftest.py @@ -162,3 +162,23 @@ def set_agg_backend(): yield finally: matplotlib.pyplot.switch_backend(prev_backend) + + +@pytest.fixture(params=['dask', 'xarray', 'masked', 'numpy']) +def array_type(request): + """Return an array type for testing calc functions.""" + quantity = metpy.units.units.Quantity + if request.param == 'dask': + dask_array = pytest.importorskip('dask.array', reason='dask.array is not available') + marker = request.node.get_closest_marker('xfail_dask') + if marker is not None: + request.applymarker(pytest.mark.xfail(reason=marker.args[0])) + return lambda d, u, *, mask=None: quantity(dask_array.array(d), u) + elif request.param == 'xarray': + return lambda d, u, *, mask=None: xarray.DataArray(d, attrs={'units': u}) + elif request.param == 'masked': + return lambda d, u, *, mask=None: quantity(numpy.ma.array(d, mask=mask), u) + elif request.param == 'numpy': + return lambda d, u, *, mask=None: quantity(numpy.array(d), u) + else: + raise ValueError(f'Unsupported array_type option {request.param}') diff --git a/setup.cfg b/setup.cfg index 61eed990283..cc283e08f67 100644 --- a/setup.cfg +++ b/setup.cfg @@ -96,8 +96,7 @@ extension = MPY = flake8_metpy:MetPyChecker paths = ./tools/flake8-metpy [tool:pytest] -# https://github.com/matplotlib/pytest-mpl/issues/69 -markers = mpl_image_compare +markers = xfail_dask: marks tests as expected to fail with Dask arrays norecursedirs = build docs .idea doctest_optionflags = NORMALIZE_WHITESPACE mpl-results-path = test_output diff --git a/src/metpy/calc/basic.py b/src/metpy/calc/basic.py index 055d2ce9cd7..d21279fe326 100644 --- a/src/metpy/calc/basic.py +++ b/src/metpy/calc/basic.py @@ -103,7 +103,8 @@ def wind_direction(u, v, convention='from'): if np.any(mask): wdir[mask] += units.Quantity(360., 'deg') # avoid unintended modification of `pint.Quantity` by direct use of magnitude - calm_mask = (np.asarray(u.magnitude) == 0.) & (np.asarray(v.magnitude) == 0.) + calm_mask = (np.asanyarray(u.magnitude) == 0.) & (np.asanyarray(v.magnitude) == 0.) + # np.any check required for legacy numpy which treats 0-d False boolean index as zero if np.any(calm_mask): wdir[calm_mask] = units.Quantity(0., 'deg') @@ -799,8 +800,12 @@ def smooth_gaussian(scalar_grid, n): # Assume the last two axes represent the horizontal directions sgma_seq = [sgma if i > num_ax - 3 else 0 for i in range(num_ax)] - # Compute smoothed field - return gaussian_filter(scalar_grid, sgma_seq, truncate=2 * np.sqrt(2)) + filter_args = {'sigma': sgma_seq, 'truncate': 2 * np.sqrt(2)} + if hasattr(scalar_grid, 'mask'): + smoothed = gaussian_filter(scalar_grid.data, **filter_args) + return np.ma.array(smoothed, mask=scalar_grid.mask) + else: + return gaussian_filter(scalar_grid, **filter_args) @exporter.export diff --git a/tests/calc/test_basic.py b/tests/calc/test_basic.py index da33c87b77c..e6ae8878e9b 100644 --- a/tests/calc/test_basic.py +++ b/tests/calc/test_basic.py @@ -19,30 +19,34 @@ from metpy.units import units -def test_wind_comps_basic(): +def test_wind_comps_basic(array_type): """Test the basic wind component calculation.""" - speed = np.array([4, 4, 4, 4, 25, 25, 25, 25, 10.]) * units.mph - dirs = np.array([0, 45, 90, 135, 180, 225, 270, 315, 360]) * units.deg + mask = [False, True, False, True, False, True, False, True, False] + speed = array_type([4, 4, 4, 4, 25, 25, 25, 25, 10.], 'mph', mask=mask) + dirs = array_type([0, 45, 90, 135, 180, 225, 270, 315, 360], 'deg', mask=mask) s2 = np.sqrt(2.) u, v = wind_components(speed, dirs) - true_u = np.array([0, -4 / s2, -4, -4 / s2, 0, 25 / s2, 25, 25 / s2, 0]) * units.mph - true_v = np.array([-4, -4 / s2, 0, 4 / s2, 25, 25 / s2, 0, -25 / s2, -10]) * units.mph + true_u = array_type([0, -4 / s2, -4, -4 / s2, 0, 25 / s2, 25, 25 / s2, 0], + 'mph', mask=mask) + true_v = array_type([-4, -4 / s2, 0, 4 / s2, 25, 25 / s2, 0, -25 / s2, -10], + 'mph', mask=mask) assert_array_almost_equal(true_u, u, 4) assert_array_almost_equal(true_v, v, 4) -def test_wind_comps_with_north_and_calm(): +def test_wind_comps_with_north_and_calm(array_type): """Test that the wind component calculation handles northerly and calm winds.""" - speed = np.array([0, 5, 5]) * units.mph - dirs = np.array([0, 360, 0]) * units.deg + mask = [False, True, False] + speed = array_type([0, 5, 5], 'mph', mask=mask) + dirs = array_type([0, 360, 0], 'deg', mask=mask) u, v = wind_components(speed, dirs) - true_u = np.array([0, 0, 0]) * units.mph - true_v = np.array([0, -5, -5]) * units.mph + true_u = array_type([0, 0, 0], 'mph', mask=mask) + true_v = array_type([0, -5, -5], 'mph', mask=mask) assert_array_almost_equal(true_u, u, 4) assert_array_almost_equal(true_v, v, 4) @@ -55,56 +59,45 @@ def test_wind_comps_scalar(): assert_almost_equal(v, 6.9282 * units('m/s'), 3) -def test_speed(): +def test_speed(array_type): """Test calculating wind speed.""" - u = np.array([4., 2., 0., 0.]) * units('m/s') - v = np.array([0., 2., 4., 0.]) * units('m/s') + mask = [False, True, False, True] + u = array_type([4., 2., 0., 0.], 'm/s', mask=mask) + v = array_type([0., 2., 4., 0.], 'm/s', mask=mask) speed = wind_speed(u, v) s2 = np.sqrt(2.) - true_speed = np.array([4., 2 * s2, 4., 0.]) * units('m/s') + true_speed = array_type([4., 2 * s2, 4., 0.], 'm/s', mask=mask) assert_array_almost_equal(true_speed, speed, 4) -def test_direction(): +@pytest.mark.xfail_dask('Item assignment with not supported') +def test_direction(array_type): """Test calculating wind direction.""" - u = np.array([4., 2., 0., 0.]) * units('m/s') - v = np.array([0., 2., 4., 0.]) * units('m/s') + # The last two (u, v) pairs and their masks test masking calm and negative directions + mask = [False, True, False, True, True] + u = array_type([4., 2., 0., 0., 1.], 'm/s', mask=mask) + v = array_type([0., 2., 4., 0., -1], 'm/s', mask=mask) direc = wind_direction(u, v) - true_dir = np.array([270., 225., 180., 0.]) * units.deg + true_dir = array_type([270., 225., 180., 0., 315.], 'degree', mask=mask) assert_array_almost_equal(true_dir, direc, 4) -def test_direction_masked(): - """Test calculating wind direction from masked wind components.""" - mask = np.array([True, False, True, False]) - u = np.array([4., 2., 0., 0.]) - v = np.array([0., 2., 4., 0.]) - - u_masked = units.Quantity(np.ma.array(u, mask=mask), units('m/s')) - v_masked = units.Quantity(np.ma.array(v, mask=mask), units('m/s')) - - direc = wind_direction(u_masked, v_masked) - - true_dir = np.array([270., 225., 180., 0.]) - true_dir_masked = units.Quantity(np.ma.array(true_dir, mask=mask), units.deg) - - assert_array_almost_equal(true_dir_masked, direc, 4) - - -def test_direction_with_north_and_calm(): +@pytest.mark.xfail_dask('Boolean index assignment in Dask expects equally shaped arrays') +def test_direction_with_north_and_calm(array_type): """Test how wind direction handles northerly and calm winds.""" - u = np.array([0., -0., 0.]) * units('m/s') - v = np.array([0., 0., -5.]) * units('m/s') + mask = [False, False, False, True] + u = array_type([0., -0., 0., 1.], 'm/s', mask=mask) + v = array_type([0., 0., -5., 1.], 'm/s', mask=mask) direc = wind_direction(u, v) - true_dir = np.array([0., 0., 360.]) * units.deg + true_dir = array_type([0., 0., 360., 225.], 'deg', mask=mask) assert_array_almost_equal(true_dir, direc, 4) @@ -115,11 +108,16 @@ def test_direction_dimensions(): assert str(d.units) == 'degree' -def test_oceanographic_direction(): +@pytest.mark.xfail_dask('Boolean index assignment in Dask expects equally shaped arrays') +def test_oceanographic_direction(array_type): """Test oceanographic direction (to) convention.""" - d = wind_direction(5 * units('m/s'), -5 * units('m/s'), convention='to') - true_dir = 135 * units.deg - assert_almost_equal(d, true_dir, 4) + mask = [False, True, False] + u = array_type([5., 5., 0.], 'm/s', mask=mask) + v = array_type([-5., 0., 5.], 'm/s', mask=mask) + + direc = wind_direction(u, v, convention='to') + true_dir = array_type([135., 90., 360.], 'deg', mask=mask) + assert_almost_equal(direc, true_dir, 4) def test_invalid_direction_convention(): @@ -161,13 +159,13 @@ def test_windchill_scalar(): assert_almost_equal(wc, -18.9357 * units.degC, 0) -def test_windchill_basic(): +def test_windchill_basic(array_type): """Test the basic wind chill calculation.""" - temp = np.array([40, -10, -45, 20]) * units.degF - speed = np.array([5, 55, 25, 15]) * units.mph + temp = array_type([40, -10, -45, 20], 'degF') + speed = array_type([5, 55, 25, 15], 'mph') wc = windchill(temp, speed) - values = np.array([36, -46, -84, 6]) * units.degF + values = array_type([36, -46, -84, 6], 'degF') assert_array_almost_equal(wc, values, 0) @@ -209,13 +207,15 @@ def test_windchill_face_level(): assert_array_almost_equal(wc, values, 0) -def test_heat_index_basic(): +@pytest.mark.xfail_dask('operands could not be broadcast together with shapes (0, 5) (nan,)') +def test_heat_index_basic(array_type): """Test the basic heat index calculation.""" - temp = np.array([80, 88, 92, 110, 86]) * units.degF - rh = np.array([40, 100, 70, 40, 88]) * units.percent + mask = [False, True, False, True, False] + temp = array_type([80, 88, 92, 110, 86], 'degF', mask=mask) + rh = array_type([40, 100, 70, 40, 88], 'percent', mask=mask) hi = heat_index(temp, rh) - values = np.array([80, 121, 112, 136, 104]) * units.degF + values = array_type([80, 121, 112, 136, 104], 'degF', mask=mask) assert_array_almost_equal(hi, values, 0) @@ -281,12 +281,13 @@ def test_heat_index_kelvin(): assert_almost_equal(hi.to('degC'), 50.3406 * units.degC, 4) -def test_height_to_geopotential(): +def test_height_to_geopotential(array_type): """Test conversion from height to geopotential.""" - height = units.Quantity([0, 1000, 2000, 3000], units.m) + mask = [False, True, False, True] + height = array_type([0, 1000, 2000, 3000], 'meter', mask=mask) geopot = height_to_geopotential(height) - assert_array_almost_equal(geopot, units.Quantity([0., 9805, 19607, - 29406], units('m**2 / second**2')), 0) + truth = array_type([0., 9805, 19607, 29406], 'm**2 / second**2', mask=mask) + assert_array_almost_equal(geopot, truth, 0) # See #1075 regarding previous destructive cancellation in floating point @@ -299,12 +300,17 @@ def test_height_to_geopotential_32bit(): assert_almost_equal(height_to_geopotential(heights), truth, 2) -def test_geopotential_to_height(): +def test_geopotential_to_height(array_type): """Test conversion from geopotential to height.""" - geopotential = units.Quantity([0., 9805.11102602, 19607.14506998, 29406.10358006], - units('m**2 / second**2')) + mask = [False, True, False, True] + geopotential = array_type( + [0., 9805.11102602, 19607.14506998, 29406.10358006], + 'm**2 / second**2', + mask=mask, + ) height = geopotential_to_height(geopotential) - assert_array_almost_equal(height, units.Quantity([0, 1000, 2000, 3000], units.m), 0) + truth = array_type([0, 1000, 2000, 3000], 'meter', mask=mask) + assert_array_almost_equal(height, truth, 0) # See #1075 regarding previous destructive cancellation in floating point @@ -317,20 +323,22 @@ def test_geopotential_to_height_32bit(): assert_almost_equal(geopotential_to_height(geopot), truth, 2) -def test_pressure_to_heights_basic(): +def test_pressure_to_heights_basic(array_type): """Test basic pressure to height calculation for standard atmosphere.""" - pressures = np.array([975.2, 987.5, 956., 943.]) * units.mbar + mask = [False, True, False, True] + pressures = array_type([975.2, 987.5, 956., 943.], 'mbar', mask=mask) heights = pressure_to_height_std(pressures) - values = np.array([321.5, 216.5, 487.6, 601.7]) * units.meter - assert_almost_equal(heights, values, 1) + values = array_type([321.5, 216.5, 487.6, 601.7], 'meter', mask=mask) + assert_array_almost_equal(heights, values, 1) -def test_heights_to_pressure_basic(): +def test_heights_to_pressure_basic(array_type): """Test basic height to pressure calculation for standard atmosphere.""" - heights = np.array([321.5, 216.5, 487.6, 601.7]) * units.meter + mask = [False, True, False, True] + heights = array_type([321.5, 216.5, 487.6, 601.7], 'meter', mask=mask) pressures = height_to_pressure_std(heights) - values = np.array([975.2, 987.5, 956., 943.]) * units.mbar - assert_almost_equal(pressures, values, 1) + values = array_type([975.2, 987.5, 956., 943.], 'mbar', mask=mask) + assert_array_almost_equal(pressures, values, 1) def test_pressure_to_heights_units(): @@ -338,33 +346,44 @@ def test_pressure_to_heights_units(): assert_almost_equal(pressure_to_height_std(29 * units.inHg), 262.8498 * units.meter, 3) -def test_coriolis_force(): +def test_coriolis_force(array_type): """Test basic coriolis force calculation.""" - lat = np.array([-90., -30., 0., 30., 90.]) * units.degrees + mask = [False, True, False, True, False] + lat = array_type([-90., -30., 0., 30., 90.], 'degrees', mask=mask) cor = coriolis_parameter(lat) - values = np.array([-1.4584232E-4, -.72921159E-4, 0, .72921159E-4, - 1.4584232E-4]) * units('s^-1') - assert_almost_equal(cor, values, 7) + values = array_type([-1.4584232E-4, -.72921159E-4, 0, .72921159E-4, + 1.4584232E-4], 's^-1', mask=mask) + assert_array_almost_equal(cor, values, 7) -def test_add_height_to_pressure(): +def test_add_height_to_pressure(array_type): """Test the pressure at height above pressure calculation.""" - pressure = add_height_to_pressure(1000 * units.hPa, 877.17421094 * units.meter) - assert_almost_equal(pressure, 900 * units.hPa, 2) + mask = [False, True, False] + pressure_in = array_type([1000., 900., 800.], 'hPa', mask=mask) + height = array_type([877.17421094, 500., 300.], 'meter', mask=mask) + pressure_out = add_height_to_pressure(pressure_in, height) + truth = array_type([900., 846.725, 770.666], 'hPa', mask=mask) + assert_array_almost_equal(pressure_out, truth, 2) -def test_add_pressure_to_height(): +def test_add_pressure_to_height(array_type): """Test the height at pressure above height calculation.""" - height = add_pressure_to_height(110.8286757 * units.m, 100 * units.hPa) - assert_almost_equal(height, 987.971601 * units.meter, 3) + mask = [False, True, False] + height_in = array_type([110.8286757, 250., 500.], 'meter', mask=mask) + pressure = array_type([100., 200., 300.], 'hPa', mask=mask) + height_out = add_pressure_to_height(height_in, pressure) + truth = array_type([987.971601, 2114.957, 3534.348], 'meter', mask=mask) + assert_array_almost_equal(height_out, truth, 3) -def test_sigma_to_pressure(): +def test_sigma_to_pressure(array_type): """Test sigma_to_pressure.""" surface_pressure = 1000. * units.hPa model_top_pressure = 0. * units.hPa - sigma = np.arange(0., 1.1, 0.1) - expected = np.arange(0., 1100., 100.) * units.hPa + sigma_values = np.arange(0., 1.1, 0.1) + mask = np.zeros_like(sigma_values)[::2] = 1 + sigma = array_type(sigma_values, '', mask=mask) + expected = array_type(np.arange(0., 1100., 100.), 'hPa', mask=mask) pressure = sigma_to_pressure(sigma, surface_pressure, model_top_pressure) assert_array_almost_equal(pressure, expected, 5) @@ -389,14 +408,19 @@ def test_coriolis_units(): assert f.units == units('1/second') -def test_apparent_temperature(): +@pytest.mark.xfail_dask( + 'boolean index did not match indexed array along dimension 0; dimension is 2 but ' + 'corresponding boolean dimension is 3' +) +def test_apparent_temperature(array_type): """Test the apparent temperature calculation.""" - temperature = np.array([[90, 90, 70], - [20, 20, 60]]) * units.degF - rel_humidity = np.array([[60, 20, 60], - [10, 10, 10]]) * units.percent - wind = np.array([[5, 3, 3], - [10, 1, 10]]) * units.mph + temperature = array_type([[90, 90, 70], + [20, 20, 60]], 'degF') + rel_humidity = array_type([[60, 20, 60], + [10, 10, 10]], 'percent') + wind = array_type([[5, 3, 3], + [10, 1, 10]], 'mph') + truth = units.Quantity(np.ma.array([[99.6777178, 86.3357671, 70], [8.8140662, 20, 60]], mask=[[False, False, True], [False, True, True]]), units.degF) @@ -455,34 +479,41 @@ def test_apparent_temperature_mask_undefined_true(): assert_array_equal(app_temperature.mask, mask) -def test_smooth_gaussian(): +def test_smooth_gaussian(array_type): """Test the smooth_gaussian function with a larger n.""" m = 10 s = np.zeros((m, m)) + for i in np.ndindex(s.shape): s[i] = i[0] + i[1]**2 - s = smooth_gaussian(s, 4) - s_true = np.array([[0.40077472, 1.59215426, 4.59665817, 9.59665817, 16.59665817, - 25.59665817, 36.59665817, 49.59665817, 64.51108392, 77.87487258], - [1.20939518, 2.40077472, 5.40527863, 10.40527863, 17.40527863, - 26.40527863, 37.40527863, 50.40527863, 65.31970438, 78.68349304], - [2.20489127, 3.39627081, 6.40077472, 11.40077472, 18.40077472, - 27.40077472, 38.40077472, 51.40077472, 66.31520047, 79.67898913], - [3.20489127, 4.39627081, 7.40077472, 12.40077472, 19.40077472, - 28.40077472, 39.40077472, 52.40077472, 67.31520047, 80.67898913], - [4.20489127, 5.39627081, 8.40077472, 13.40077472, 20.40077472, - 29.40077472, 40.40077472, 53.40077472, 68.31520047, 81.67898913], - [5.20489127, 6.39627081, 9.40077472, 14.40077472, 21.40077472, - 30.40077472, 41.40077472, 54.40077472, 69.31520047, 82.67898913], - [6.20489127, 7.39627081, 10.40077472, 15.40077472, 22.40077472, - 31.40077472, 42.40077472, 55.40077472, 70.31520047, 83.67898913], - [7.20489127, 8.39627081, 11.40077472, 16.40077472, 23.40077472, - 32.40077472, 43.40077472, 56.40077472, 71.31520047, 84.67898913], - [8.20038736, 9.3917669, 12.39627081, 17.39627081, 24.39627081, - 33.39627081, 44.39627081, 57.39627081, 72.31069656, 85.67448522], - [9.00900782, 10.20038736, 13.20489127, 18.20489127, 25.20489127, - 34.20489127, 45.20489127, 58.20489127, 73.11931702, 86.48310568]]) - assert_array_almost_equal(s, s_true) + + mask = np.zeros_like(s) + mask[::2, ::2] = 1 + scalar_grid = array_type(s, '', mask=mask) + + s_actual = smooth_gaussian(scalar_grid, 4) + s_true = array_type([[0.40077472, 1.59215426, 4.59665817, 9.59665817, 16.59665817, + 25.59665817, 36.59665817, 49.59665817, 64.51108392, 77.87487258], + [1.20939518, 2.40077472, 5.40527863, 10.40527863, 17.40527863, + 26.40527863, 37.40527863, 50.40527863, 65.31970438, 78.68349304], + [2.20489127, 3.39627081, 6.40077472, 11.40077472, 18.40077472, + 27.40077472, 38.40077472, 51.40077472, 66.31520047, 79.67898913], + [3.20489127, 4.39627081, 7.40077472, 12.40077472, 19.40077472, + 28.40077472, 39.40077472, 52.40077472, 67.31520047, 80.67898913], + [4.20489127, 5.39627081, 8.40077472, 13.40077472, 20.40077472, + 29.40077472, 40.40077472, 53.40077472, 68.31520047, 81.67898913], + [5.20489127, 6.39627081, 9.40077472, 14.40077472, 21.40077472, + 30.40077472, 41.40077472, 54.40077472, 69.31520047, 82.67898913], + [6.20489127, 7.39627081, 10.40077472, 15.40077472, 22.40077472, + 31.40077472, 42.40077472, 55.40077472, 70.31520047, 83.67898913], + [7.20489127, 8.39627081, 11.40077472, 16.40077472, 23.40077472, + 32.40077472, 43.40077472, 56.40077472, 71.31520047, 84.67898913], + [8.20038736, 9.3917669, 12.39627081, 17.39627081, 24.39627081, + 33.39627081, 44.39627081, 57.39627081, 72.31069656, 85.67448522], + [9.00900782, 10.20038736, 13.20489127, 18.20489127, 25.20489127, + 34.20489127, 45.20489127, 58.20489127, 73.11931702, 86.48310568]], + '', mask=mask) + assert_array_almost_equal(s_actual, s_true) def test_smooth_gaussian_small_n(): @@ -517,19 +548,23 @@ def test_smooth_gaussian_3d_units(): assert_array_almost_equal(s[1, :, :], s_true) -def test_smooth_n_pt_5(): +def test_smooth_n_pt_5(array_type): """Test the smooth_n_pt function using 5 points.""" hght = np.array([[5640., 5640., 5640., 5640., 5640.], - [5684., 5676., 5666., 5659., 5651.], - [5728., 5712., 5692., 5678., 5662.], - [5772., 5748., 5718., 5697., 5673.], - [5816., 5784., 5744., 5716., 5684.]]) + [5684., 5676., 5666., 5659., 5651.], + [5728., 5712., 5692., 5678., 5662.], + [5772., 5748., 5718., 5697., 5673.], + [5816., 5784., 5744., 5716., 5684.]]) + mask = np.zeros_like(hght) + mask[::2, ::2] = 1 + hght = array_type(hght, '', mask=mask) + shght = smooth_n_point(hght, 5, 1) - s_true = np.array([[5640., 5640., 5640., 5640., 5640.], - [5684., 5675.75, 5666.375, 5658.875, 5651.], - [5728., 5711.5, 5692.75, 5677.75, 5662.], - [5772., 5747.25, 5719.125, 5696.625, 5673.], - [5816., 5784., 5744., 5716., 5684.]]) + s_true = array_type([[5640., 5640., 5640., 5640., 5640.], + [5684., 5675.75, 5666.375, 5658.875, 5651.], + [5728., 5711.5, 5692.75, 5677.75, 5662.], + [5772., 5747.25, 5719.125, 5696.625, 5673.], + [5816., 5784., 5744., 5716., 5684.]], '') assert_array_almost_equal(shght, s_true) @@ -652,19 +687,23 @@ def test_smooth_gaussian_temperature(): assert_array_almost_equal(smooth_t, smooth_t_true, 4) -def test_smooth_window(): +def test_smooth_window(array_type): """Test smooth_window with default configuration.""" - hght = [[5640., 5640., 5640., 5640., 5640.], - [5684., 5676., 5666., 5659., 5651.], - [5728., 5712., 5692., 5678., 5662.], - [5772., 5748., 5718., 5697., 5673.], - [5816., 5784., 5744., 5716., 5684.]] * units.m + hght = np.array([[5640., 5640., 5640., 5640., 5640.], + [5684., 5676., 5666., 5659., 5651.], + [5728., 5712., 5692., 5678., 5662.], + [5772., 5748., 5718., 5697., 5673.], + [5816., 5784., 5744., 5716., 5684.]]) + mask = np.zeros_like(hght) + mask[::2, ::2] = 1 + hght = array_type(hght, 'meter', mask=mask) + smoothed = smooth_window(hght, np.array([[1, 0, 1], [0, 0, 0], [1, 0, 1]])) - truth = [[5640., 5640., 5640., 5640., 5640.], - [5684., 5675., 5667.5, 5658.5, 5651.], - [5728., 5710., 5695., 5677., 5662.], - [5772., 5745., 5722.5, 5695.5, 5673.], - [5816., 5784., 5744., 5716., 5684.]] * units.m + truth = array_type([[5640., 5640., 5640., 5640., 5640.], + [5684., 5675., 5667.5, 5658.5, 5651.], + [5728., 5710., 5695., 5677., 5662.], + [5772., 5745., 5722.5, 5695.5, 5673.], + [5816., 5784., 5744., 5716., 5684.]], 'meter') assert_array_almost_equal(smoothed, truth) @@ -685,35 +724,43 @@ def test_smooth_window_1d_dataarray(): xr.testing.assert_allclose(smoothed, truth) -def test_smooth_rectangular(): +def test_smooth_rectangular(array_type): """Test smooth_rectangular with default configuration.""" - hght = [[5640., 5640., 5640., 5640., 5640.], - [5684., 5676., 5666., 5659., 5651.], - [5728., 5712., 5692., 5678., 5662.], - [5772., 5748., 5718., 5697., 5673.], - [5816., 5784., 5744., 5716., 5684.]] * units.m + hght = np.array([[5640., 5640., 5640., 5640., 5640.], + [5684., 5676., 5666., 5659., 5651.], + [5728., 5712., 5692., 5678., 5662.], + [5772., 5748., 5718., 5697., 5673.], + [5816., 5784., 5744., 5716., 5684.]]) + mask = np.zeros_like(hght) + mask[::2, ::2] = 1 + hght = array_type(hght, 'meter', mask=mask) + smoothed = smooth_rectangular(hght, (5, 3)) - truth = [[5640., 5640., 5640., 5640., 5640.], - [5684., 5676., 5666., 5659., 5651.], - [5728., 5710.66667, 5694., 5677.33333, 5662.], - [5772., 5748., 5718., 5697., 5673.], - [5816., 5784., 5744., 5716., 5684.]] * units.m + truth = array_type([[5640., 5640., 5640., 5640., 5640.], + [5684., 5676., 5666., 5659., 5651.], + [5728., 5710.66667, 5694., 5677.33333, 5662.], + [5772., 5748., 5718., 5697., 5673.], + [5816., 5784., 5744., 5716., 5684.]], 'meter') assert_array_almost_equal(smoothed, truth, 4) -def test_smooth_circular(): +def test_smooth_circular(array_type): """Test smooth_circular with default configuration.""" - hght = [[5640., 5640., 5640., 5640., 5640.], - [5684., 5676., 5666., 5659., 5651.], - [5728., 5712., 5692., 5678., 5662.], - [5772., 5748., 5718., 5697., 5673.], - [5816., 5784., 5744., 5716., 5684.]] * units.m + hght = np.array([[5640., 5640., 5640., 5640., 5640.], + [5684., 5676., 5666., 5659., 5651.], + [5728., 5712., 5692., 5678., 5662.], + [5772., 5748., 5718., 5697., 5673.], + [5816., 5784., 5744., 5716., 5684.]]) + mask = np.zeros_like(hght) + mask[::2, ::2] = 1 + hght = array_type(hght, 'meter', mask=mask) + smoothed = smooth_circular(hght, 2, 2) - truth = [[5640., 5640., 5640., 5640., 5640.], - [5684., 5676., 5666., 5659., 5651.], - [5728., 5712., 5693.98817, 5678., 5662.], - [5772., 5748., 5718., 5697., 5673.], - [5816., 5784., 5744., 5716., 5684.]] * units.m + truth = array_type([[5640., 5640., 5640., 5640., 5640.], + [5684., 5676., 5666., 5659., 5651.], + [5728., 5712., 5693.98817, 5678., 5662.], + [5772., 5748., 5718., 5697., 5673.], + [5816., 5784., 5744., 5716., 5684.]], 'meter') assert_array_almost_equal(smoothed, truth, 4) @@ -734,13 +781,16 @@ def test_altimeter_to_station_pressure_inhg(): assert_almost_equal(res, truth, 3) -def test_altimeter_to_station_pressure_hpa(): +def test_altimeter_to_station_pressure_hpa(array_type): """Test the altimeter to station pressure function with hectopascals.""" - altim = 1013 * units.hectopascal - elev = 500 * units.m + mask = [False, True, False, True] + altim = array_type([1000., 1005., 1010., 1013.], 'hectopascal', mask=mask) + elev = array_type([2000., 1500., 1000., 500.], 'meter', mask=mask) res = altimeter_to_station_pressure(altim, elev) - truth = 954.639265 * units.hectopascal - assert_almost_equal(res, truth, 3) + truth = array_type( + [784.262996, 838.651657, 896.037821, 954.639265], 'hectopascal', mask=mask + ) + assert_array_almost_equal(res, truth, 3) def test_altimiter_to_sea_level_pressure_inhg(): @@ -753,11 +803,14 @@ def test_altimiter_to_sea_level_pressure_inhg(): assert_almost_equal(res, truth, 3) -def test_altimeter_to_sea_level_pressure_hpa(): +def test_altimeter_to_sea_level_pressure_hpa(array_type): """Test the altimeter to sea level pressure function with hectopascals.""" - altim = 1013 * units.hectopascal - elev = 500 * units.m - temp = 0 * units.degC + mask = [False, True, False, True] + altim = array_type([1000., 1005., 1010., 1013], 'hectopascal', mask=mask) + elev = array_type([2000., 1500., 1000., 500.], 'meter', mask=mask) + temp = array_type([-3., -2., -1., 0.], 'degC') res = altimeter_to_sea_level_pressure(altim, elev, temp) - truth = 1016.246 * units.hectopascal - assert_almost_equal(res, truth, 3) + truth = array_type( + [1009.963556, 1013.119712, 1015.885392, 1016.245615], 'hectopascal', mask=mask + ) + assert_array_almost_equal(res, truth, 3)