From 0419794b707c673b8e52e98e139684fa555b3871 Mon Sep 17 00:00:00 2001 From: Pim de Haan Date: Thu, 28 Jul 2016 02:10:31 +0200 Subject: [PATCH 1/3] WIP: MAINT: Made clip into an ufunc In order to solve issue #7633, it was suggested to make clip into an ufunc. This is an partial implementation of that. Left to do: - Implement proper typecasting - Propely handle None and NaN - Add a faster loop to loops.c.src to speed up if min or max are not arrays - Update the docs - Create benchmarks and test the new implementation - Remove remnants of the old implementation from - - numeric.py - - fromnumeric.py - - numpy_api.py - - multiarray_api.* - - arraytypes.*.src - - calculation.* --- numpy/core/code_generators/generate_umath.py | 25 +++- .../core/code_generators/ufunc_docstrings.py | 51 +++++++ numpy/core/src/multiarray/calculation.c | 5 + numpy/core/src/multiarray/number.c | 2 + numpy/core/src/multiarray/number.h | 1 + numpy/core/src/umath/loops.c.src | 134 ++++++++++++++++++ numpy/core/src/umath/loops.h.src | 14 +- numpy/core/tests/test_numeric.py | 26 ++++ 8 files changed, 254 insertions(+), 4 deletions(-) diff --git a/numpy/core/code_generators/generate_umath.py b/numpy/core/code_generators/generate_umath.py index 2241618f7c4e..170289f8ba04 100644 --- a/numpy/core/code_generators/generate_umath.py +++ b/numpy/core/code_generators/generate_umath.py @@ -877,6 +877,12 @@ def english_upper(s): TypeDescription('d', None, 'd', 'di'), TypeDescription('g', None, 'g', 'gi'), ], + ), +'clip' : + Ufunc(3, 1, None, + docstrings.get('numpy.core.umath.clip'), + None, + TD(noobj), ) } @@ -910,6 +916,17 @@ def indent(st, spaces): 'G': 'GG_G', 'O': 'OO_O', 'P': 'OO_O_method'} + +chartotype3 = {'e': 'eee_e', + 'f': 'fff_f', + 'd': 'ddd_d', + 'g': 'ggg_g', + 'F': 'FFF_F', + 'D': 'DDD_D', + 'G': 'GGG_G', + 'O': 'OOO_O', + 'P': 'OOO_O_method'} + #for each name # 1) create functions, data, and signature # 2) fill in functions and data in InitOperators @@ -930,9 +947,11 @@ def make_arrays(funcdict): k = 0 sub = 0 - if uf.nin > 1: - assert uf.nin == 2 - thedict = chartotype2 # two inputs and one output + if uf.nin > 2: + assert uf.nin == 3 + thedict = chartotype3 # three inputs and one output + elif uf.nin == 2: + thedict = chartotype2 else: thedict = chartotype1 # one input and one output diff --git a/numpy/core/code_generators/ufunc_docstrings.py b/numpy/core/code_generators/ufunc_docstrings.py index c5172f6a8bcf..19a0a9e2dd66 100644 --- a/numpy/core/code_generators/ufunc_docstrings.py +++ b/numpy/core/code_generators/ufunc_docstrings.py @@ -3669,3 +3669,54 @@ def add_newdoc(place, name, doc): array([ 0., 1., 2., 3., 4., 5.]) """) + +add_newdoc('numpy.core.umath', 'clip', + """ + Clip (limit) the values in an array. + + Given an interval, values outside the interval are clipped to + the interval edges. For example, if an interval of ``[0, 1]`` + is specified, values smaller than 0 become 0, and values larger + than 1 become 1. + + Parameters + ---------- + a : array_like + Array containing elements to clip. + a_min : scalar or array_like + Minimum value. + a_max : scalar or array_like + Maximum value. If `a_min` or `a_max` are array_like, then they will + be broadcasted to the shape of `a`. + out : ndarray, optional + The results will be placed in this array. It may be the input + array for in-place clipping. `out` must be of the right shape + to hold the output. Its type is preserved. + + Returns + ------- + clipped_array : ndarray + An array with the elements of `a`, but where values + < `a_min` are replaced with `a_min`, and those > `a_max` + with `a_max`. + + See Also + -------- + numpy.doc.ufuncs : Section "Output arguments" + + Examples + -------- + >>> a = np.arange(10) + >>> np.clip(a, 1, 8) + array([1, 1, 2, 3, 4, 5, 6, 7, 8, 8]) + >>> a + array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + >>> np.clip(a, 3, 6, out=a) + array([3, 3, 3, 3, 4, 5, 6, 6, 6, 6]) + >>> a = np.arange(10) + >>> a + array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + >>> np.clip(a, [3,4,1,1,1,4,4,4,4,4], 8) + array([3, 4, 2, 3, 4, 5, 6, 7, 8, 8]) + + """) diff --git a/numpy/core/src/multiarray/calculation.c b/numpy/core/src/multiarray/calculation.c index 379e5c3d26c6..2334c6a83646 100644 --- a/numpy/core/src/multiarray/calculation.c +++ b/numpy/core/src/multiarray/calculation.c @@ -891,6 +891,11 @@ _slow_array_clip(PyArrayObject *self, PyObject *min, PyObject *max, PyArrayObjec NPY_NO_EXPORT PyObject * PyArray_Clip(PyArrayObject *self, PyObject *min, PyObject *max, PyArrayObject *out) { + if (out == NULL) { + return PyObject_CallFunction(n_ops.clip, "OOO", self, min, max); + } + return PyObject_CallFunction(n_ops.clip, "OOOO", self, min, max, out); + PyArray_FastClipFunc *func; int outgood = 0, ingood = 0; PyArrayObject *maxa = NULL; diff --git a/numpy/core/src/multiarray/number.c b/numpy/core/src/multiarray/number.c index 9c1343497db3..99df096fe8d6 100644 --- a/numpy/core/src/multiarray/number.c +++ b/numpy/core/src/multiarray/number.c @@ -115,6 +115,7 @@ PyArray_SetNumericOps(PyObject *dict) SET(minimum); SET(rint); SET(conjugate); + SET(clip); return 0; } @@ -168,6 +169,7 @@ PyArray_GetNumericOps(void) GET(minimum); GET(rint); GET(conjugate); + GET(clip); return dict; fail: diff --git a/numpy/core/src/multiarray/number.h b/numpy/core/src/multiarray/number.h index 99a2a722b6b9..869a03b972f1 100644 --- a/numpy/core/src/multiarray/number.h +++ b/numpy/core/src/multiarray/number.h @@ -39,6 +39,7 @@ typedef struct { PyObject *minimum; PyObject *rint; PyObject *conjugate; + PyObject *clip; } NumericOps; extern NPY_NO_EXPORT NumericOps n_ops; diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src index 40ebc119aefe..8d9209986d38 100644 --- a/numpy/core/src/umath/loops.c.src +++ b/numpy/core/src/umath/loops.c.src @@ -231,6 +231,13 @@ npy_intp i;\ for(i = 0; i < n; i++, ip1 += is1, ip2 += is2, op1 += os1, op2 += os2) +#define TERNARY_LOOP\ + char *ip1 = args[0], *ip2 = args[1], *ip3 = args[2], *op1 = args[3];\ + npy_intp is1 = steps[0], is2 = steps[1], is3 = steps[2], os1 = steps[3];\ + npy_intp n = dimensions[0];\ + npy_intp i;\ + for(i = 0; i < n; i++, ip1 += is1, ip2 += is2, ip3 += is3, op1 += os1) + /****************************************************************************** ** GENERIC FLOAT LOOPS ** *****************************************************************************/ @@ -804,6 +811,26 @@ BOOL__ones_like(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UN } } +NPY_NO_EXPORT void +BOOL_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + TERNARY_LOOP { + const npy_bool in = *((npy_bool *)ip1) != 0; + const npy_bool min = *((npy_bool *)ip2) != 0; + const npy_bool max = *((npy_bool *)ip3) != 0; + + if (in < min) { + *((npy_bool *)op1) = min; + } + else if (in > max) { + *((npy_bool *)op1) = max; + } + else { + *((npy_bool *)op1) = in; + } + } +} + /* ***************************************************************************** @@ -1046,6 +1073,26 @@ NPY_NO_EXPORT void } } +NPY_NO_EXPORT void +@TYPE@_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + TERNARY_LOOP { + const @type@ in = *((@type@ *)ip1); + const @type@ min = *((@type@ *)ip2); + const @type@ max = *((@type@ *)ip3); + + if (in < min) { + *((@type@ *)op1) = min; + } + else if (in > max) { + *((@type@ *)op1) = max; + } + else { + *((@type@ *)op1) = in; + } + } +} + /**end repeat**/ /**begin repeat @@ -1366,6 +1413,27 @@ NPY_NO_EXPORT void } /**end repeat1**/ + +NPY_NO_EXPORT void +@TYPE@_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + TERNARY_LOOP { + const @type@ in = *((@type@ *)ip1); + const @type@ min = *((@type@ *)ip2); + const @type@ max = *((@type@ *)ip3); + + if (in < min) { + *((@type@ *)op1) = min; + } + else if (in > max) { + *((@type@ *)op1) = max; + } + else { + *((@type@ *)op1) = in; + } + } +} + /**end repeat**/ NPY_NO_EXPORT void @@ -2042,6 +2110,26 @@ NPY_NO_EXPORT void } } +NPY_NO_EXPORT void +@TYPE@_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + TERNARY_LOOP { + const @type@ in = *((@type@ *)ip1); + const @type@ min = *((@type@ *)ip2); + const @type@ max = *((@type@ *)ip3); + + if (in < min) { + *((@type@ *)op1) = min; + } + else if (in > max) { + *((@type@ *)op1) = max; + } + else { + *((@type@ *)op1) = in; + } + } +} + #define @TYPE@_true_divide @TYPE@_divide /**end repeat**/ @@ -2369,6 +2457,26 @@ HALF_ldexp_long(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UN } } +NPY_NO_EXPORT void +HALF_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + TERNARY_LOOP { + const npy_half in = *((npy_half *)ip1); + const npy_half min = *((npy_half *)ip2); + const npy_half max = *((npy_half *)ip3); + + if (npy_half_lt(in, min)) { + *((npy_half *)op1) = min; + } + else if (npy_half_gt(in, max)) { + *((npy_half *)op1) = max; + } + else { + *((npy_half *)op1) = in; + } + } +} + #define HALF_true_divide HALF_divide @@ -2776,6 +2884,32 @@ NPY_NO_EXPORT void } /**end repeat1**/ +NPY_NO_EXPORT void +@TYPE@_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + TERNARY_LOOP { + const @ftype@ inr = ((@ftype@ *)ip1)[0]; + const @ftype@ ini = ((@ftype@ *)ip1)[1]; + const @ftype@ minr = ((@ftype@ *)ip2)[0]; + const @ftype@ mini = ((@ftype@ *)ip2)[1]; + const @ftype@ maxr = ((@ftype@ *)ip3)[0]; + const @ftype@ maxi = ((@ftype@ *)ip3)[1]; + + if (CLT(inr, ini, minr, mini)) { + ((@ftype@ *)op1)[0] = minr; + ((@ftype@ *)op1)[1] = mini; + } + else if (CGT(inr, ini, maxr, maxi)) { + ((@ftype@ *)op1)[0] = maxr; + ((@ftype@ *)op1)[1] = maxi; + } + else { + ((@ftype@ *)op1)[0] = inr; + ((@ftype@ *)op1)[1] = ini; + } + } +} + #define @TYPE@_true_divide @TYPE@_divide /**end repeat**/ diff --git a/numpy/core/src/umath/loops.h.src b/numpy/core/src/umath/loops.h.src index 4243c6522683..40264bd0ccfc 100644 --- a/numpy/core/src/umath/loops.h.src +++ b/numpy/core/src/umath/loops.h.src @@ -29,7 +29,7 @@ /**begin repeat * #kind = equal, not_equal, greater, greater_equal, less, less_equal, - * logical_and, logical_or, absolute, logical_not# + * logical_and, logical_or, absolute, logical_not, clip# **/ NPY_NO_EXPORT void BOOL_@kind@(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); @@ -143,6 +143,9 @@ NPY_NO_EXPORT void NPY_NO_EXPORT void @S@@TYPE@_divmod(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); +NPY_NO_EXPORT void +U@TYPE@_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); + /**end repeat1**/ /**end repeat**/ @@ -262,6 +265,9 @@ NPY_NO_EXPORT void NPY_NO_EXPORT void @TYPE@_ldexp_long(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); +NPY_NO_EXPORT void +@TYPE@_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); + #define @TYPE@_true_divide @TYPE@_divide /**end repeat**/ @@ -374,6 +380,10 @@ NPY_NO_EXPORT void C@TYPE@_@kind@(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); /**end repeat1**/ + +NPY_NO_EXPORT void +C@TYPE@_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); + #define C@TYPE@_true_divide C@TYPE@_divide /**end repeat**/ @@ -429,6 +439,8 @@ NPY_NO_EXPORT void @TYPE@_@kind@(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); /**end repeat1**/ +NPY_NO_EXPORT void +@TYPE@_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); /**end repeat**/ NPY_NO_EXPORT void diff --git a/numpy/core/tests/test_numeric.py b/numpy/core/tests/test_numeric.py index 0f87ffdf2a5f..3b1e5b06a713 100644 --- a/numpy/core/tests/test_numeric.py +++ b/numpy/core/tests/test_numeric.py @@ -1701,6 +1701,32 @@ def test_clip_nan(self): assert_equal(d.clip(min=-2, max=np.nan), d) assert_equal(d.clip(min=np.nan, max=10), d) + def test_clip_with_out_order_c_to_f(self): + # Ensure that the clip() function takes an out with different order from input. + a = self._generate_data(self.nr, self.nc) + out = a.copy(order='F') + out2 = a.copy(order='F') + m = -0.5 + M = 0.6 + self.fastclip(a, m, M, out=out) + self.clip(a, m, M, out=out2) + + assert_array_strict_equal(out, out2) + self.assertTrue(np.isfortran(out)) + + def test_clip_with_out_order_f_to_c(self): + # Ensure that the clip() function takes an out with different order from input. + a = self._generate_data(self.nr, self.nc).copy(order='F') + out = a.copy(order='C') + out2 = a.copy(order='C') + m = -0.5 + M = 0.6 + self.fastclip(a, m, M, out=out) + self.clip(a, m, M, out=out2) + + assert_array_strict_equal(out, out2) + self.assertFalse(np.isfortran(out)) + class TestAllclose(object): rtol = 1e-5 From c33433c4e6bb6b5534436c2b3481d683990dce5d Mon Sep 17 00:00:00 2001 From: Pim de Haan Date: Sun, 14 Aug 2016 00:55:46 +0200 Subject: [PATCH 2/3] WIP: MAINT: Made clip into an ufunc In order to solve issue #7633, it was suggested to make clip into an ufunc. This is an partial implementation of that. The tests should pass except for test_clip_nan (test_numeric.TestClip), which tests NaN behaviour if either min or max is missing. Ufuncs doen't seem to allow for missing arguements, so ideal (f)min/max are used. However, the expected behaviour is asymmetrical: propagate NaN for the first, but not for the other arguments. None of the existing ufuncs has this behaviour. Left to do: - Handle asymmetrical NaN behaviour is either min or max is missing - Implement warning if unsafe typecasting is used - Add a faster loop to loops.c.src to speed up if min or max are not arrays - Update the docs - Create benchmarks and test the new implementation - Remove remnants of the old implementation from - - numeric.py - - fromnumeric.py - - numpy_api.py - - multiarray_api.* - - arraytypes.*.src - - calculation.* --- numpy/core/code_generators/generate_umath.py | 2 +- numpy/core/fromnumeric.py | 59 +--- numpy/core/src/multiarray/calculation.c | 275 +------------------ numpy/core/src/umath/loops.c.src | 262 +++++++++--------- numpy/core/src/umath/ufunc_type_resolution.c | 126 +++++++++ numpy/core/src/umath/ufunc_type_resolution.h | 14 + numpy/core/tests/test_numeric.py | 16 +- 7 files changed, 293 insertions(+), 461 deletions(-) diff --git a/numpy/core/code_generators/generate_umath.py b/numpy/core/code_generators/generate_umath.py index 170289f8ba04..16149bb8d922 100644 --- a/numpy/core/code_generators/generate_umath.py +++ b/numpy/core/code_generators/generate_umath.py @@ -881,7 +881,7 @@ def english_upper(s): 'clip' : Ufunc(3, 1, None, docstrings.get('numpy.core.umath.clip'), - None, + 'PyUFunc_ClipTypeResolver', TD(noobj), ) } diff --git a/numpy/core/fromnumeric.py b/numpy/core/fromnumeric.py index a8c2fd2fb088..4a6e8cec17e3 100644 --- a/numpy/core/fromnumeric.py +++ b/numpy/core/fromnumeric.py @@ -20,7 +20,7 @@ # functions that are methods __all__ = [ 'alen', 'all', 'alltrue', 'amax', 'amin', 'any', 'argmax', - 'argmin', 'argpartition', 'argsort', 'around', 'choose', 'clip', + 'argmin', 'argpartition', 'argsort', 'around', 'choose', 'compress', 'cumprod', 'cumproduct', 'cumsum', 'diagonal', 'mean', 'ndim', 'nonzero', 'partition', 'prod', 'product', 'ptp', 'put', 'rank', 'ravel', 'repeat', 'reshape', 'resize', 'round_', @@ -1670,63 +1670,6 @@ def compress(condition, a, axis=None, out=None): return _wrapfunc(a, 'compress', condition, axis=axis, out=out) -def clip(a, a_min, a_max, out=None): - """ - Clip (limit) the values in an array. - - Given an interval, values outside the interval are clipped to - the interval edges. For example, if an interval of ``[0, 1]`` - is specified, values smaller than 0 become 0, and values larger - than 1 become 1. - - Parameters - ---------- - a : array_like - Array containing elements to clip. - a_min : scalar or array_like or `None` - Minimum value. If `None`, clipping is not performed on lower - interval edge. Not more than one of `a_min` and `a_max` may be - `None`. - a_max : scalar or array_like or `None` - Maximum value. If `None`, clipping is not performed on upper - interval edge. Not more than one of `a_min` and `a_max` may be - `None`. If `a_min` or `a_max` are array_like, then the three - arrays will be broadcasted to match their shapes. - out : ndarray, optional - The results will be placed in this array. It may be the input - array for in-place clipping. `out` must be of the right shape - to hold the output. Its type is preserved. - - Returns - ------- - clipped_array : ndarray - An array with the elements of `a`, but where values - < `a_min` are replaced with `a_min`, and those > `a_max` - with `a_max`. - - See Also - -------- - numpy.doc.ufuncs : Section "Output arguments" - - Examples - -------- - >>> a = np.arange(10) - >>> np.clip(a, 1, 8) - array([1, 1, 2, 3, 4, 5, 6, 7, 8, 8]) - >>> a - array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - >>> np.clip(a, 3, 6, out=a) - array([3, 3, 3, 3, 4, 5, 6, 6, 6, 6]) - >>> a = np.arange(10) - >>> a - array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - >>> np.clip(a, [3, 4, 1, 1, 1, 4, 4, 4, 4, 4], 8) - array([3, 4, 2, 3, 4, 5, 6, 7, 8, 8]) - - """ - return _wrapfunc(a, 'clip', a_min, a_max, out=out) - - def sum(a, axis=None, dtype=None, out=None, keepdims=np._NoValue): """ Sum of array elements over a given axis. diff --git a/numpy/core/src/multiarray/calculation.c b/numpy/core/src/multiarray/calculation.c index 2334c6a83646..58a8c0e9f2bf 100644 --- a/numpy/core/src/multiarray/calculation.c +++ b/numpy/core/src/multiarray/calculation.c @@ -891,19 +891,7 @@ _slow_array_clip(PyArrayObject *self, PyObject *min, PyObject *max, PyArrayObjec NPY_NO_EXPORT PyObject * PyArray_Clip(PyArrayObject *self, PyObject *min, PyObject *max, PyArrayObject *out) { - if (out == NULL) { - return PyObject_CallFunction(n_ops.clip, "OOO", self, min, max); - } - return PyObject_CallFunction(n_ops.clip, "OOOO", self, min, max, out); - - PyArray_FastClipFunc *func; - int outgood = 0, ingood = 0; - PyArrayObject *maxa = NULL; - PyArrayObject *mina = NULL; - PyArrayObject *newout = NULL, *newin = NULL; - PyArray_Descr *indescr = NULL, *newdescr = NULL; - char *max_data, *min_data; - PyObject *zero; + PyObject *res = NULL; /* Treat None the same as NULL */ if (min == Py_None) { @@ -913,262 +901,23 @@ PyArray_Clip(PyArrayObject *self, PyObject *min, PyObject *max, PyArrayObject *o max = NULL; } - if ((max == NULL) && (min == NULL)) { - PyErr_SetString(PyExc_ValueError, - "array_clip: must set either max or min"); - return NULL; + /* Handle missing arguments */ + if (min == NULL && max == NULL) { + res = (PyObject *)self; + Py_INCREF(res); + return res; } - - func = PyArray_DESCR(self)->f->fastclip; - if (func == NULL - || (min != NULL && !PyArray_CheckAnyScalar(min)) - || (max != NULL && !PyArray_CheckAnyScalar(max)) - || PyArray_ISBYTESWAPPED(self) - || (out && PyArray_ISBYTESWAPPED(out))) { - return _slow_array_clip(self, min, max, out); - } - /* Use the fast scalar clip function */ - - /* First we need to figure out the correct type */ - if (min != NULL) { - indescr = PyArray_DescrFromObject(min, NULL); - if (indescr == NULL) { - goto fail; - } + if (min == NULL) { + return _GenericBinaryOutFunction(self, max, out, n_ops.minimum); } - if (max != NULL) { - newdescr = PyArray_DescrFromObject(max, indescr); - Py_XDECREF(indescr); - indescr = NULL; - if (newdescr == NULL) { - goto fail; - } - } - else { - /* Steal the reference */ - newdescr = indescr; - indescr = NULL; - } - - - /* - * Use the scalar descriptor only if it is of a bigger - * KIND than the input array (and then find the - * type that matches both). - */ - if (PyArray_ScalarKind(newdescr->type_num, NULL) > - PyArray_ScalarKind(PyArray_DESCR(self)->type_num, NULL)) { - indescr = PyArray_PromoteTypes(newdescr, PyArray_DESCR(self)); - if (indescr == NULL) { - goto fail; - } - func = indescr->f->fastclip; - if (func == NULL) { - Py_DECREF(indescr); - return _slow_array_clip(self, min, max, out); - } - } - else { - indescr = PyArray_DESCR(self); - Py_INCREF(indescr); - } - Py_DECREF(newdescr); - newdescr = NULL; - - if (!PyDataType_ISNOTSWAPPED(indescr)) { - PyArray_Descr *descr2; - descr2 = PyArray_DescrNewByteorder(indescr, '='); - Py_DECREF(indescr); - indescr = NULL; - if (descr2 == NULL) { - goto fail; - } - indescr = descr2; - } - - /* Convert max to an array */ - if (max != NULL) { - Py_INCREF(indescr); - maxa = (PyArrayObject *)PyArray_FromAny(max, indescr, 0, 0, - NPY_ARRAY_DEFAULT, NULL); - if (maxa == NULL) { - goto fail; - } - } - - /* - * If we are unsigned, then make sure min is not < 0 - * This is to match the behavior of _slow_array_clip - * - * We allow min and max to go beyond the limits - * for other data-types in which case they - * are interpreted as their modular counterparts. - */ - if (min != NULL) { - if (PyArray_ISUNSIGNED(self)) { - int cmp; - zero = PyInt_FromLong(0); - cmp = PyObject_RichCompareBool(min, zero, Py_LT); - if (cmp == -1) { - Py_DECREF(zero); - goto fail; - } - if (cmp == 1) { - min = zero; - } - else { - Py_DECREF(zero); - Py_INCREF(min); - } - } - else { - Py_INCREF(min); - } - - /* Convert min to an array */ - Py_INCREF(indescr); - mina = (PyArrayObject *)PyArray_FromAny(min, indescr, 0, 0, - NPY_ARRAY_DEFAULT, NULL); - Py_DECREF(min); - if (mina == NULL) { - goto fail; - } + if (max == NULL) { + return _GenericBinaryOutFunction(self, min, out, n_ops.maximum); } - /* - * Check to see if input is single-segment, aligned, - * and in native byteorder - */ - if (PyArray_ISONESEGMENT(self) && - PyArray_CHKFLAGS(self, NPY_ARRAY_ALIGNED) && - PyArray_ISNOTSWAPPED(self) && - (PyArray_DESCR(self) == indescr)) { - ingood = 1; - } - if (!ingood) { - int flags; - - if (PyArray_ISFORTRAN(self)) { - flags = NPY_ARRAY_FARRAY; - } - else { - flags = NPY_ARRAY_CARRAY; - } - Py_INCREF(indescr); - newin = (PyArrayObject *)PyArray_FromArray(self, indescr, flags); - if (newin == NULL) { - goto fail; - } - } - else { - newin = self; - Py_INCREF(newin); - } - - /* - * At this point, newin is a single-segment, aligned, and correct - * byte-order array of the correct type - * - * if ingood == 0, then it is a copy, otherwise, - * it is the original input. - */ - - /* - * If we have already made a copy of the data, then use - * that as the output array - */ - if (out == NULL && !ingood) { - out = newin; - } - - /* - * Now, we know newin is a usable array for fastclip, - * we need to make sure the output array is available - * and usable - */ if (out == NULL) { - Py_INCREF(indescr); - out = (PyArrayObject*)PyArray_NewFromDescr(Py_TYPE(self), - indescr, PyArray_NDIM(self), - PyArray_DIMS(self), - NULL, NULL, - PyArray_ISFORTRAN(self), - (PyObject *)self); - if (out == NULL) { - goto fail; - } - - outgood = 1; - } - else Py_INCREF(out); - /* Input is good at this point */ - if (out == newin) { - outgood = 1; - } - if (!outgood && PyArray_ISONESEGMENT(out) && - PyArray_CHKFLAGS(out, NPY_ARRAY_ALIGNED) && - PyArray_ISNOTSWAPPED(out) && - PyArray_EquivTypes(PyArray_DESCR(out), indescr)) { - outgood = 1; - } - - /* - * Do we still not have a suitable output array? - * Create one, now - */ - if (!outgood) { - int oflags; - if (PyArray_ISFORTRAN(out)) - oflags = NPY_ARRAY_FARRAY; - else - oflags = NPY_ARRAY_CARRAY; - oflags |= NPY_ARRAY_UPDATEIFCOPY | NPY_ARRAY_FORCECAST; - Py_INCREF(indescr); - newout = (PyArrayObject*)PyArray_FromArray(out, indescr, oflags); - if (newout == NULL) { - goto fail; - } - } - else { - newout = out; - Py_INCREF(newout); - } - - /* make sure the shape of the output array is the same */ - if (!PyArray_SAMESHAPE(newin, newout)) { - PyErr_SetString(PyExc_ValueError, "clip: Output array must have the" - "same shape as the input."); - goto fail; - } - - /* Now we can call the fast-clip function */ - min_data = max_data = NULL; - if (mina != NULL) { - min_data = PyArray_DATA(mina); - } - if (maxa != NULL) { - max_data = PyArray_DATA(maxa); + return PyObject_CallFunction(n_ops.clip, "OOO", self, min, max); } - func(PyArray_DATA(newin), PyArray_SIZE(newin), min_data, max_data, PyArray_DATA(newout)); - - /* Clean up temporary variables */ - Py_XDECREF(indescr); - Py_XDECREF(newdescr); - Py_XDECREF(mina); - Py_XDECREF(maxa); - Py_DECREF(newin); - /* Copy back into out if out was not already a nice array. */ - Py_DECREF(newout); - return (PyObject *)out; - - fail: - Py_XDECREF(indescr); - Py_XDECREF(newdescr); - Py_XDECREF(maxa); - Py_XDECREF(mina); - Py_XDECREF(newin); - PyArray_XDECREF_ERR(newout); - return NULL; + return PyObject_CallFunction(n_ops.clip, "OOOO", self, min, max, out); } diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src index 8d9209986d38..c37987dbad6b 100644 --- a/numpy/core/src/umath/loops.c.src +++ b/numpy/core/src/umath/loops.c.src @@ -231,13 +231,6 @@ npy_intp i;\ for(i = 0; i < n; i++, ip1 += is1, ip2 += is2, op1 += os1, op2 += os2) -#define TERNARY_LOOP\ - char *ip1 = args[0], *ip2 = args[1], *ip3 = args[2], *op1 = args[3];\ - npy_intp is1 = steps[0], is2 = steps[1], is3 = steps[2], os1 = steps[3];\ - npy_intp n = dimensions[0];\ - npy_intp i;\ - for(i = 0; i < n; i++, ip1 += is1, ip2 += is2, ip3 += is3, op1 += os1) - /****************************************************************************** ** GENERIC FLOAT LOOPS ** *****************************************************************************/ @@ -811,26 +804,6 @@ BOOL__ones_like(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UN } } -NPY_NO_EXPORT void -BOOL_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) -{ - TERNARY_LOOP { - const npy_bool in = *((npy_bool *)ip1) != 0; - const npy_bool min = *((npy_bool *)ip2) != 0; - const npy_bool max = *((npy_bool *)ip3) != 0; - - if (in < min) { - *((npy_bool *)op1) = min; - } - else if (in > max) { - *((npy_bool *)op1) = max; - } - else { - *((npy_bool *)op1) = in; - } - } -} - /* ***************************************************************************** @@ -1073,26 +1046,6 @@ NPY_NO_EXPORT void } } -NPY_NO_EXPORT void -@TYPE@_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) -{ - TERNARY_LOOP { - const @type@ in = *((@type@ *)ip1); - const @type@ min = *((@type@ *)ip2); - const @type@ max = *((@type@ *)ip3); - - if (in < min) { - *((@type@ *)op1) = min; - } - else if (in > max) { - *((@type@ *)op1) = max; - } - else { - *((@type@ *)op1) = in; - } - } -} - /**end repeat**/ /**begin repeat @@ -1413,27 +1366,6 @@ NPY_NO_EXPORT void } /**end repeat1**/ - -NPY_NO_EXPORT void -@TYPE@_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) -{ - TERNARY_LOOP { - const @type@ in = *((@type@ *)ip1); - const @type@ min = *((@type@ *)ip2); - const @type@ max = *((@type@ *)ip3); - - if (in < min) { - *((@type@ *)op1) = min; - } - else if (in > max) { - *((@type@ *)op1) = max; - } - else { - *((@type@ *)op1) = in; - } - } -} - /**end repeat**/ NPY_NO_EXPORT void @@ -2110,26 +2042,6 @@ NPY_NO_EXPORT void } } -NPY_NO_EXPORT void -@TYPE@_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) -{ - TERNARY_LOOP { - const @type@ in = *((@type@ *)ip1); - const @type@ min = *((@type@ *)ip2); - const @type@ max = *((@type@ *)ip3); - - if (in < min) { - *((@type@ *)op1) = min; - } - else if (in > max) { - *((@type@ *)op1) = max; - } - else { - *((@type@ *)op1) = in; - } - } -} - #define @TYPE@_true_divide @TYPE@_divide /**end repeat**/ @@ -2457,26 +2369,6 @@ HALF_ldexp_long(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UN } } -NPY_NO_EXPORT void -HALF_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) -{ - TERNARY_LOOP { - const npy_half in = *((npy_half *)ip1); - const npy_half min = *((npy_half *)ip2); - const npy_half max = *((npy_half *)ip3); - - if (npy_half_lt(in, min)) { - *((npy_half *)op1) = min; - } - else if (npy_half_gt(in, max)) { - *((npy_half *)op1) = max; - } - else { - *((npy_half *)op1) = in; - } - } -} - #define HALF_true_divide HALF_divide @@ -2884,32 +2776,6 @@ NPY_NO_EXPORT void } /**end repeat1**/ -NPY_NO_EXPORT void -@TYPE@_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) -{ - TERNARY_LOOP { - const @ftype@ inr = ((@ftype@ *)ip1)[0]; - const @ftype@ ini = ((@ftype@ *)ip1)[1]; - const @ftype@ minr = ((@ftype@ *)ip2)[0]; - const @ftype@ mini = ((@ftype@ *)ip2)[1]; - const @ftype@ maxr = ((@ftype@ *)ip3)[0]; - const @ftype@ maxi = ((@ftype@ *)ip3)[1]; - - if (CLT(inr, ini, minr, mini)) { - ((@ftype@ *)op1)[0] = minr; - ((@ftype@ *)op1)[1] = mini; - } - else if (CGT(inr, ini, maxr, maxi)) { - ((@ftype@ *)op1)[0] = maxr; - ((@ftype@ *)op1)[1] = maxi; - } - else { - ((@ftype@ *)op1)[0] = inr; - ((@ftype@ *)op1)[1] = ini; - } - } -} - #define @TYPE@_true_divide @TYPE@_divide /**end repeat**/ @@ -3008,3 +2874,131 @@ OBJECT_sign(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED ** END LOOPS ** ***************************************************************************** */ + + + +/* + ***************************************************************************** + ** CLIP LOOPS ** + ***************************************************************************** + */ + + + +#define TERNARY_LOOP\ + char *ip1 = args[0], *ip2 = args[1], *ip3 = args[2], *op1 = args[3];\ + npy_intp is1 = steps[0], is2 = steps[1], is3 = steps[2], os1 = steps[3];\ + npy_intp n = dimensions[0];\ + npy_intp i;\ + for(i = 0; i < n; i++, ip1 += is1, ip2 += is2, ip3 += is3, op1 += os1) + +#define _LESS_THAN(a, b) ((a) < (b)) +#define _GREATER_THAN(a, b) ((a) > (b)) + +/* + * In fastclip, 'b' was already checked for NaN, so the half comparison + * only needs to check 'a' for NaN. + */ + +#define _HALF_LESS_THAN(a, b) (!npy_half_isnan(a) && npy_half_lt_nonan(a, b)) +#define _HALF_GREATER_THAN(a, b) (!npy_half_isnan(a) && npy_half_lt_nonan(b, a)) + +/**begin repeat + * + * #TYPE = BOOL, + * BYTE, UBYTE, SHORT, USHORT, INT, UINT, + * LONG, ULONG, LONGLONG, ULONGLONG, + * HALF, FLOAT, DOUBLE, LONGDOUBLE, + * DATETIME, TIMEDELTA# + * #type = npy_bool, + * npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint, + * npy_long, npy_ulong, npy_longlong, npy_ulonglong, + * npy_half, npy_float, npy_double, npy_longdouble, + * npy_datetime, npy_timedelta# + * #isfloat = 0*11, 1*4, 0*2# + * #isnan = nop*11, npy_half_isnan, npy_isnan*3, nop*2# + * #lt = _LESS_THAN*11, _HALF_LESS_THAN, _LESS_THAN*5# + * #gt = _GREATER_THAN*11, _HALF_GREATER_THAN, _GREATER_THAN*5# + */ + +NPY_NO_EXPORT void +@TYPE@_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + TERNARY_LOOP { + const @type@ in = *((@type@ *)ip1); + const @type@ min = *((@type@ *)ip2); + const @type@ max = *((@type@ *)ip3); + if (@lt@(in, min)) { + *((@type@ *)op1) = min; + } + else if (@gt@(in, max)) { + *((@type@ *)op1) = max; + } + else { + *((@type@ *)op1) = in; + } + } +} + +/**end repeat**/ + +#undef _LESS_THAN +#undef _GREATER_THAN +#undef _HALF_LESS_THAN +#undef _HALF_GREATER_THAN + + +#define CGE(xr,xi,yr,yi) ((xr > yr && !npy_isnan(xi) && !npy_isnan(yi)) \ + || (xr == yr && xi >= yi)) +#define CLE(xr,xi,yr,yi) ((xr < yr && !npy_isnan(xi) && !npy_isnan(yi)) \ + || (xr == yr && xi <= yi)) +#define CGT(xr,xi,yr,yi) ((xr > yr && !npy_isnan(xi) && !npy_isnan(yi)) \ + || (xr == yr && xi > yi)) +#define CLT(xr,xi,yr,yi) ((xr < yr && !npy_isnan(xi) && !npy_isnan(yi)) \ + || (xr == yr && xi < yi)) +#define CEQ(xr,xi,yr,yi) (xr == yr && xi == yi) +#define CNE(xr,xi,yr,yi) (xr != yr || xi != yi) + + +/**begin repeat + * complex types + * #TYPE = CFLOAT, CDOUBLE, CLONGDOUBLE# + * #ftype = npy_float, npy_double, npy_longdouble# + * #c = f, , l# + * #C = F, , L# + */ + +NPY_NO_EXPORT void +@TYPE@_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + TERNARY_LOOP { + const @ftype@ inr = ((@ftype@ *)ip1)[0]; + const @ftype@ ini = ((@ftype@ *)ip1)[1]; + const @ftype@ minr = ((@ftype@ *)ip2)[0]; + const @ftype@ mini = ((@ftype@ *)ip2)[1]; + const @ftype@ maxr = ((@ftype@ *)ip3)[0]; + const @ftype@ maxi = ((@ftype@ *)ip3)[1]; + + if (CLT(inr, ini, minr, mini)) { + ((@ftype@ *)op1)[0] = minr; + ((@ftype@ *)op1)[1] = mini; + } + else if (CGT(inr, ini, maxr, maxi)) { + ((@ftype@ *)op1)[0] = maxr; + ((@ftype@ *)op1)[1] = maxi; + } + else { + ((@ftype@ *)op1)[0] = inr; + ((@ftype@ *)op1)[1] = ini; + } + } +} + + +/**end repeat**/ +#undef CGE +#undef CLE +#undef CGT +#undef CLT +#undef CEQ +#undef CNE diff --git a/numpy/core/src/umath/ufunc_type_resolution.c b/numpy/core/src/umath/ufunc_type_resolution.c index 0fd3c45c5d36..66dba3fac191 100644 --- a/numpy/core/src/umath/ufunc_type_resolution.c +++ b/numpy/core/src/umath/ufunc_type_resolution.c @@ -511,6 +511,132 @@ PyUFunc_SimpleBinaryOperationTypeResolver(PyUFuncObject *ufunc, return 0; } + + +/* + * This function applies special type resolution rules for the case + * where all the functions have the pattern XXX->X, using + * PyArray_ResultType instead of a linear search to get the best + * loop. + * + * Note that a simpler linear search through the functions loop + * is still done, but switching to a simple array lookup for + * built-in types would be better at some point. + * + * Returns 0 on success, -1 on error. + */ +NPY_NO_EXPORT int +PyUFunc_SimpleTernaryOperationTypeResolver(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes) +{ + int i, type_num1, type_num2, type_num3; + const char *ufunc_name; + + ufunc_name = ufunc->name ? ufunc->name : ""; + + if (ufunc->nin != 3 || ufunc->nout != 1) { + PyErr_Format(PyExc_RuntimeError, "ufunc %s is configured " + "to use ternary operation type resolution but has " + "the wrong number of inputs or outputs", + ufunc_name); + return -1; + } + + /* + * Use the default type resolution if there's a custom data type + * or object arrays. + */ + type_num1 = PyArray_DESCR(operands[0])->type_num; + type_num2 = PyArray_DESCR(operands[1])->type_num; + type_num3 = PyArray_DESCR(operands[2])->type_num; + if (type_num1 >= NPY_NTYPES || type_num2 >= NPY_NTYPES || + type_num3 >= NPY_NTYPES || type_num1 == NPY_OBJECT || + type_num2 == NPY_OBJECT || type_num3 == NPY_OBJECT) { + return PyUFunc_DefaultTypeResolver(ufunc, casting, operands, + type_tup, out_dtypes); + } + + if (type_tup == NULL) { + /* Input types are the result type */ + out_dtypes[0] = PyArray_ResultType(3, operands, 0, NULL); + if (out_dtypes[0] == NULL) { + return -1; + } + out_dtypes[1] = out_dtypes[0]; + Py_INCREF(out_dtypes[1]); + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + out_dtypes[3] = out_dtypes[0]; + Py_INCREF(out_dtypes[3]); + } + else { + PyObject *item; + PyArray_Descr *dtype = NULL; + + /* + * If the type tuple isn't a single-element tuple, let the + * default type resolution handle this one. + */ + if (!PyTuple_Check(type_tup) || PyTuple_GET_SIZE(type_tup) != 1) { + return PyUFunc_DefaultTypeResolver(ufunc, casting, + operands, type_tup, out_dtypes); + } + + item = PyTuple_GET_ITEM(type_tup, 0); + + if (item == Py_None) { + PyErr_SetString(PyExc_ValueError, + "require data type in the type tuple"); + return -1; + } + else if (!PyArray_DescrConverter(item, &dtype)) { + return -1; + } + + out_dtypes[0] = ensure_dtype_nbo(dtype); + if (out_dtypes[0] == NULL) { + return -1; + } + out_dtypes[1] = out_dtypes[0]; + Py_INCREF(out_dtypes[1]); + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + out_dtypes[3] = out_dtypes[0]; + Py_INCREF(out_dtypes[3]); + } + + /* Check against the casting rules */ + if (PyUFunc_ValidateCasting(ufunc, casting, operands, out_dtypes) < 0) { + for (i = 0; i < 4; ++i) { + Py_DECREF(out_dtypes[i]); + out_dtypes[i] = NULL; + } + return -1; + } + + return 0; +} + +/* + * This function applies special type resolution rules for the clip + * ufunc. For legacy reasons, allow float -> int. + * + * Returns 0 on success, -1 on error. + */ +NPY_NO_EXPORT int +PyUFunc_ClipTypeResolver(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes) +{ + casting = NPY_UNSAFE_CASTING; + return PyUFunc_SimpleTernaryOperationTypeResolver(ufunc, casting, operands, type_tup, out_dtypes); +} + /* * This function applies special type resolution rules for the absolute * ufunc. This ufunc converts complex -> float, so isn't covered diff --git a/numpy/core/src/umath/ufunc_type_resolution.h b/numpy/core/src/umath/ufunc_type_resolution.h index eaf5e91cec67..1f81b8f4b777 100644 --- a/numpy/core/src/umath/ufunc_type_resolution.h +++ b/numpy/core/src/umath/ufunc_type_resolution.h @@ -36,6 +36,13 @@ PyUFunc_SimpleBinaryOperationTypeResolver(PyUFuncObject *ufunc, PyObject *type_tup, PyArray_Descr **out_dtypes); +NPY_NO_EXPORT int +PyUFunc_SimpleTernaryOperationTypeResolver(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes); + NPY_NO_EXPORT int PyUFunc_AbsoluteTypeResolver(PyUFuncObject *ufunc, NPY_CASTING casting, @@ -85,6 +92,13 @@ PyUFunc_DivisionTypeResolver(PyUFuncObject *ufunc, PyObject *type_tup, PyArray_Descr **out_dtypes); +NPY_NO_EXPORT int +PyUFunc_ClipTypeResolver(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes); + /* * Does a linear search for the best inner loop of the ufunc. * diff --git a/numpy/core/tests/test_numeric.py b/numpy/core/tests/test_numeric.py index 3b1e5b06a713..385770fde85e 100644 --- a/numpy/core/tests/test_numeric.py +++ b/numpy/core/tests/test_numeric.py @@ -1695,11 +1695,17 @@ def test_clip_func_takes_out(self): def test_clip_nan(self): d = np.arange(7.) - assert_equal(d.clip(min=np.nan), d) - assert_equal(d.clip(max=np.nan), d) - assert_equal(d.clip(min=np.nan, max=np.nan), d) - assert_equal(d.clip(min=-2, max=np.nan), d) - assert_equal(d.clip(min=np.nan, max=10), d) + assert_array_equal(d.clip(min=np.nan), d) + assert_array_equal(d.clip(max=np.nan), d) + assert_array_equal(d.clip(min=np.nan, max=np.nan), d) + assert_array_equal(d.clip(min=-2, max=np.nan), d) + assert_array_equal(d.clip(min=np.nan, max=10), d) + + def test_clip_nan2(self): + a = np.array([np.NaN]) + assert_array_equal(a.clip(min=2), a) + assert_array_equal(a.clip(max=2), a) + assert_array_equal(a.clip(min=2, max=2), a) def test_clip_with_out_order_c_to_f(self): # Ensure that the clip() function takes an out with different order from input. From 1d14f84f9555b32a3c9b44f3414137c2c120eaf9 Mon Sep 17 00:00:00 2001 From: Pim de Haan Date: Sat, 10 Jun 2017 17:50:12 +0200 Subject: [PATCH 3/3] Bugfix and incorporated docstring improvement #8475 --- numpy/core/code_generators/ufunc_docstrings.py | 6 +++--- numpy/core/src/umath/loops.h.src | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/numpy/core/code_generators/ufunc_docstrings.py b/numpy/core/code_generators/ufunc_docstrings.py index 19a0a9e2dd66..a99e65d1a4ac 100644 --- a/numpy/core/code_generators/ufunc_docstrings.py +++ b/numpy/core/code_generators/ufunc_docstrings.py @@ -3686,8 +3686,8 @@ def add_newdoc(place, name, doc): a_min : scalar or array_like Minimum value. a_max : scalar or array_like - Maximum value. If `a_min` or `a_max` are array_like, then they will - be broadcasted to the shape of `a`. + Maximum value. If `a_min` or `a_max` are array_like, then the + three arrays will be broadcasted to match their shapes. out : ndarray, optional The results will be placed in this array. It may be the input array for in-place clipping. `out` must be of the right shape @@ -3716,7 +3716,7 @@ def add_newdoc(place, name, doc): >>> a = np.arange(10) >>> a array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - >>> np.clip(a, [3,4,1,1,1,4,4,4,4,4], 8) + >>> np.clip(a, [3, 4, 1, 1, 1, 4, 4, 4, 4, 4], 8) array([3, 4, 2, 3, 4, 5, 6, 7, 8, 8]) """) diff --git a/numpy/core/src/umath/loops.h.src b/numpy/core/src/umath/loops.h.src index 40264bd0ccfc..e264aa2b6e4f 100644 --- a/numpy/core/src/umath/loops.h.src +++ b/numpy/core/src/umath/loops.h.src @@ -144,7 +144,7 @@ NPY_NO_EXPORT void @S@@TYPE@_divmod(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); NPY_NO_EXPORT void -U@TYPE@_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); +@S@@TYPE@_clip(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); /**end repeat1**/