From 8db9662a3352ab029a5bbc2d81e5363d7a388e2e Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Fri, 21 Apr 2017 00:05:44 -0700 Subject: [PATCH 1/2] ENH: add np.positive ufunc and use it for ndarray.__pos__ xref GH8932 --- doc/release/1.13.0-notes.rst | 5 +++ doc/source/reference/c-api.array.rst | 4 +-- doc/source/reference/ufuncs.rst | 1 + numpy/core/code_generators/generate_umath.py | 8 +++++ .../core/code_generators/ufunc_docstrings.py | 21 ++++++++++++ numpy/core/src/umath/funcs.inc.src | 8 +++++ numpy/core/src/umath/loops.c.src | 33 +++++++++++++++++++ numpy/core/src/umath/loops.h.src | 8 +++++ numpy/core/tests/test_datetime.py | 6 ++++ numpy/core/tests/test_half.py | 1 + numpy/core/tests/test_umath.py | 20 +++++++++++ 11 files changed, 113 insertions(+), 2 deletions(-) diff --git a/doc/release/1.13.0-notes.rst b/doc/release/1.13.0-notes.rst index df6ce7c4b77f..f594c1825cb9 100644 --- a/doc/release/1.13.0-notes.rst +++ b/doc/release/1.13.0-notes.rst @@ -327,6 +327,11 @@ regarding "workspace" sizes, and in some places may use faster algorithms. This now works on empty arrays, returning 0, and can reduce over multiple axes. Previously, a ``ValueError`` was thrown in these cases. +New ``positive`` ufunc +---------------------- +This ufunc corresponds to unary `+`, but unlike `+` on an ndarray it will raise +an error if array values do not support numeric operations. + Changes ======= diff --git a/doc/source/reference/c-api.array.rst b/doc/source/reference/c-api.array.rst index b50f86e46392..35df42daae75 100644 --- a/doc/source/reference/c-api.array.rst +++ b/doc/source/reference/c-api.array.rst @@ -3129,8 +3129,8 @@ Internal Flexibility **add**, **subtract**, **multiply**, **divide**, **remainder**, **power**, **square**, **reciprocal**, - **ones_like**, **sqrt**, **negative**, **absolute**, - **invert**, **left_shift**, **right_shift**, + **ones_like**, **sqrt**, **negative**, **positive**, + **absolute**, **invert**, **left_shift**, **right_shift**, **bitwise_and**, **bitwise_xor**, **bitwise_or**, **less**, **less_equal**, **equal**, **not_equal**, **greater**, **greater_equal**, **floor_divide**, diff --git a/doc/source/reference/ufuncs.rst b/doc/source/reference/ufuncs.rst index bcd3d5f0aaa2..94663a141931 100644 --- a/doc/source/reference/ufuncs.rst +++ b/doc/source/reference/ufuncs.rst @@ -505,6 +505,7 @@ Math operations true_divide floor_divide negative + positive power remainder mod diff --git a/numpy/core/code_generators/generate_umath.py b/numpy/core/code_generators/generate_umath.py index 4a8566d9d1bc..dfba04c18148 100644 --- a/numpy/core/code_generators/generate_umath.py +++ b/numpy/core/code_generators/generate_umath.py @@ -398,6 +398,14 @@ def english_upper(s): TD(cmplx, f='neg'), TD(O, f='PyNumber_Negative'), ), +'positive': + Ufunc(1, 1, None, + docstrings.get('numpy.core.umath.positive'), + 'PyUFunc_SimpleUnaryOperationTypeResolver', + TD(ints+flts+timedeltaonly), + TD(cmplx, f='pos'), + TD(O, f='PyNumber_Positive'), + ), 'sign': Ufunc(1, 1, None, docstrings.get('numpy.core.umath.sign'), diff --git a/numpy/core/code_generators/ufunc_docstrings.py b/numpy/core/code_generators/ufunc_docstrings.py index ad4a10d3ba91..ed9e05b15b25 100644 --- a/numpy/core/code_generators/ufunc_docstrings.py +++ b/numpy/core/code_generators/ufunc_docstrings.py @@ -2537,6 +2537,27 @@ def add_newdoc(place, name, doc): """) +add_newdoc('numpy.core.umath', 'positive', + """ + Numerical positive, element-wise. + + Parameters + ---------- + x : array_like or scalar + Input array. + + Returns + ------- + y : ndarray or scalar + Returned array or scalar: `y = +x`. + + Notes + ----- + Equivalent to `x.copy()`, but only defined for types that support + arithmetic. + + """) + add_newdoc('numpy.core.umath', 'not_equal', """ Return (x1 != x2) element-wise. diff --git a/numpy/core/src/umath/funcs.inc.src b/numpy/core/src/umath/funcs.inc.src index 9887120f5c8f..5613c30ee60e 100644 --- a/numpy/core/src/umath/funcs.inc.src +++ b/numpy/core/src/umath/funcs.inc.src @@ -187,6 +187,14 @@ nc_neg@c@(@ctype@ *a, @ctype@ *r) return; } +static void +nc_pos@c@(@ctype@ *a, @ctype@ *r) +{ + r->real = +a->real; + r->imag = +a->imag; + return; +} + static void nc_sqrt@c@(@ctype@ *x, @ctype@ *r) { diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src index 24364afbde8b..47faaf180262 100644 --- a/numpy/core/src/umath/loops.c.src +++ b/numpy/core/src/umath/loops.c.src @@ -833,6 +833,12 @@ NPY_NO_EXPORT void } } +NPY_NO_EXPORT void +@TYPE@_positive(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + UNARY_LOOP_FAST(@type@, @type@, *out = +in); +} + /**begin repeat1 * #isa = , _avx2# * #ISA = , AVX2# @@ -1184,6 +1190,15 @@ TIMEDELTA_negative(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY } } +NPY_NO_EXPORT void +TIMEDELTA_positive(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + UNARY_LOOP { + const npy_timedelta in1 = *(npy_timedelta *)ip1; + *((npy_timedelta *)op1) = +in1; + } +} + NPY_NO_EXPORT void TIMEDELTA_absolute(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) { @@ -1885,6 +1900,15 @@ NPY_NO_EXPORT void } } +NPY_NO_EXPORT void +@TYPE@_positive(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + UNARY_LOOP { + const @type@ in1 = *(@type@ *)ip1; + *((@type@ *)op1) = +in1; + } +} + NPY_NO_EXPORT void @TYPE@_sign(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) { @@ -2188,6 +2212,15 @@ HALF_negative(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUS } } +NPY_NO_EXPORT void +HALF_positive(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + UNARY_LOOP { + const npy_half in1 = *(npy_half *)ip1; + *((npy_half *)op1) = +in1; + } +} + NPY_NO_EXPORT void HALF_sign(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) { diff --git a/numpy/core/src/umath/loops.h.src b/numpy/core/src/umath/loops.h.src index e5c0dc8551d2..d20b776a0132 100644 --- a/numpy/core/src/umath/loops.h.src +++ b/numpy/core/src/umath/loops.h.src @@ -61,6 +61,9 @@ BOOL__ones_like(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UN NPY_NO_EXPORT void @S@@TYPE@__ones_like(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(data)); +NPY_NO_EXPORT void +@S@@TYPE@_positive(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); + /**begin repeat2 * #isa = , _avx2# */ @@ -235,6 +238,8 @@ NPY_NO_EXPORT void NPY_NO_EXPORT void @TYPE@_negative(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); +NPY_NO_EXPORT void +@TYPE@_positive(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); NPY_NO_EXPORT void @TYPE@_sign(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); @@ -383,6 +388,9 @@ C@TYPE@_@kind@(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNU NPY_NO_EXPORT void TIMEDELTA_negative(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); +NPY_NO_EXPORT void +TIMEDELTA_positive(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); + NPY_NO_EXPORT void TIMEDELTA_absolute(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py index b5d096d478b6..12ebd5ae9586 100644 --- a/numpy/core/tests/test_datetime.py +++ b/numpy/core/tests/test_datetime.py @@ -794,6 +794,12 @@ def test_datetime_unary(self): assert_equal(np.negative(tdb), tda) assert_equal(np.negative(tdb).dtype, tda.dtype) + # positive ufunc + assert_equal(np.positive(tda), tda) + assert_equal(np.positive(tda).dtype, tda.dtype) + assert_equal(np.positive(tdb), tdb) + assert_equal(np.positive(tdb).dtype, tdb.dtype) + # absolute ufunc assert_equal(np.absolute(tdb), tda) assert_equal(np.absolute(tdb).dtype, tda.dtype) diff --git a/numpy/core/tests/test_half.py b/numpy/core/tests/test_half.py index 56b574ae8183..75b74f407626 100644 --- a/numpy/core/tests/test_half.py +++ b/numpy/core/tests/test_half.py @@ -323,6 +323,7 @@ def test_half_ufuncs(self): assert_equal(np.conjugate(b), b) assert_equal(np.absolute(b), [2, 5, 1, 4, 3]) assert_equal(np.negative(b), [2, -5, -1, -4, -3]) + assert_equal(np.positive(b), b) assert_equal(np.sign(b), [-1, 1, 1, 1, 1]) assert_equal(np.modf(b), ([0, 0, 0, 0, 0], b)) assert_equal(np.frexp(b), ([-0.5, 0.625, 0.5, 0.5, 0.75], [2, 3, 1, 3, 2])) diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py index 41108ab5f8d2..62882eaa9898 100644 --- a/numpy/core/tests/test_umath.py +++ b/numpy/core/tests/test_umath.py @@ -1057,6 +1057,7 @@ class TestBool(TestCase): def test_exceptions(self): a = np.ones(1, dtype=np.bool_) assert_raises(TypeError, np.negative, a) + assert_raises(TypeError, np.positive, a) assert_raises(TypeError, np.subtract, a, a) def test_truth_table_logical(self): @@ -1349,6 +1350,25 @@ def test_lower_align(self): np.abs(np.ones_like(d), out=d) +class TestPositive(TestCase): + def test_valid(self): + valid_dtypes = [int, float, complex, object] + for dtype in valid_dtypes: + x = np.arange(5, dtype=dtype) + result = np.positive(x) + assert_equal(x, result, err_msg=str(dtype)) + + def test_invalid(self): + with assert_raises(TypeError): + np.positive(True) + with assert_raises(TypeError): + np.positive(np.datetime64('2000-01-01')) + with assert_raises(TypeError): + np.positive(np.array(['foo'], dtype=str)) + with assert_raises(TypeError): + np.positive(np.array(['bar'], dtype=object)) + + class TestSpecialMethods(TestCase): def test_wrap(self): From e799be5f6a1bb2e0a294a1b0f03a1dc5333f529b Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Sun, 30 Apr 2017 21:33:21 -0700 Subject: [PATCH 2/2] ENH: add __pos__ to NDArrayOperatorsMixin --- numpy/lib/mixins.py | 5 +++-- numpy/lib/tests/test_mixins.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/numpy/lib/mixins.py b/numpy/lib/mixins.py index b5231e372046..bbeed1437f83 100644 --- a/numpy/lib/mixins.py +++ b/numpy/lib/mixins.py @@ -70,8 +70,8 @@ class NDArrayOperatorsMixin(object): implement. This class does not yet implement the special operators corresponding - to ``divmod``, unary ``+`` or ``matmul`` (``@``), because these operation - do not yet have corresponding NumPy ufuncs. + to ``divmod`` or ``matmul`` (``@``), because these operation do not yet + have corresponding NumPy ufuncs. It is useful for writing classes that do not inherit from `numpy.ndarray`, but that should support arithmetic and numpy universal functions like @@ -174,5 +174,6 @@ def __repr__(self): # unary methods __neg__ = _unary_method(um.negative, 'neg') + __pos__ = _unary_method(um.positive, 'pos') __abs__ = _unary_method(um.absolute, 'abs') __invert__ = _unary_method(um.invert, 'invert') diff --git a/numpy/lib/tests/test_mixins.py b/numpy/lib/tests/test_mixins.py index 57c4a4cd80e0..287d4ed2955a 100644 --- a/numpy/lib/tests/test_mixins.py +++ b/numpy/lib/tests/test_mixins.py @@ -143,7 +143,7 @@ def test_unary_methods(self): array = np.array([-1, 0, 1, 2]) array_like = ArrayLike(array) for op in [operator.neg, - # pos is not yet implemented + operator.pos, abs, operator.invert]: _assert_equal_type_and_value(op(array_like), ArrayLike(op(array)))