From d86e3fee3c9906ce0fad36520de86d9ac306cee8 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Thu, 13 Jul 2017 12:40:05 +0100 Subject: [PATCH 1/4] BUG: Allow 0d arrays instead of scalars in gradient This fixes gh-8292 --- numpy/lib/function_base.py | 18 +++++++++++------- numpy/lib/tests/test_function_base.py | 5 ++++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/numpy/lib/function_base.py b/numpy/lib/function_base.py index 8ee6a54dda21..989030e03f6d 100644 --- a/numpy/lib/function_base.py +++ b/numpy/lib/function_base.py @@ -1674,6 +1674,7 @@ def gradient(f, *varargs, **kwargs): S0025-5718-1988-0935077-0/S0025-5718-1988-0935077-0.pdf>`_. """ f = np.asanyarray(f) + varargs = [asanyarray(d) for d in varargs] N = f.ndim # number of dimensions axes = kwargs.pop('axis', None) @@ -1685,11 +1686,16 @@ def gradient(f, *varargs, **kwargs): len_axes = len(axes) n = len(varargs) if n == 0: - dx = [1.0] * len_axes - elif n == len_axes or (n == 1 and np.isscalar(varargs[0])): - dx = list(varargs) + # no spacing argument - use 1 in all axes + dx = [np.array(1.0)] * len_axes + elif n == 1 and varargs[0].ndim == 0: + # single scalar for all axes + dx = varargs * len_axes + elif n == len_axes: + # scalar or 1d array for each axis + dx = varargs[:] for i, distances in enumerate(dx): - if np.isscalar(distances): + if distances.ndim == 0: continue if len(distances) != f.shape[axes[i]]: raise ValueError("distances must be either scalars or match " @@ -1700,8 +1706,6 @@ def gradient(f, *varargs, **kwargs): if (diffx == diffx[0]).all(): diffx = diffx[0] dx[i] = diffx - if len(dx) == 1: - dx *= len_axes else: raise TypeError("invalid number of arguments") @@ -1751,7 +1755,7 @@ def gradient(f, *varargs, **kwargs): # result allocation out = np.empty_like(y, dtype=otype) - uniform_spacing = np.isscalar(dx[i]) + uniform_spacing = dx[i].ndim == 0 # Numerical differentiation: 2nd order interior slice1[axis] = slice(1, -1) diff --git a/numpy/lib/tests/test_function_base.py b/numpy/lib/tests/test_function_base.py index 4f21e261f485..ee2cc166a9e1 100644 --- a/numpy/lib/tests/test_function_base.py +++ b/numpy/lib/tests/test_function_base.py @@ -742,8 +742,11 @@ def test_args(self): # distances must be scalars or have size equal to gradient[axis] gradient(np.arange(5), 3.) + gradient(np.arange(5), np.array(3.)) gradient(np.arange(5), dx) - gradient(f_2d, 1.5) # dy is set equal to dx because scalar + # dy is set equal to dx because scalar + gradient(f_2d, 1.5) + gradient(f_2d, np.array(1.5)) gradient(f_2d, dx_uneven, dx_uneven) # mix between even and uneven spaces and From 2bfec2c0dd92958694e6d736530ae6333d7d62d7 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Thu, 13 Jul 2017 12:40:32 +0100 Subject: [PATCH 2/4] BUG: Only allow 1d distance arrays 2d arrays would work, but in unpredictable and undocumented ways. This at least makes gh-9401 give a better error message. --- numpy/lib/function_base.py | 4 +++- numpy/lib/tests/test_function_base.py | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/numpy/lib/function_base.py b/numpy/lib/function_base.py index 989030e03f6d..91e7dc6161df 100644 --- a/numpy/lib/function_base.py +++ b/numpy/lib/function_base.py @@ -1697,8 +1697,10 @@ def gradient(f, *varargs, **kwargs): for i, distances in enumerate(dx): if distances.ndim == 0: continue + elif distances.ndim != 1: + raise ValueError("distances must be either scalars or 1d") if len(distances) != f.shape[axes[i]]: - raise ValueError("distances must be either scalars or match " + raise ValueError("when 1d, distances must match " "the length of the corresponding dimension") diffx = np.diff(dx[i]) # if distances are constant reduce to the scalar case diff --git a/numpy/lib/tests/test_function_base.py b/numpy/lib/tests/test_function_base.py index ee2cc166a9e1..d7d00758efd9 100644 --- a/numpy/lib/tests/test_function_base.py +++ b/numpy/lib/tests/test_function_base.py @@ -756,6 +756,10 @@ def test_args(self): # 2D but axis specified gradient(f_2d, dx, axis=1) + # 2d coordinate arguments are not yet allowed + assert_raises_regex(ValueError, '.*scalars or 1d', + gradient, f_2d, np.stack([dx]*2, axis=-1), 1) + def test_badargs(self): f_2d = np.arange(25).reshape(5, 5) x = np.cumsum(np.ones(5)) From 24508c1220a608df116db9035df30418bfc74a9b Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Thu, 13 Jul 2017 13:08:46 +0100 Subject: [PATCH 3/4] BUG: Use np.ndim not asarray, to allow duck-types --- numpy/lib/function_base.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/numpy/lib/function_base.py b/numpy/lib/function_base.py index 91e7dc6161df..b616011d1898 100644 --- a/numpy/lib/function_base.py +++ b/numpy/lib/function_base.py @@ -1674,7 +1674,6 @@ def gradient(f, *varargs, **kwargs): S0025-5718-1988-0935077-0/S0025-5718-1988-0935077-0.pdf>`_. """ f = np.asanyarray(f) - varargs = [asanyarray(d) for d in varargs] N = f.ndim # number of dimensions axes = kwargs.pop('axis', None) @@ -1687,17 +1686,17 @@ def gradient(f, *varargs, **kwargs): n = len(varargs) if n == 0: # no spacing argument - use 1 in all axes - dx = [np.array(1.0)] * len_axes - elif n == 1 and varargs[0].ndim == 0: + dx = [1.0] * len_axes + elif n == 1 and np.ndim(varargs[0]) == 0: # single scalar for all axes dx = varargs * len_axes elif n == len_axes: # scalar or 1d array for each axis - dx = varargs[:] + dx = list(varargs) for i, distances in enumerate(dx): - if distances.ndim == 0: + if np.ndim(distances) == 0: continue - elif distances.ndim != 1: + elif np.ndim(distances) != 1: raise ValueError("distances must be either scalars or 1d") if len(distances) != f.shape[axes[i]]: raise ValueError("when 1d, distances must match " @@ -1757,7 +1756,7 @@ def gradient(f, *varargs, **kwargs): # result allocation out = np.empty_like(y, dtype=otype) - uniform_spacing = dx[i].ndim == 0 + uniform_spacing = np.ndim(dx[i]) == 0 # Numerical differentiation: 2nd order interior slice1[axis] = slice(1, -1) From 1f4ba5bee1240cc7a888087dc06acb7709f12870 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Thu, 13 Jul 2017 13:10:59 +0100 Subject: [PATCH 4/4] MAINT: Use clearer variable --- numpy/lib/function_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/lib/function_base.py b/numpy/lib/function_base.py index b616011d1898..32c999dfcb42 100644 --- a/numpy/lib/function_base.py +++ b/numpy/lib/function_base.py @@ -1701,7 +1701,7 @@ def gradient(f, *varargs, **kwargs): if len(distances) != f.shape[axes[i]]: raise ValueError("when 1d, distances must match " "the length of the corresponding dimension") - diffx = np.diff(dx[i]) + diffx = np.diff(distances) # if distances are constant reduce to the scalar case # since it brings a consistent speedup if (diffx == diffx[0]).all():