From a95ade8c494b6eab56ac9c67f772ecac603c2ad9 Mon Sep 17 00:00:00 2001 From: MattHarrigan Date: Tue, 8 Nov 2016 10:46:40 -0500 Subject: [PATCH 1/3] ENH: add identity kwarg to frompyfunc --- numpy/core/src/umath/umathmodule.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/numpy/core/src/umath/umathmodule.c b/numpy/core/src/umath/umathmodule.c index 45accb970787..d77838880beb 100644 --- a/numpy/core/src/umath/umathmodule.c +++ b/numpy/core/src/umath/umathmodule.c @@ -83,8 +83,7 @@ object_ufunc_loop_selector(PyUFuncObject *ufunc, } static PyObject * -ufunc_frompyfunc(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *NPY_UNUSED(kwds)) { - /* Keywords are ignored for now */ +ufunc_frompyfunc(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *kwds) { PyObject *function, *pyname = NULL; int nin, nout, i; @@ -93,8 +92,11 @@ ufunc_frompyfunc(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *NPY_UNUS char *fname, *str; Py_ssize_t fname_len = -1; int offset[2]; + int identity = PyUFunc_None; + static char *kwlist[] = {"func", "nin", "nout", "identity", NULL}; - if (!PyArg_ParseTuple(args, "Oii", &function, &nin, &nout)) { + if (!PyArg_ParseTupleAndKeywords(args, kwds, "Oii|i", kwlist, + &function, &nin, &nout, &identity)) { return NULL; } if (!PyCallable_Check(function)) { @@ -118,7 +120,7 @@ ufunc_frompyfunc(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *NPY_UNUS self->nin = nin; self->nout = nout; self->nargs = nin + nout; - self->identity = PyUFunc_None; + self->identity = identity; self->functions = pyfunc_functions; self->ntypes = 1; From ecca72e069008af6c36c2dcd64201a4ecc141a1a Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Wed, 4 Dec 2019 12:54:18 +0000 Subject: [PATCH 2/3] Update umathmodule.c --- numpy/core/src/umath/umathmodule.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/numpy/core/src/umath/umathmodule.c b/numpy/core/src/umath/umathmodule.c index 9714fcf4e600..3d41d8f813ad 100644 --- a/numpy/core/src/umath/umathmodule.c +++ b/numpy/core/src/umath/umathmodule.c @@ -79,10 +79,10 @@ ufunc_frompyfunc(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *kwds) { Py_ssize_t fname_len = -1; void * ptr, **data; int offset[2]; - int identity = PyUFunc_None; + PyObject *identity = NULL; /* note: not the same semantics as Py_None */ static char *kwlist[] = {"", "nin", "nout", "identity", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "Oii|$i:frompyfunc", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "Oii|$O:frompyfunc", kwlist, &function, &nin, &nout, &identity)) { return NULL; } @@ -150,7 +150,7 @@ ufunc_frompyfunc(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *kwds) { self = (PyUFuncObject *)PyUFunc_FromFuncAndDataAndSignatureAndIdentity( (PyUFuncGenericFunction *)pyfunc_functions, data, - types, /* ntypes */ 1, nin, nout, PyUFunc_IdentityValue, + types, /* ntypes */ 1, nin, nout, identity ? PyUFunc_IdentityValue : PyUFunc_None, str, doc, /* unused */ 0, NULL, identity); if (self == NULL) { From 2e43d9d6638401b3dd8bc40a63b08602271c772c Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Fri, 6 Dec 2019 10:52:43 +0000 Subject: [PATCH 3/3] Add test, docs, and release note for identity --- .../upcoming_changes/8255.new_feature.rst | 5 ++++ numpy/core/_add_newdocs.py | 9 ++++++- numpy/core/tests/test_umath.py | 26 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 doc/release/upcoming_changes/8255.new_feature.rst diff --git a/doc/release/upcoming_changes/8255.new_feature.rst b/doc/release/upcoming_changes/8255.new_feature.rst new file mode 100644 index 000000000000..c0bc21b3ef1e --- /dev/null +++ b/doc/release/upcoming_changes/8255.new_feature.rst @@ -0,0 +1,5 @@ +`numpy.frompyfunc` now accepts an identity argument +--------------------------------------------------- +This allows the :attr:`numpy.ufunc.identity` attribute to be set on the +resulting ufunc, meaning it can be used for empty and multi-dimensional +calls to :meth:`numpy.ufunc.reduce`. diff --git a/numpy/core/_add_newdocs.py b/numpy/core/_add_newdocs.py index 2f12739040bb..8993424fe494 100644 --- a/numpy/core/_add_newdocs.py +++ b/numpy/core/_add_newdocs.py @@ -4230,7 +4230,7 @@ add_newdoc('numpy.core.umath', 'frompyfunc', """ - frompyfunc(func, nin, nout) + frompyfunc(func, nin, nout, *[, identity]) Takes an arbitrary Python function and returns a NumPy ufunc. @@ -4245,6 +4245,13 @@ The number of input arguments. nout : int The number of objects returned by `func`. + identity : object, optional + The value to use for the `~numpy.ufunc.identity` attribute of the resulting + object. If specified, this is equivalent to setting the underlying + C ``identity`` field to ``PyUFunc_IdentityValue``. + If omitted, the identity is set to ``PyUFunc_None``. Note that this is + _not_ equivalent to setting the identity to ``None``, which implies the + operation is reorderable. Returns ------- diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py index e892e81d23c6..cab98b54a0b5 100644 --- a/numpy/core/tests/test_umath.py +++ b/numpy/core/tests/test_umath.py @@ -2876,6 +2876,32 @@ def __new__(subtype, shape): a = simple((3, 4)) assert_equal(a+a, a) + +class TestFrompyfunc(object): + + def test_identity(self): + def mul(a, b): + return a * b + + # with identity=value + mul_ufunc = np.frompyfunc(mul, nin=2, nout=1, identity=1) + assert_equal(mul_ufunc.reduce([2, 3, 4]), 24) + assert_equal(mul_ufunc.reduce(np.ones((2, 2)), axis=(0, 1)), 1) + assert_equal(mul_ufunc.reduce([]), 1) + + # with identity=None (reorderable) + mul_ufunc = np.frompyfunc(mul, nin=2, nout=1, identity=None) + assert_equal(mul_ufunc.reduce([2, 3, 4]), 24) + assert_equal(mul_ufunc.reduce(np.ones((2, 2)), axis=(0, 1)), 1) + assert_raises(ValueError, lambda: mul_ufunc.reduce([])) + + # with no identity (not reorderable) + mul_ufunc = np.frompyfunc(mul, nin=2, nout=1) + assert_equal(mul_ufunc.reduce([2, 3, 4]), 24) + assert_raises(ValueError, lambda: mul_ufunc.reduce(np.ones((2, 2)), axis=(0, 1))) + assert_raises(ValueError, lambda: mul_ufunc.reduce([])) + + def _check_branch_cut(f, x0, dx, re_sign=1, im_sign=-1, sig_zero_ok=False, dtype=complex): """