From 77782e7f2d52f01173dfee7829bc38b579aae8fd Mon Sep 17 00:00:00 2001 From: Jon Thielen Date: Wed, 1 Aug 2018 15:41:27 -0600 Subject: [PATCH 01/10] Fix non-tuple NumPy indexing in MetPy Since NumPy 1.15 came out, we have been getting pages and pages of warnings about our use of non-tuple based indexing. This commit converts all of our previous list-based indexing to tuple-based indexing. This doesn't yet clear out all the warnings (some of them are upstream in Pint and scipy), but it does drop the number down from 600-something to 10-something. --- metpy/calc/thermo.py | 1 + metpy/calc/tools.py | 68 +++++++++++++++++------------- metpy/calc/turbulence.py | 7 +-- metpy/cbook.py | 4 +- metpy/interpolate/one_dimension.py | 2 +- 5 files changed, 43 insertions(+), 39 deletions(-) diff --git a/metpy/calc/thermo.py b/metpy/calc/thermo.py index 947f5b1193e..12f2a55ee49 100644 --- a/metpy/calc/thermo.py +++ b/metpy/calc/thermo.py @@ -1457,6 +1457,7 @@ def _isen_iter(iter_log_p, isentlevs_nd, ka, a, b, pok): slices = [np.newaxis] * ndim slices[axis] = slice(None) + slices = tuple(slices) pres = np.broadcast_to(pres[slices], temperature.shape) * pres.units # Sort input data diff --git a/metpy/calc/tools.py b/metpy/calc/tools.py index 25efe2091c0..6a9bdde4e0b 100644 --- a/metpy/calc/tools.py +++ b/metpy/calc/tools.py @@ -697,15 +697,15 @@ def find_bounding_indices(arr, values, axis, from_below=True): # Same as above, but we use the slice to come from the end; then adjust those # indices to measure from the front. - index = arr.shape[axis] - 1 - switches[arr_slice].argmax(axis=axis) + index = arr.shape[axis] - 1 - switches[tuple(arr_slice)].argmax(axis=axis) # Set all indices where the results are not good to 0 index[~good_search] = 0 # Put the results in the proper slice store_slice[axis] = level_index - indices[store_slice] = index - good[store_slice] = good_search + indices[tuple(store_slice)] = index + good[tuple(store_slice)] = good_search # Create index values for broadcasting arrays above = broadcast_indices(arr, indices, arr.ndim, axis) @@ -1011,11 +1011,14 @@ def first_derivative(f, **kwargs): delta_slice0[axis] = slice(None, -1) delta_slice1[axis] = slice(1, None) - combined_delta = delta[delta_slice0] + delta[delta_slice1] - delta_diff = delta[delta_slice1] - delta[delta_slice0] - center = (- delta[delta_slice1] / (combined_delta * delta[delta_slice0]) * f[slice0] + - delta_diff / (delta[delta_slice0] * delta[delta_slice1]) * f[slice1] + - delta[delta_slice0] / (combined_delta * delta[delta_slice1]) * f[slice2]) + combined_delta = delta[tuple(delta_slice0)] + delta[tuple(delta_slice1)] + delta_diff = delta[tuple(delta_slice1)] - delta[tuple(delta_slice0)] + center = (- delta[tuple(delta_slice1)] / (combined_delta * delta[tuple(delta_slice0)]) * + f[tuple(slice0)] + + delta_diff / (delta[tuple(delta_slice0)] * delta[tuple(delta_slice1)]) * + f[tuple(slice1)] + + delta[tuple(delta_slice0)] / (combined_delta * delta[tuple(delta_slice1)]) * + f[tuple(slice2)]) # Fill in "left" edge with forward difference slice0[axis] = slice(None, 1) @@ -1024,11 +1027,13 @@ def first_derivative(f, **kwargs): delta_slice0[axis] = slice(None, 1) delta_slice1[axis] = slice(1, 2) - combined_delta = delta[delta_slice0] + delta[delta_slice1] - big_delta = combined_delta + delta[delta_slice0] - left = (- big_delta / (combined_delta * delta[delta_slice0]) * f[slice0] + - combined_delta / (delta[delta_slice0] * delta[delta_slice1]) * f[slice1] - - delta[delta_slice0] / (combined_delta * delta[delta_slice1]) * f[slice2]) + combined_delta = delta[tuple(delta_slice0)] + delta[tuple(delta_slice1)] + big_delta = combined_delta + delta[tuple(delta_slice0)] + left = (- big_delta / (combined_delta * delta[tuple(delta_slice0)]) * f[tuple(slice0)] + + combined_delta / (delta[tuple(delta_slice0)] * delta[tuple(delta_slice1)]) * + f[tuple(slice1)] - + delta[tuple(delta_slice0)] / (combined_delta * delta[tuple(delta_slice1)]) * + f[tuple(slice2)]) # Now the "right" edge with backward difference slice0[axis] = slice(-3, -2) @@ -1037,11 +1042,13 @@ def first_derivative(f, **kwargs): delta_slice0[axis] = slice(-2, -1) delta_slice1[axis] = slice(-1, None) - combined_delta = delta[delta_slice0] + delta[delta_slice1] - big_delta = combined_delta + delta[delta_slice1] - right = (delta[delta_slice1] / (combined_delta * delta[delta_slice0]) * f[slice0] - - combined_delta / (delta[delta_slice0] * delta[delta_slice1]) * f[slice1] + - big_delta / (combined_delta * delta[delta_slice1]) * f[slice2]) + combined_delta = delta[tuple(delta_slice0)] + delta[tuple(delta_slice1)] + big_delta = combined_delta + delta[tuple(delta_slice1)] + right = (delta[tuple(delta_slice1)] / (combined_delta * delta[tuple(delta_slice0)]) * + f[tuple(slice0)] - + combined_delta / (delta[tuple(delta_slice0)] * delta[tuple(delta_slice1)]) * + f[tuple(slice1)] + + big_delta / (combined_delta * delta[tuple(delta_slice1)]) * f[tuple(slice2)]) return concatenate((left, center, right), axis=axis) @@ -1106,10 +1113,11 @@ def second_derivative(f, **kwargs): delta_slice0[axis] = slice(None, -1) delta_slice1[axis] = slice(1, None) - combined_delta = delta[delta_slice0] + delta[delta_slice1] - center = 2 * (f[slice0] / (combined_delta * delta[delta_slice0]) - - f[slice1] / (delta[delta_slice0] * delta[delta_slice1]) + - f[slice2] / (combined_delta * delta[delta_slice1])) + combined_delta = delta[tuple(delta_slice0)] + delta[tuple(delta_slice1)] + center = 2 * (f[tuple(slice0)] / (combined_delta * delta[tuple(delta_slice0)]) - + f[tuple(slice1)] / (delta[tuple(delta_slice0)] * + delta[tuple(delta_slice1)]) + + f[tuple(slice2)] / (combined_delta * delta[tuple(delta_slice1)])) # Fill in "left" edge slice0[axis] = slice(None, 1) @@ -1118,10 +1126,10 @@ def second_derivative(f, **kwargs): delta_slice0[axis] = slice(None, 1) delta_slice1[axis] = slice(1, 2) - combined_delta = delta[delta_slice0] + delta[delta_slice1] - left = 2 * (f[slice0] / (combined_delta * delta[delta_slice0]) - - f[slice1] / (delta[delta_slice0] * delta[delta_slice1]) + - f[slice2] / (combined_delta * delta[delta_slice1])) + combined_delta = delta[tuple(delta_slice0)] + delta[tuple(delta_slice1)] + left = 2 * (f[tuple(slice0)] / (combined_delta * delta[tuple(delta_slice0)]) - + f[tuple(slice1)] / (delta[tuple(delta_slice0)] * delta[tuple(delta_slice1)]) + + f[tuple(slice2)] / (combined_delta * delta[tuple(delta_slice1)])) # Now the "right" edge slice0[axis] = slice(-3, -2) @@ -1130,10 +1138,10 @@ def second_derivative(f, **kwargs): delta_slice0[axis] = slice(-2, -1) delta_slice1[axis] = slice(-1, None) - combined_delta = delta[delta_slice0] + delta[delta_slice1] - right = 2 * (f[slice0] / (combined_delta * delta[delta_slice0]) - - f[slice1] / (delta[delta_slice0] * delta[delta_slice1]) + - f[slice2] / (combined_delta * delta[delta_slice1])) + combined_delta = delta[tuple(delta_slice0)] + delta[tuple(delta_slice1)] + right = 2 * (f[tuple(slice0)] / (combined_delta * delta[tuple(delta_slice0)]) - + f[tuple(slice1)] / (delta[tuple(delta_slice0)] * delta[tuple(delta_slice1)]) + + f[tuple(slice2)] / (combined_delta * delta[tuple(delta_slice1)])) return concatenate((left, center, right), axis=axis) diff --git a/metpy/calc/turbulence.py b/metpy/calc/turbulence.py index e817bdb8a11..08b6144a6ae 100644 --- a/metpy/calc/turbulence.py +++ b/metpy/calc/turbulence.py @@ -42,12 +42,7 @@ def get_perturbation(ts, axis=-1): """ slices = [slice(None)] * ts.ndim slices[axis] = None - # For numpy<=1.8.0, can't slice on a scalar - mean = ts.mean(axis=axis) - if ts.ndim == 1: - mean = np.atleast_1d(mean) - else: - mean = mean[slices] + mean = ts.mean(axis=axis)[tuple(slices)] return ts - mean diff --git a/metpy/cbook.py b/metpy/cbook.py index 86d64004f4a..100128eb51e 100644 --- a/metpy/cbook.py +++ b/metpy/cbook.py @@ -87,8 +87,8 @@ def broadcast_indices(x, minv, ndim, axis): broadcast_slice = [np.newaxis] * ndim broadcast_slice[dim] = slice(None) dim_inds = np.arange(x.shape[dim]) - ret.append(dim_inds[broadcast_slice]) - return ret + ret.append(dim_inds[tuple(broadcast_slice)]) + return tuple(ret) __all__ = ('Registry', 'broadcast_indices', 'get_test_data', 'is_string_like', 'iterable') diff --git a/metpy/interpolate/one_dimension.py b/metpy/interpolate/one_dimension.py index dba0cb07bec..58fe0526d4b 100644 --- a/metpy/interpolate/one_dimension.py +++ b/metpy/interpolate/one_dimension.py @@ -122,7 +122,7 @@ def interpolate_1d(x, xp, *args, **kwargs): x_array = x[sort_x] expand = [np.newaxis] * ndim expand[axis] = slice(None) - x_array = x_array[expand] + x_array = x_array[tuple(expand)] # Calculate value above interpolated value minv = np.apply_along_axis(np.searchsorted, axis, xp, x[sort_x]) From 764b722eb3e8a672fb0c648ec7ba2d882163a5c1 Mon Sep 17 00:00:00 2001 From: Steve Decker Date: Thu, 2 Aug 2018 11:46:44 -0400 Subject: [PATCH 02/10] Add GWFS function from GEMPAK This commit wraps the gaussian_filter function from SciPy so that it emulates the GWFS function from GEMPAK as closely as possible. The key difference is that GEMPAK adds the leftover weights from outside the averaging window to the center point, but there is no option for gaussian_filter to do this. Left unfinished is restoring metadata to the result. For instance, an xarray.DataArray provided as input becomes a lowly numpy.ndarray on output. --- metpy/calc/basic.py | 85 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/metpy/calc/basic.py b/metpy/calc/basic.py index 2c22f409ae3..7a732fc3559 100644 --- a/metpy/calc/basic.py +++ b/metpy/calc/basic.py @@ -16,6 +16,8 @@ import numpy as np +from scipy.ndimage import gaussian_filter + from ..constants import G, g, me, omega, Rd, Re from ..deprecation import deprecated from ..package_tools import Exporter @@ -614,6 +616,89 @@ def sigma_to_pressure(sigma, psfc, ptop): return sigma * (psfc - ptop) + ptop +@exporter.export +@preprocess_xarray +def gwfs(scalar_grid, n): + """Filter with normal distribution of weights. + + Parameters + ---------- + scalar_grid : `pint.Quantity` + Some two-dimensional scalar grid + + n : int + Degree of filtering + + Returns + ------- + `pint.Quantity` + The filtered 2D scalar grid + + Notes + ----- + This function is a close replication of the GEMPAK function GWFS, + but is not identical. The following notes are incorporated from + the GEMPAK source code: + + This function smoothes a scalar grid using a moving average + low-pass filter whose weights are determined by the normal + (Gaussian) probability distribution function for two dimensions. + The weight given to any grid point within the area covered by the + moving average for a target grid point is proportional to + + EXP [ -( D ** 2 ) ], + + where D is the distance from that point to the target point divided + by the standard deviation of the normal distribution. The value of + the standard deviation is determined by the degree of filtering + requested. The degree of filtering is specified by an integer. + This integer is the number of grid increments from crest to crest + of the wave for which the theoretical response is 1/e = .3679. If + the grid increment is called delta_x, and the value of this integer + is represented by N, then the theoretical filter response function + value for the N * delta_x wave will be 1/e. The actual response + function will be greater than the theoretical value. + + The larger N is, the more severe the filtering will be, because the + response function for all wavelengths shorter than N * delta_x + will be less than 1/e. Furthermore, as N is increased, the slope + of the filter response function becomes more shallow; so, the + response at all wavelengths decreases, but the amount of decrease + lessens with increasing wavelength. (The theoretical response + function can be obtained easily--it is the Fourier transform of the + weight function described above.) + + The area of the patch covered by the moving average varies with N. + As N gets bigger, the smoothing gets stronger, and weight values + farther from the target grid point are larger because the standard + deviation of the normal distribution is bigger. Thus, increasing + N has the effect of expanding the moving average window as well as + changing the values of weights. The patch is a square covering all + points whose weight values are within two standard deviations of the + mean of the two dimensional normal distribution. + + The key difference between GEMPAK's GWFS and this function is that, + in GEMPAK, the leftover weight values representing the fringe of the + distribution are applied to the target grid point. In this + function, the leftover weights are not used. + + When this function is invoked, the first argument is the grid to be + smoothed, the second is the value of N as described above: + + GWFS ( S, N ) + + where N > 1. If N <= 1, N = 2 is assumed. For example, if N = 4, + then the 4 delta x wave length is passed with approximate response + 1/e. + """ + n = int(round(n)) + if n < 2: + n = 2 + sgma = n / (2*np.pi) + res = gaussian_filter(scalar_grid, sgma, truncate=2*np.sqrt(2)) + return res + + def _check_radians(value, max_radians=2 * np.pi): """Input validation of values that could be in degrees instead of radians. From 841c8ea5a3155a9af7f2f4490987a3ae026a9932 Mon Sep 17 00:00:00 2001 From: Steve Decker Date: Thu, 2 Aug 2018 14:46:52 -0400 Subject: [PATCH 03/10] Add tests and docs for gwfs() This commit adds a few basic tests for the metpy.calc.gwfs function, and also updates the GEMPAK compatibility table. I'm not sure how to add new documentation to the MetPy API section. --- docs/gempak.rst | 10 ++++---- metpy/calc/tests/test_basic.py | 47 +++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/docs/gempak.rst b/docs/gempak.rst index 2897dcf3389..fe699d7b62c 100644 --- a/docs/gempak.rst +++ b/docs/gempak.rst @@ -469,11 +469,11 @@ blue is uncertain of parity, and white is unevaluated. - GWFS(S, N) - Filter with normal distribution of weights - - - + GWFS(S, N) + Filter with normal distribution of weights + metpy.calc.gwfs + Yes + Yes diff --git a/metpy/calc/tests/test_basic.py b/metpy/calc/tests/test_basic.py index 1497f07049b..89901ea180b 100644 --- a/metpy/calc/tests/test_basic.py +++ b/metpy/calc/tests/test_basic.py @@ -8,7 +8,7 @@ from metpy.calc import (add_height_to_pressure, add_pressure_to_height, apparent_temperature, coriolis_parameter, geopotential_to_height, get_wind_components, - get_wind_dir, get_wind_speed, heat_index, height_to_geopotential, + get_wind_dir, get_wind_speed, gwfs, heat_index, height_to_geopotential, height_to_pressure_std, pressure_to_height_std, sigma_to_pressure, wind_components, wind_direction, wind_speed, windchill) from metpy.deprecation import MetpyDeprecationWarning @@ -436,3 +436,48 @@ def test_apparent_temperature_windchill(): truth = -18.9357 * units.degC res = apparent_temperature(temperature, rel_humidity, wind) assert_almost_equal(res, truth, 0) + + +def test_gwfs(): + """Test the gwfs 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 = gwfs(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) + +def test_gwfs_small_n(): + """Test the gwfs function with a smaller n.""" + m = 5 + s = np.zeros((m, m)) + for i in np.ndindex(s.shape): + s[i] = i[0] + i[1]**2 + s = gwfs(s, 1) + s_true = [[0.0141798077, 1.02126971, 4.02126971, 9.02126971, 15.9574606], + [1.00708990, 2.01417981, 5.01417981, 10.0141798, 16.9503707], + [2.00708990, 3.01417981, 6.01417981, 11.0141798, 17.9503707], + [3.00708990, 4.01417981, 7.01417981, 12.0141798, 18.9503707], + [4.00000000, 5.00708990, 8.00708990, 13.0070899, 19.9432808]] + print(s-s_true) + assert_array_almost_equal(s, s_true) From b1d7f0e76dadfd33a412fea7a0d8852fd665cc26 Mon Sep 17 00:00:00 2001 From: Steve Decker Date: Thu, 2 Aug 2018 16:04:37 -0400 Subject: [PATCH 04/10] Fix whitespace issues re pep8 --- metpy/calc/basic.py | 12 +++++----- metpy/calc/tests/test_basic.py | 42 +++++++++++++++++----------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/metpy/calc/basic.py b/metpy/calc/basic.py index 7a732fc3559..0711cff9430 100644 --- a/metpy/calc/basic.py +++ b/metpy/calc/basic.py @@ -15,7 +15,6 @@ import warnings import numpy as np - from scipy.ndimage import gaussian_filter from ..constants import G, g, me, omega, Rd, Re @@ -633,7 +632,7 @@ def gwfs(scalar_grid, n): ------- `pint.Quantity` The filtered 2D scalar grid - + Notes ----- This function is a close replication of the GEMPAK function GWFS, @@ -689,16 +688,17 @@ def gwfs(scalar_grid, n): where N > 1. If N <= 1, N = 2 is assumed. For example, if N = 4, then the 4 delta x wave length is passed with approximate response - 1/e. + 1/e. + """ n = int(round(n)) if n < 2: n = 2 - sgma = n / (2*np.pi) - res = gaussian_filter(scalar_grid, sgma, truncate=2*np.sqrt(2)) + sgma = n / (2 * np.pi) + res = gaussian_filter(scalar_grid, sgma, truncate=2 * np.sqrt(2)) return res - + def _check_radians(value, max_radians=2 * np.pi): """Input validation of values that could be in degrees instead of radians. diff --git a/metpy/calc/tests/test_basic.py b/metpy/calc/tests/test_basic.py index 89901ea180b..a50d1ed6756 100644 --- a/metpy/calc/tests/test_basic.py +++ b/metpy/calc/tests/test_basic.py @@ -445,28 +445,29 @@ def test_gwfs(): for i in np.ndindex(s.shape): s[i] = i[0] + i[1]**2 s = gwfs(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]]) + 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) + def test_gwfs_small_n(): """Test the gwfs function with a smaller n.""" m = 5 @@ -479,5 +480,4 @@ def test_gwfs_small_n(): [2.00708990, 3.01417981, 6.01417981, 11.0141798, 17.9503707], [3.00708990, 4.01417981, 7.01417981, 12.0141798, 18.9503707], [4.00000000, 5.00708990, 8.00708990, 13.0070899, 19.9432808]] - print(s-s_true) assert_array_almost_equal(s, s_true) From 261188f6334ec47a8f2268ed7a397c732018956a Mon Sep 17 00:00:00 2001 From: Steve Decker Date: Thu, 2 Aug 2018 11:46:44 -0400 Subject: [PATCH 05/10] Add GWFS function from GEMPAK This commit wraps the gaussian_filter function from SciPy so that it emulates the GWFS function from GEMPAK as closely as possible. The key difference is that GEMPAK adds the leftover weights from outside the averaging window to the center point, but there is no option for gaussian_filter to do this. Left unfinished is restoring metadata to the result. For instance, an xarray.DataArray provided as input becomes a lowly numpy.ndarray on output. --- metpy/calc/basic.py | 85 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/metpy/calc/basic.py b/metpy/calc/basic.py index 2c22f409ae3..7a732fc3559 100644 --- a/metpy/calc/basic.py +++ b/metpy/calc/basic.py @@ -16,6 +16,8 @@ import numpy as np +from scipy.ndimage import gaussian_filter + from ..constants import G, g, me, omega, Rd, Re from ..deprecation import deprecated from ..package_tools import Exporter @@ -614,6 +616,89 @@ def sigma_to_pressure(sigma, psfc, ptop): return sigma * (psfc - ptop) + ptop +@exporter.export +@preprocess_xarray +def gwfs(scalar_grid, n): + """Filter with normal distribution of weights. + + Parameters + ---------- + scalar_grid : `pint.Quantity` + Some two-dimensional scalar grid + + n : int + Degree of filtering + + Returns + ------- + `pint.Quantity` + The filtered 2D scalar grid + + Notes + ----- + This function is a close replication of the GEMPAK function GWFS, + but is not identical. The following notes are incorporated from + the GEMPAK source code: + + This function smoothes a scalar grid using a moving average + low-pass filter whose weights are determined by the normal + (Gaussian) probability distribution function for two dimensions. + The weight given to any grid point within the area covered by the + moving average for a target grid point is proportional to + + EXP [ -( D ** 2 ) ], + + where D is the distance from that point to the target point divided + by the standard deviation of the normal distribution. The value of + the standard deviation is determined by the degree of filtering + requested. The degree of filtering is specified by an integer. + This integer is the number of grid increments from crest to crest + of the wave for which the theoretical response is 1/e = .3679. If + the grid increment is called delta_x, and the value of this integer + is represented by N, then the theoretical filter response function + value for the N * delta_x wave will be 1/e. The actual response + function will be greater than the theoretical value. + + The larger N is, the more severe the filtering will be, because the + response function for all wavelengths shorter than N * delta_x + will be less than 1/e. Furthermore, as N is increased, the slope + of the filter response function becomes more shallow; so, the + response at all wavelengths decreases, but the amount of decrease + lessens with increasing wavelength. (The theoretical response + function can be obtained easily--it is the Fourier transform of the + weight function described above.) + + The area of the patch covered by the moving average varies with N. + As N gets bigger, the smoothing gets stronger, and weight values + farther from the target grid point are larger because the standard + deviation of the normal distribution is bigger. Thus, increasing + N has the effect of expanding the moving average window as well as + changing the values of weights. The patch is a square covering all + points whose weight values are within two standard deviations of the + mean of the two dimensional normal distribution. + + The key difference between GEMPAK's GWFS and this function is that, + in GEMPAK, the leftover weight values representing the fringe of the + distribution are applied to the target grid point. In this + function, the leftover weights are not used. + + When this function is invoked, the first argument is the grid to be + smoothed, the second is the value of N as described above: + + GWFS ( S, N ) + + where N > 1. If N <= 1, N = 2 is assumed. For example, if N = 4, + then the 4 delta x wave length is passed with approximate response + 1/e. + """ + n = int(round(n)) + if n < 2: + n = 2 + sgma = n / (2*np.pi) + res = gaussian_filter(scalar_grid, sgma, truncate=2*np.sqrt(2)) + return res + + def _check_radians(value, max_radians=2 * np.pi): """Input validation of values that could be in degrees instead of radians. From 10418d7127e951a6d61afc52fbbde4d211aa54ad Mon Sep 17 00:00:00 2001 From: Steve Decker Date: Thu, 2 Aug 2018 14:46:52 -0400 Subject: [PATCH 06/10] Add tests and docs for gwfs() This commit adds a few basic tests for the metpy.calc.gwfs function, and also updates the GEMPAK compatibility table. I'm not sure how to add new documentation to the MetPy API section. --- docs/gempak.rst | 10 ++++---- metpy/calc/tests/test_basic.py | 47 +++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/docs/gempak.rst b/docs/gempak.rst index 2897dcf3389..fe699d7b62c 100644 --- a/docs/gempak.rst +++ b/docs/gempak.rst @@ -469,11 +469,11 @@ blue is uncertain of parity, and white is unevaluated. - GWFS(S, N) - Filter with normal distribution of weights - - - + GWFS(S, N) + Filter with normal distribution of weights + metpy.calc.gwfs + Yes + Yes diff --git a/metpy/calc/tests/test_basic.py b/metpy/calc/tests/test_basic.py index 1497f07049b..89901ea180b 100644 --- a/metpy/calc/tests/test_basic.py +++ b/metpy/calc/tests/test_basic.py @@ -8,7 +8,7 @@ from metpy.calc import (add_height_to_pressure, add_pressure_to_height, apparent_temperature, coriolis_parameter, geopotential_to_height, get_wind_components, - get_wind_dir, get_wind_speed, heat_index, height_to_geopotential, + get_wind_dir, get_wind_speed, gwfs, heat_index, height_to_geopotential, height_to_pressure_std, pressure_to_height_std, sigma_to_pressure, wind_components, wind_direction, wind_speed, windchill) from metpy.deprecation import MetpyDeprecationWarning @@ -436,3 +436,48 @@ def test_apparent_temperature_windchill(): truth = -18.9357 * units.degC res = apparent_temperature(temperature, rel_humidity, wind) assert_almost_equal(res, truth, 0) + + +def test_gwfs(): + """Test the gwfs 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 = gwfs(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) + +def test_gwfs_small_n(): + """Test the gwfs function with a smaller n.""" + m = 5 + s = np.zeros((m, m)) + for i in np.ndindex(s.shape): + s[i] = i[0] + i[1]**2 + s = gwfs(s, 1) + s_true = [[0.0141798077, 1.02126971, 4.02126971, 9.02126971, 15.9574606], + [1.00708990, 2.01417981, 5.01417981, 10.0141798, 16.9503707], + [2.00708990, 3.01417981, 6.01417981, 11.0141798, 17.9503707], + [3.00708990, 4.01417981, 7.01417981, 12.0141798, 18.9503707], + [4.00000000, 5.00708990, 8.00708990, 13.0070899, 19.9432808]] + print(s-s_true) + assert_array_almost_equal(s, s_true) From 027bb9794f67045e2fcc08f3eb64ddabd054f14b Mon Sep 17 00:00:00 2001 From: Steve Decker Date: Thu, 2 Aug 2018 16:04:37 -0400 Subject: [PATCH 07/10] Fix whitespace issues re pep8 --- metpy/calc/basic.py | 12 +++++----- metpy/calc/tests/test_basic.py | 42 +++++++++++++++++----------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/metpy/calc/basic.py b/metpy/calc/basic.py index 7a732fc3559..0711cff9430 100644 --- a/metpy/calc/basic.py +++ b/metpy/calc/basic.py @@ -15,7 +15,6 @@ import warnings import numpy as np - from scipy.ndimage import gaussian_filter from ..constants import G, g, me, omega, Rd, Re @@ -633,7 +632,7 @@ def gwfs(scalar_grid, n): ------- `pint.Quantity` The filtered 2D scalar grid - + Notes ----- This function is a close replication of the GEMPAK function GWFS, @@ -689,16 +688,17 @@ def gwfs(scalar_grid, n): where N > 1. If N <= 1, N = 2 is assumed. For example, if N = 4, then the 4 delta x wave length is passed with approximate response - 1/e. + 1/e. + """ n = int(round(n)) if n < 2: n = 2 - sgma = n / (2*np.pi) - res = gaussian_filter(scalar_grid, sgma, truncate=2*np.sqrt(2)) + sgma = n / (2 * np.pi) + res = gaussian_filter(scalar_grid, sgma, truncate=2 * np.sqrt(2)) return res - + def _check_radians(value, max_radians=2 * np.pi): """Input validation of values that could be in degrees instead of radians. diff --git a/metpy/calc/tests/test_basic.py b/metpy/calc/tests/test_basic.py index 89901ea180b..a50d1ed6756 100644 --- a/metpy/calc/tests/test_basic.py +++ b/metpy/calc/tests/test_basic.py @@ -445,28 +445,29 @@ def test_gwfs(): for i in np.ndindex(s.shape): s[i] = i[0] + i[1]**2 s = gwfs(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]]) + 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) + def test_gwfs_small_n(): """Test the gwfs function with a smaller n.""" m = 5 @@ -479,5 +480,4 @@ def test_gwfs_small_n(): [2.00708990, 3.01417981, 6.01417981, 11.0141798, 17.9503707], [3.00708990, 4.01417981, 7.01417981, 12.0141798, 18.9503707], [4.00000000, 5.00708990, 8.00708990, 13.0070899, 19.9432808]] - print(s-s_true) assert_array_almost_equal(s, s_true) From 4f898e2addd6ae9c9ed16ac730e8389cc747e325 Mon Sep 17 00:00:00 2001 From: Steve Decker Date: Fri, 3 Aug 2018 15:41:36 -0400 Subject: [PATCH 08/10] Extend functionality of gwfs and update docs gwfs is extended to preserve units and ensure that only horizontal directions are smoothed (assuming last two axes are horizontal). Documentation is updated so that this function appears. --- docs/_templates/overrides/metpy.calc.rst | 1 + metpy/calc/basic.py | 15 +++++++++++++-- metpy/calc/tests/test_basic.py | 17 +++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/docs/_templates/overrides/metpy.calc.rst b/docs/_templates/overrides/metpy.calc.rst index 9ee61de74b1..061ece69568 100644 --- a/docs/_templates/overrides/metpy.calc.rst +++ b/docs/_templates/overrides/metpy.calc.rst @@ -167,6 +167,7 @@ calc get_layer get_layer_heights get_perturbation + gwfs isentropic_interpolation nearest_intersection_idx parse_angle diff --git a/metpy/calc/basic.py b/metpy/calc/basic.py index 0711cff9430..83c530a8607 100644 --- a/metpy/calc/basic.py +++ b/metpy/calc/basic.py @@ -623,7 +623,8 @@ def gwfs(scalar_grid, n): Parameters ---------- scalar_grid : `pint.Quantity` - Some two-dimensional scalar grid + Some n-dimensional scalar grid. If more than two axes, smoothing + is only done across the last two. n : int Degree of filtering @@ -691,11 +692,21 @@ def gwfs(scalar_grid, n): 1/e. """ + # Compute standard deviation in a manner consistent with GEMPAK n = int(round(n)) if n < 2: n = 2 sgma = n / (2 * np.pi) - res = gaussian_filter(scalar_grid, sgma, truncate=2 * np.sqrt(2)) + + # Construct sigma sequence so smoothing occurs only in horizontal direction + nax = len(scalar_grid.shape) + # Assume the last two axes represent the horizontal directions + sgma_seq = [sgma if i > nax - 3 else 0 for i in range(nax)] + + # Compute smoothed field and reattach units + res = gaussian_filter(scalar_grid, sgma_seq, truncate=2 * np.sqrt(2)) + if hasattr(scalar_grid, 'units'): + res = res * scalar_grid.units return res diff --git a/metpy/calc/tests/test_basic.py b/metpy/calc/tests/test_basic.py index a50d1ed6756..acf12d81305 100644 --- a/metpy/calc/tests/test_basic.py +++ b/metpy/calc/tests/test_basic.py @@ -481,3 +481,20 @@ def test_gwfs_small_n(): [3.00708990, 4.01417981, 7.01417981, 12.0141798, 18.9503707], [4.00000000, 5.00708990, 8.00708990, 13.0070899, 19.9432808]] assert_array_almost_equal(s, s_true) + + +def test_gwfs_3d_units(): + """Test the gwfs function with units and a 3D array.""" + m = 5 + s = np.zeros((3, m, m)) + for i in np.ndindex(s.shape): + s[i] = i[1] + i[2]**2 + s[0::2, :, :] = 10 * s[0::2, :, :] + s = s * units('m') + s = gwfs(s, 1) + s_true = ([[0.0141798077, 1.02126971, 4.02126971, 9.02126971, 15.9574606], + [1.00708990, 2.01417981, 5.01417981, 10.0141798, 16.9503707], + [2.00708990, 3.01417981, 6.01417981, 11.0141798, 17.9503707], + [3.00708990, 4.01417981, 7.01417981, 12.0141798, 18.9503707], + [4.00000000, 5.00708990, 8.00708990, 13.0070899, 19.9432808]]) * units('m') + assert_array_almost_equal(s[1, :, :], s_true) From 067d33bf3c9716ce09d58bf07dff7a928d62ecf5 Mon Sep 17 00:00:00 2001 From: Steve Decker Date: Fri, 3 Aug 2018 15:52:27 -0400 Subject: [PATCH 09/10] Rename gwfs() to smooth_gaussian() --- docs/_templates/overrides/metpy.calc.rst | 2 +- metpy/calc/basic.py | 2 +- metpy/calc/tests/test_basic.py | 23 ++++++++++++----------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/_templates/overrides/metpy.calc.rst b/docs/_templates/overrides/metpy.calc.rst index 061ece69568..d83c5e69074 100644 --- a/docs/_templates/overrides/metpy.calc.rst +++ b/docs/_templates/overrides/metpy.calc.rst @@ -167,12 +167,12 @@ calc get_layer get_layer_heights get_perturbation - gwfs isentropic_interpolation nearest_intersection_idx parse_angle reduce_point_density resample_nn_1d + smooth_gaussian Deprecated diff --git a/metpy/calc/basic.py b/metpy/calc/basic.py index 83c530a8607..317baff9b3a 100644 --- a/metpy/calc/basic.py +++ b/metpy/calc/basic.py @@ -617,7 +617,7 @@ def sigma_to_pressure(sigma, psfc, ptop): @exporter.export @preprocess_xarray -def gwfs(scalar_grid, n): +def smooth_gaussian(scalar_grid, n): """Filter with normal distribution of weights. Parameters diff --git a/metpy/calc/tests/test_basic.py b/metpy/calc/tests/test_basic.py index acf12d81305..2ef0255bdff 100644 --- a/metpy/calc/tests/test_basic.py +++ b/metpy/calc/tests/test_basic.py @@ -8,9 +8,10 @@ from metpy.calc import (add_height_to_pressure, add_pressure_to_height, apparent_temperature, coriolis_parameter, geopotential_to_height, get_wind_components, - get_wind_dir, get_wind_speed, gwfs, heat_index, height_to_geopotential, + get_wind_dir, get_wind_speed, heat_index, height_to_geopotential, height_to_pressure_std, pressure_to_height_std, sigma_to_pressure, - wind_components, wind_direction, wind_speed, windchill) + smooth_gaussian, wind_components, wind_direction, wind_speed, + windchill) from metpy.deprecation import MetpyDeprecationWarning from metpy.testing import assert_almost_equal, assert_array_almost_equal, assert_array_equal from metpy.units import units @@ -438,13 +439,13 @@ def test_apparent_temperature_windchill(): assert_almost_equal(res, truth, 0) -def test_gwfs(): - """Test the gwfs function with a larger n.""" +def test_smooth_gaussian(): + """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 = gwfs(s, 4) + 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, @@ -468,13 +469,13 @@ def test_gwfs(): assert_array_almost_equal(s, s_true) -def test_gwfs_small_n(): - """Test the gwfs function with a smaller n.""" +def test_smooth_gaussian_small_n(): + """Test the smooth_gaussian function with a smaller n.""" m = 5 s = np.zeros((m, m)) for i in np.ndindex(s.shape): s[i] = i[0] + i[1]**2 - s = gwfs(s, 1) + s = smooth_gaussian(s, 1) s_true = [[0.0141798077, 1.02126971, 4.02126971, 9.02126971, 15.9574606], [1.00708990, 2.01417981, 5.01417981, 10.0141798, 16.9503707], [2.00708990, 3.01417981, 6.01417981, 11.0141798, 17.9503707], @@ -483,15 +484,15 @@ def test_gwfs_small_n(): assert_array_almost_equal(s, s_true) -def test_gwfs_3d_units(): - """Test the gwfs function with units and a 3D array.""" +def test_smooth_gaussian_3d_units(): + """Test the smooth_gaussian function with units and a 3D array.""" m = 5 s = np.zeros((3, m, m)) for i in np.ndindex(s.shape): s[i] = i[1] + i[2]**2 s[0::2, :, :] = 10 * s[0::2, :, :] s = s * units('m') - s = gwfs(s, 1) + s = smooth_gaussian(s, 1) s_true = ([[0.0141798077, 1.02126971, 4.02126971, 9.02126971, 15.9574606], [1.00708990, 2.01417981, 5.01417981, 10.0141798, 16.9503707], [2.00708990, 3.01417981, 6.01417981, 11.0141798, 17.9503707], From 2fdb0d49e44e604c65cbdab2c5cba3f270b3e591 Mon Sep 17 00:00:00 2001 From: Steve Decker Date: Fri, 3 Aug 2018 17:51:14 -0400 Subject: [PATCH 10/10] Update GEMPAK Conversion Guide One place was overlooked when changing gwfs() to smooth_gaussian(). This corrects the oversight. --- docs/gempak.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gempak.rst b/docs/gempak.rst index fe699d7b62c..62c6e019268 100644 --- a/docs/gempak.rst +++ b/docs/gempak.rst @@ -471,7 +471,7 @@ blue is uncertain of parity, and white is unevaluated. GWFS(S, N) Filter with normal distribution of weights - metpy.calc.gwfs + metpy.calc.smooth_gaussian Yes Yes