diff options
Diffstat (limited to 'numpy')
-rw-r--r-- | numpy/core/_exceptions.py | 1 | ||||
-rw-r--r-- | numpy/fft/pocketfft.py | 32 | ||||
-rw-r--r-- | numpy/lib/arraypad.py | 84 | ||||
-rw-r--r-- | numpy/lib/tests/test_arraypad.py | 33 | ||||
-rw-r--r-- | numpy/testing/_private/utils.py | 9 | ||||
-rw-r--r-- | numpy/testing/tests/test_utils.py | 9 |
6 files changed, 77 insertions, 91 deletions
diff --git a/numpy/core/_exceptions.py b/numpy/core/_exceptions.py index a1af7a78d..b3805af04 100644 --- a/numpy/core/_exceptions.py +++ b/numpy/core/_exceptions.py @@ -27,6 +27,7 @@ def _display_as_base(cls): assert issubclass(cls, Exception) cls.__name__ = cls.__base__.__name__ cls.__qualname__ = cls.__base__.__qualname__ + set_module(cls.__base__.__module__)(cls) return cls diff --git a/numpy/fft/pocketfft.py b/numpy/fft/pocketfft.py index 77ea6e3ba..1f6201c7c 100644 --- a/numpy/fft/pocketfft.py +++ b/numpy/fft/pocketfft.py @@ -44,7 +44,11 @@ array_function_dispatch = functools.partial( overrides.array_function_dispatch, module='numpy.fft') -def _raw_fft(a, n, axis, is_real, is_forward, fct): +# `inv_norm` is a float by which the result of the transform needs to be +# divided. This replaces the original, more intuitive 'fct` parameter to avoid +# divisions by zero (or alternatively additional checks) in the case of +# zero-length axes during its computation. +def _raw_fft(a, n, axis, is_real, is_forward, inv_norm): axis = normalize_axis_index(axis, a.ndim) if n is None: n = a.shape[axis] @@ -53,6 +57,8 @@ def _raw_fft(a, n, axis, is_real, is_forward, fct): raise ValueError("Invalid number of FFT data points (%d) specified." % n) + fct = 1/inv_norm + if a.shape[axis] != n: s = list(a.shape) if s[axis] > n: @@ -176,10 +182,10 @@ def fft(a, n=None, axis=-1, norm=None): a = asarray(a) if n is None: n = a.shape[axis] - fct = 1 + inv_norm = 1 if norm is not None and _unitary(norm): - fct = 1 / sqrt(n) - output = _raw_fft(a, n, axis, False, True, fct) + inv_norm = sqrt(n) + output = _raw_fft(a, n, axis, False, True, inv_norm) return output @@ -272,10 +278,10 @@ def ifft(a, n=None, axis=-1, norm=None): if n is None: n = a.shape[axis] if norm is not None and _unitary(norm): - fct = 1/sqrt(max(n, 1)) + inv_norm = sqrt(max(n, 1)) else: - fct = 1/max(n, 1) - output = _raw_fft(a, n, axis, False, False, fct) + inv_norm = n + output = _raw_fft(a, n, axis, False, False, inv_norm) return output @@ -360,12 +366,12 @@ def rfft(a, n=None, axis=-1, norm=None): """ a = asarray(a) - fct = 1 + inv_norm = 1 if norm is not None and _unitary(norm): if n is None: n = a.shape[axis] - fct = 1/sqrt(n) - output = _raw_fft(a, n, axis, True, True, fct) + inv_norm = sqrt(n) + output = _raw_fft(a, n, axis, True, True, inv_norm) return output @@ -462,10 +468,10 @@ def irfft(a, n=None, axis=-1, norm=None): a = asarray(a) if n is None: n = (a.shape[axis] - 1) * 2 - fct = 1/n + inv_norm = n if norm is not None and _unitary(norm): - fct = 1/sqrt(n) - output = _raw_fft(a, n, axis, True, False, fct) + inv_norm = sqrt(n) + output = _raw_fft(a, n, axis, True, False, inv_norm) return output diff --git a/numpy/lib/arraypad.py b/numpy/lib/arraypad.py index 62330e692..33e64708d 100644 --- a/numpy/lib/arraypad.py +++ b/numpy/lib/arraypad.py @@ -17,66 +17,6 @@ __all__ = ['pad'] # Private utility functions. -def _linear_ramp(ndim, axis, start, stop, size, reverse=False): - """ - Create a linear ramp of `size` in `axis` with `ndim`. - - This algorithm behaves like a vectorized version of `numpy.linspace`. - The resulting linear ramp is broadcastable to any array that matches the - ramp in `shape[axis]` and `ndim`. - - Parameters - ---------- - ndim : int - Number of dimensions of the resulting array. All dimensions except - the one specified by `axis` will have the size 1. - axis : int - The dimension that contains the linear ramp of `size`. - start : int or ndarray - The starting value(s) of the linear ramp. If given as an array, its - size must match `size`. - stop : int or ndarray - The stop value(s) (not included!) of the linear ramp. If given as an - array, its size must match `size`. - size : int - The number of elements in the linear ramp. If this argument is 0 the - dimensions of `ramp` will all be of length 1 except for the one given - by `axis` which will be 0. - reverse : bool - If False, increment in a positive fashion, otherwise decrement. - - Returns - ------- - ramp : ndarray - Output array of dtype np.float64 that in- or decrements along the given - `axis`. - - Examples - -------- - >>> _linear_ramp(ndim=2, axis=0, start=np.arange(3), stop=10, size=2) - array([[0. , 1. , 2. ], - [5. , 5.5, 6. ]]) - >>> _linear_ramp(ndim=3, axis=0, start=2, stop=0, size=0) - array([], shape=(0, 1, 1), dtype=float64) - """ - # Create initial ramp - ramp = np.arange(size, dtype=np.float64) - if reverse: - ramp = ramp[::-1] - - # Make sure, that ramp is broadcastable - init_shape = (1,) * axis + (size,) + (1,) * (ndim - axis - 1) - ramp = ramp.reshape(init_shape) - - if size != 0: - # And scale to given start and stop values - gain = (stop - start) / float(size) - ramp = ramp * gain - ramp += start - - return ramp - - def _round_if_needed(arr, dtype): """ Rounds arr inplace if destination dtype is integer. @@ -269,17 +209,25 @@ def _get_linear_ramps(padded, axis, width_pair, end_value_pair): """ edge_pair = _get_edges(padded, axis, width_pair) - left_ramp = _linear_ramp( - padded.ndim, axis, start=end_value_pair[0], stop=edge_pair[0], - size=width_pair[0], reverse=False + left_ramp = np.linspace( + start=end_value_pair[0], + stop=edge_pair[0].squeeze(axis), # Dimensions is replaced by linspace + num=width_pair[0], + endpoint=False, + dtype=padded.dtype, + axis=axis, ) - _round_if_needed(left_ramp, padded.dtype) - right_ramp = _linear_ramp( - padded.ndim, axis, start=end_value_pair[1], stop=edge_pair[1], - size=width_pair[1], reverse=True + right_ramp = np.linspace( + start=end_value_pair[1], + stop=edge_pair[1].squeeze(axis), # Dimension is replaced by linspace + num=width_pair[1], + endpoint=False, + dtype=padded.dtype, + axis=axis, ) - _round_if_needed(right_ramp, padded.dtype) + # Reverse linear space in appropriate dimension + right_ramp = right_ramp[_slice_at_axis(slice(None, None, -1), axis)] return left_ramp, right_ramp diff --git a/numpy/lib/tests/test_arraypad.py b/numpy/lib/tests/test_arraypad.py index b6dd3b31c..65593dd29 100644 --- a/numpy/lib/tests/test_arraypad.py +++ b/numpy/lib/tests/test_arraypad.py @@ -2,7 +2,6 @@ """ from __future__ import division, absolute_import, print_function -from itertools import chain import pytest @@ -11,6 +10,12 @@ from numpy.testing import assert_array_equal, assert_allclose, assert_equal from numpy.lib.arraypad import _as_pairs +_numeric_dtypes = ( + np.sctypes["uint"] + + np.sctypes["int"] + + np.sctypes["float"] + + np.sctypes["complex"] +) _all_modes = { 'constant': {'constant_values': 0}, 'edge': {}, @@ -738,6 +743,24 @@ class TestLinearRamp(object): assert_equal(a[0, :], 0.) assert_equal(a[-1, :], 0.) + @pytest.mark.parametrize("dtype", _numeric_dtypes) + def test_negative_difference(self, dtype): + """ + Check correct behavior of unsigned dtypes if there is a negative + difference between the edge to pad and `end_values`. Check both cases + to be independent of implementation. Test behavior for all other dtypes + in case dtype casting interferes with complex dtypes. See gh-14191. + """ + x = np.array([3], dtype=dtype) + result = np.pad(x, 3, mode="linear_ramp", end_values=0) + expected = np.array([0, 1, 2, 3, 2, 1, 0], dtype=dtype) + assert_equal(result, expected) + + x = np.array([0], dtype=dtype) + result = np.pad(x, 3, mode="linear_ramp", end_values=3) + expected = np.array([3, 2, 1, 0, 1, 2, 3], dtype=dtype) + assert_equal(result, expected) + class TestReflect(object): def test_check_simple(self): @@ -1330,13 +1353,7 @@ def test_memory_layout_persistence(mode): assert np.pad(x, 5, mode).flags["F_CONTIGUOUS"] -@pytest.mark.parametrize("dtype", chain( - # Skip "other" dtypes as they are not supported by all modes - np.sctypes["int"], - np.sctypes["uint"], - np.sctypes["float"], - np.sctypes["complex"] -)) +@pytest.mark.parametrize("dtype", _numeric_dtypes) @pytest.mark.parametrize("mode", _all_modes.keys()) def test_dtype_persistence(dtype, mode): arr = np.zeros((3, 2, 1), dtype=dtype) diff --git a/numpy/testing/_private/utils.py b/numpy/testing/_private/utils.py index 97a5eac17..4ac0715bf 100644 --- a/numpy/testing/_private/utils.py +++ b/numpy/testing/_private/utils.py @@ -686,7 +686,7 @@ def assert_array_compare(comparison, x, y, err_msg='', verbose=True, header='', precision=6, equal_nan=True, equal_inf=True): __tracebackhide__ = True # Hide traceback for py.test - from numpy.core import array, array2string, isnan, inf, bool_, errstate + from numpy.core import array, array2string, isnan, inf, bool_, errstate, all x = array(x, copy=False, subok=True) y = array(y, copy=False, subok=True) @@ -807,7 +807,12 @@ def assert_array_compare(comparison, x, y, err_msg='', verbose=True, # note: this definition of relative error matches that one # used by assert_allclose (found in np.isclose) - max_rel_error = (error / abs(y)).max() + # Filter values where the divisor would be zero + nonzero = bool_(y != 0) + if all(~nonzero): + max_rel_error = array(inf) + else: + max_rel_error = (error[nonzero] / abs(y[nonzero])).max() if error.dtype == 'object': remarks.append('Max relative difference: ' + str(max_rel_error)) diff --git a/numpy/testing/tests/test_utils.py b/numpy/testing/tests/test_utils.py index 4f1b46d4f..688bedc16 100644 --- a/numpy/testing/tests/test_utils.py +++ b/numpy/testing/tests/test_utils.py @@ -881,6 +881,15 @@ class TestAssertAllclose(object): assert_array_less(a, b) assert_allclose(a, b) + def test_report_max_relative_error(self): + a = np.array([0, 1]) + b = np.array([0, 2]) + + with pytest.raises(AssertionError) as exc_info: + assert_allclose(a, b) + msg = str(exc_info.value) + assert_('Max relative difference: 0.5' in msg) + class TestArrayAlmostEqualNulp(object): |