summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephan Hoyer <shoyer@google.com>2017-04-21 00:05:44 -0700
committerStephan Hoyer <shoyer@google.com>2017-04-30 20:37:48 -0700
commit8db9662a3352ab029a5bbc2d81e5363d7a388e2e (patch)
tree829b6d03dc3927538336913158fd7b15ee12682f
parent590facafcbd454fc02f96aa52c1c3412d6cccd14 (diff)
downloadnumpy-8db9662a3352ab029a5bbc2d81e5363d7a388e2e.tar.gz
ENH: add np.positive ufunc and use it for ndarray.__pos__
xref GH8932
-rw-r--r--doc/release/1.13.0-notes.rst5
-rw-r--r--doc/source/reference/c-api.array.rst4
-rw-r--r--doc/source/reference/ufuncs.rst1
-rw-r--r--numpy/core/code_generators/generate_umath.py8
-rw-r--r--numpy/core/code_generators/ufunc_docstrings.py21
-rw-r--r--numpy/core/src/umath/funcs.inc.src8
-rw-r--r--numpy/core/src/umath/loops.c.src33
-rw-r--r--numpy/core/src/umath/loops.h.src8
-rw-r--r--numpy/core/tests/test_datetime.py6
-rw-r--r--numpy/core/tests/test_half.py1
-rw-r--r--numpy/core/tests/test_umath.py20
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 df6ce7c4b..f594c1825 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 b50f86e46..35df42daa 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 bcd3d5f0a..94663a141 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 4a8566d9d..dfba04c18 100644
--- a/numpy/core/code_generators/generate_umath.py
+++ b/numpy/core/code_generators/generate_umath.py
@@ -398,6 +398,14 @@ defdict = {
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 ad4a10d3b..ed9e05b15 100644
--- a/numpy/core/code_generators/ufunc_docstrings.py
+++ b/numpy/core/code_generators/ufunc_docstrings.py
@@ -2537,6 +2537,27 @@ add_newdoc('numpy.core.umath', 'negative',
""")
+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 9887120f5..5613c30ee 100644
--- a/numpy/core/src/umath/funcs.inc.src
+++ b/numpy/core/src/umath/funcs.inc.src
@@ -188,6 +188,14 @@ nc_neg@c@(@ctype@ *a, @ctype@ *r)
}
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)
{
*r = npy_csqrt@c@(*x);
diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src
index 24364afbd..47faaf180 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#
@@ -1185,6 +1191,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))
{
UNARY_LOOP {
@@ -1886,6 +1901,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))
{
/* Sign of nan is nan */
@@ -2189,6 +2213,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))
{
/* Sign of nan is nan */
diff --git a/numpy/core/src/umath/loops.h.src b/numpy/core/src/umath/loops.h.src
index e5c0dc855..d20b776a0 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));
@@ -384,6 +389,9 @@ 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));
NPY_NO_EXPORT void
diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py
index b5d096d47..12ebd5ae9 100644
--- a/numpy/core/tests/test_datetime.py
+++ b/numpy/core/tests/test_datetime.py
@@ -794,6 +794,12 @@ class TestDateTime(TestCase):
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 56b574ae8..75b74f407 100644
--- a/numpy/core/tests/test_half.py
+++ b/numpy/core/tests/test_half.py
@@ -323,6 +323,7 @@ class TestHalf(TestCase):
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 41108ab5f..62882eaa9 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 @@ class TestAbsoluteNegative(TestCase):
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):