summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
authorSebastian Berg <sebastianb@nvidia.com>2022-11-03 10:44:17 +0100
committerSebastian Berg <sebastianb@nvidia.com>2023-01-20 15:28:48 +0100
commit07a0d9c8e80dde5676183bf817ebf55889d5cfec (patch)
treec2f2e6cdd3bd67bda461df27011fe6d7d01006ab /numpy
parent336bb64f2aedc8eaf3a52975b99e14958068618d (diff)
downloadnumpy-07a0d9c8e80dde5676183bf817ebf55889d5cfec.tar.gz
MAINT: Rename arraymethod reduction identity getter
Need to look into whether to cut out the dynamic discovery of reorderability though.
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/include/numpy/experimental_dtype_api.h36
-rw-r--r--numpy/core/src/multiarray/array_method.c16
-rw-r--r--numpy/core/src/multiarray/array_method.h21
-rw-r--r--numpy/core/src/umath/reduction.c44
-rw-r--r--numpy/core/src/umath/wrapping_array_method.c9
5 files changed, 69 insertions, 57 deletions
diff --git a/numpy/core/include/numpy/experimental_dtype_api.h b/numpy/core/include/numpy/experimental_dtype_api.h
index 05fb1d936..1664c1a9a 100644
--- a/numpy/core/include/numpy/experimental_dtype_api.h
+++ b/numpy/core/include/numpy/experimental_dtype_api.h
@@ -322,24 +322,32 @@ typedef int (PyArrayMethod_StridedLoop)(PyArrayMethod_Context *context,
/*
- * For reductions, NumPy sometimes requires an identity or default value.
- * The typical way of relying on a single "default" for ufuncs does not always
- * work however. This function allows customizing the identity value as well
- * as whether the operation is "reorderable".
+ * For reductions, NumPy sometimes may use an identity or default value
+ * (which we group as the "initial" value; a user provided `initial=` is
+ * used as both).
+ * The function is after the reduction identity, but generalizes it.
+ * It further is used to indicate whether the reduction can be reordered
+ * for optimization.
+ * The value may be used for reductions which are empty, non-empty, or both
+ * to allow maximum flexibility. A typical identity allows both.
+ * Object dtype sum has only a default 0, but no identity which allows things
+ * like `np.array(["a", "b"], dtype=object).sum()` to work.
+ * The opposite should not really happen, but allows `np.min([])` to error,
+ * when `-inf` is a valid identity (for optimization/easier processing).
*/
-#define NPY_METH_get_identity 7
+#define NPY_METH_get_reduction_initial 4
typedef enum {
- /* The value can be used as a default for empty reductions */
- NPY_METH_ITEM_IS_DEFAULT = 1 << 0,
- /* The value represents the identity value */
- NPY_METH_ITEM_IS_IDENTITY = 1 << 1,
+ /* The "identity" is used as result for empty reductions */
+ NPY_METH_INITIAL_IS_DEFAULT = 1 << 0,
+ /* The "identity" is used for non-empty reductions as initial value */
+ NPY_METH_INITIAL_IS_IDENTITY = 1 << 1,
/* The operation is fully reorderable (iteration order may be optimized) */
NPY_METH_IS_REORDERABLE = 1 << 2,
-} NPY_ARRAYMETHOD_IDENTITY_FLAGS;
+} NPY_ARRAYMETHOD_REDUCTION_FLAGS;
/*
- * If an identity exists, should set the `NPY_METH_ITEM_IS_IDENTITY`, normally
+ * If an identity exists, should set the `NPY_METH_INITIAL_IS_IDENTITY`, normally
* the `NPY_METH_ITEM_IS_DEFAULT` should also be set, but it is distinct.
* By default NumPy provides a "default" for `object` dtype, but does not use
* it as an identity (this is e.g. to allows reducing even Python strings
@@ -350,9 +358,9 @@ typedef enum {
* NOTE: `item` can be `NULL` when a user passed a custom initial value, in
* this case only the `reorderable` flag is valid.
*/
-typedef int (get_identity_function)(
- PyArrayMethod_Context *context, char *item,
- NPY_ARRAYMETHOD_IDENTITY_FLAGS *flags);
+typedef int (get_reduction_intial_function)(
+ PyArrayMethod_Context *context, char *initial,
+ NPY_ARRAYMETHOD_REDUCTION_FLAGS *flags);
/*
diff --git a/numpy/core/src/multiarray/array_method.c b/numpy/core/src/multiarray/array_method.c
index e9b1c3e83..02199280a 100644
--- a/numpy/core/src/multiarray/array_method.c
+++ b/numpy/core/src/multiarray/array_method.c
@@ -176,7 +176,7 @@ PyUFunc_GetIdentity(PyUFuncObject *ufunc, npy_bool *reorderable);
*/
static int
default_get_identity_function(PyArrayMethod_Context *context,
- char *item, NPY_ARRAYMETHOD_IDENTITY_FLAGS *flags)
+ char *initial, NPY_ARRAYMETHOD_REDUCTION_FLAGS *flags)
{
*flags = 0;
@@ -202,7 +202,7 @@ default_get_identity_function(PyArrayMethod_Context *context,
*flags |= NPY_METH_IS_REORDERABLE;
}
- if (item == NULL || identity_obj == Py_None) {
+ if (initial == NULL || identity_obj == Py_None) {
/*
* Only reorderable flag was requested (user provided initial value)
* or there is no identity/default value to report.
@@ -212,11 +212,11 @@ default_get_identity_function(PyArrayMethod_Context *context,
}
/* Report the default value and identity unless object dtype */
- *flags |= NPY_METH_ITEM_IS_DEFAULT;
+ *flags |= NPY_METH_INITIAL_IS_DEFAULT;
if (context->descriptors[0]->type_num != NPY_OBJECT) {
- *flags |= NPY_METH_ITEM_IS_IDENTITY;
+ *flags |= NPY_METH_INITIAL_IS_IDENTITY;
}
- int res = PyArray_Pack(context->descriptors[0], item, identity_obj);
+ int res = PyArray_Pack(context->descriptors[0], initial, identity_obj);
Py_DECREF(identity_obj);
return res;
}
@@ -307,7 +307,7 @@ fill_arraymethod_from_slots(
/* Set the defaults */
meth->get_strided_loop = &npy_default_get_strided_loop;
meth->resolve_descriptors = &default_resolve_descriptors;
- meth->get_identity = &default_get_identity_function;
+ meth->get_reduction_initial = &default_get_identity_function;
/* Fill in the slots passed by the user */
/*
@@ -344,8 +344,8 @@ fill_arraymethod_from_slots(
case NPY_METH_unaligned_contiguous_loop:
meth->unaligned_contiguous_loop = slot->pfunc;
continue;
- case NPY_METH_get_identity:
- meth->get_identity = slot->pfunc;
+ case NPY_METH_get_reduction_initial:
+ meth->get_reduction_initial = slot->pfunc;
continue;
default:
break;
diff --git a/numpy/core/src/multiarray/array_method.h b/numpy/core/src/multiarray/array_method.h
index 0715bcd39..493bae5e1 100644
--- a/numpy/core/src/multiarray/array_method.h
+++ b/numpy/core/src/multiarray/array_method.h
@@ -105,13 +105,13 @@ typedef int (get_loop_function)(
typedef enum {
- /* The value can be used as a default for empty reductions */
- NPY_METH_ITEM_IS_DEFAULT = 1 << 0,
- /* The value represents the identity value */
- NPY_METH_ITEM_IS_IDENTITY = 1 << 1,
+ /* The "identity" is used as result for empty reductions */
+ NPY_METH_INITIAL_IS_DEFAULT = 1 << 0,
+ /* The "identity" is used for non-empty reductions as initial value */
+ NPY_METH_INITIAL_IS_IDENTITY = 1 << 1,
/* The operation is fully reorderable (iteration order may be optimized) */
NPY_METH_IS_REORDERABLE = 1 << 2,
-} NPY_ARRAYMETHOD_IDENTITY_FLAGS;
+} NPY_ARRAYMETHOD_REDUCTION_FLAGS;
/*
* Query an ArrayMethod for its identity (for use with reductions) and whether
@@ -127,10 +127,9 @@ typedef enum {
*
* The function must return 0 on success and -1 on error (and clean up `item`).
*/
-typedef int (get_identity_function)(
- PyArrayMethod_Context *context, char *item,
- NPY_ARRAYMETHOD_IDENTITY_FLAGS *flags);
-
+typedef int (get_reduction_intial_function)(
+ PyArrayMethod_Context *context, char *initial,
+ NPY_ARRAYMETHOD_REDUCTION_FLAGS *flags);
/*
* The following functions are only used be the wrapping array method defined
@@ -221,7 +220,7 @@ typedef struct PyArrayMethodObject_tag {
NPY_ARRAYMETHOD_FLAGS flags;
resolve_descriptors_function *resolve_descriptors;
get_loop_function *get_strided_loop;
- get_identity_function *get_identity;
+ get_reduction_intial_function *get_reduction_initial;
/* Typical loop functions (contiguous ones are used in current casts) */
PyArrayMethod_StridedLoop *strided_loop;
PyArrayMethod_StridedLoop *contiguous_loop;
@@ -263,7 +262,7 @@ extern NPY_NO_EXPORT PyTypeObject PyBoundArrayMethod_Type;
#define NPY_METH_resolve_descriptors 1
#define NPY_METH_get_loop 2
#define NPY_METH_strided_loop 3
-#define NPY_METH_get_identity 4
+#define NPY_METH_get_reduction_initial 4
#define NPY_METH_contiguous_loop 5
#define NPY_METH_unaligned_strided_loop 6
#define NPY_METH_unaligned_contiguous_loop 7
diff --git a/numpy/core/src/umath/reduction.c b/numpy/core/src/umath/reduction.c
index fa37bdef0..5e7b8224e 100644
--- a/numpy/core/src/umath/reduction.c
+++ b/numpy/core/src/umath/reduction.c
@@ -6,6 +6,7 @@
*
* See LICENSE.txt for the license.
*/
+#include "array_method.h"
#define NPY_NO_DEPRECATED_API NPY_API_VERSION
#define _MULTIARRAYMODULE
#define _UMATHMODULE
@@ -199,27 +200,27 @@ PyUFunc_ReduceWrapper(PyArrayMethod_Context *context,
/*
* Fill in default or identity value and ask if this is reorderable.
* Note that if the initial value was provided, we pass `initial_buf=NULL`
- * to the `get_identity` function to indicate that we only require the
- * reorderable flag.
+ * to the `get_reduction_initial` function to indicate that we only
+ * require the reorderable flag.
* If a `result` was passed in, it is possible that the result has a dtype
* differing to the operation one.
*/
- NPY_ARRAYMETHOD_IDENTITY_FLAGS identity_flags = 0;
- char *identity_buf = NULL;
+ NPY_ARRAYMETHOD_REDUCTION_FLAGS reduction_flags = 0;
+ char *initial_buf = NULL;
if (initial == NULL) {
/* Always init buffer (only necessary if it holds references) */
- identity_buf = PyMem_Calloc(1, op_dtypes[0]->elsize);
- if (identity_buf == NULL) {
+ initial_buf = PyMem_Calloc(1, op_dtypes[0]->elsize);
+ if (initial_buf == NULL) {
PyErr_NoMemory();
goto fail;
}
}
- if (context->method->get_identity(
- context, identity_buf, &identity_flags) < 0) {
+ if (context->method->get_reduction_initial(
+ context, initial_buf, &reduction_flags) < 0) {
goto fail;
}
/* More than one axis means multiple orders are possible */
- if (!(identity_flags & NPY_METH_IS_REORDERABLE)
+ if (!(reduction_flags & NPY_METH_IS_REORDERABLE)
&& count_axes(PyArray_NDIM(operand), axis_flags) > 1) {
PyErr_Format(PyExc_ValueError,
"reduction operation '%s' is not reorderable, "
@@ -235,7 +236,7 @@ PyUFunc_ReduceWrapper(PyArrayMethod_Context *context,
NPY_ITER_REFS_OK |
NPY_ITER_DELAY_BUFALLOC |
NPY_ITER_COPY_IF_OVERLAP;
- if (!(identity_flags & NPY_METH_IS_REORDERABLE)) {
+ if (!(reduction_flags & NPY_METH_IS_REORDERABLE)) {
it_flags |= NPY_ITER_DONT_NEGATE_STRIDES;
}
op_flags[0] = NPY_ITER_READWRITE |
@@ -307,6 +308,9 @@ PyUFunc_ReduceWrapper(PyArrayMethod_Context *context,
result = NpyIter_GetOperandArray(iter)[0];
npy_bool empty_reduce = NpyIter_GetIterSize(iter) == 0;
+ if (empty_reduce) {
+ //empty_reduce = PyArray_SIZE(result) != 0;
+ }
PyArrayMethod_StridedLoop *strided_loop;
NPY_ARRAYMETHOD_FLAGS flags = 0;
@@ -333,15 +337,15 @@ PyUFunc_ReduceWrapper(PyArrayMethod_Context *context,
goto fail;
}
}
- else if (identity_buf != NULL && ( /* cannot fill for `initial=None` */
- identity_flags & NPY_METH_ITEM_IS_IDENTITY ||
- (empty_reduce && identity_flags & NPY_METH_ITEM_IS_DEFAULT))) {
+ else if (initial_buf != NULL && ( /* cannot fill for `initial=None` */
+ (reduction_flags & NPY_METH_INITIAL_IS_IDENTITY)
+ || (empty_reduce && reduction_flags & NPY_METH_INITIAL_IS_DEFAULT))) {
/* Loop provided an identity or default value, assign to result. */
int ret = raw_array_assign_scalar(
PyArray_NDIM(result), PyArray_DIMS(result),
PyArray_DESCR(result),
PyArray_BYTES(result), PyArray_STRIDES(result),
- op_dtypes[0], identity_buf);
+ op_dtypes[0], initial_buf);
if (ret < 0) {
goto fail;
}
@@ -424,10 +428,10 @@ PyUFunc_ReduceWrapper(PyArrayMethod_Context *context,
}
Py_INCREF(result);
- if (identity_buf != NULL && PyDataType_REFCHK(PyArray_DESCR(result))) {
- PyArray_Item_XDECREF(identity_buf, PyArray_DESCR(result));
+ if (initial_buf != NULL && PyDataType_REFCHK(PyArray_DESCR(result))) {
+ PyArray_Item_XDECREF(initial_buf, PyArray_DESCR(result));
}
- PyMem_FREE(identity_buf);
+ PyMem_FREE(initial_buf);
NPY_AUXDATA_FREE(auxdata);
if (!NpyIter_Deallocate(iter)) {
Py_DECREF(result);
@@ -436,10 +440,10 @@ PyUFunc_ReduceWrapper(PyArrayMethod_Context *context,
return result;
fail:
- if (identity_buf != NULL && PyDataType_REFCHK(op_dtypes[0])) {
- PyArray_Item_XDECREF(identity_buf, op_dtypes[0]);
+ if (initial_buf != NULL && PyDataType_REFCHK(op_dtypes[0])) {
+ PyArray_Item_XDECREF(initial_buf, op_dtypes[0]);
}
- PyMem_FREE(identity_buf);
+ PyMem_FREE(initial_buf);
NPY_AUXDATA_FREE(auxdata);
if (iter != NULL) {
NpyIter_Deallocate(iter);
diff --git a/numpy/core/src/umath/wrapping_array_method.c b/numpy/core/src/umath/wrapping_array_method.c
index 4d0c4caa4..688f71fde 100644
--- a/numpy/core/src/umath/wrapping_array_method.c
+++ b/numpy/core/src/umath/wrapping_array_method.c
@@ -185,7 +185,7 @@ wrapping_method_get_loop(
*/
static int
wrapping_method_get_identity_function(PyArrayMethod_Context *context,
- char *item, NPY_ARRAYMETHOD_IDENTITY_FLAGS *flags)
+ char *item, NPY_ARRAYMETHOD_REDUCTION_FLAGS *flags)
{
/* Copy the context, and replace descriptors: */
PyArrayMethod_Context orig_context = *context;
@@ -200,8 +200,8 @@ wrapping_method_get_identity_function(PyArrayMethod_Context *context,
nin, nout, dtypes, context->descriptors, orig_descrs) < 0) {
return -1;
}
- int res = context->method->wrapped_meth->get_identity(&orig_context,
- item, flags);
+ int res = context->method->wrapped_meth->get_reduction_initial(
+ &orig_context, item, flags);
for (int i = 0; i < nin + nout; i++) {
Py_DECREF(orig_descrs);
}
@@ -275,7 +275,8 @@ PyUFunc_AddWrappingLoop(PyObject *ufunc_obj,
PyType_Slot slots[] = {
{NPY_METH_resolve_descriptors, &wrapping_method_resolve_descriptors},
{NPY_METH_get_loop, &wrapping_method_get_loop},
- {NPY_METH_get_identity, &wrapping_method_get_identity_function},
+ {NPY_METH_get_reduction_initial,
+ &wrapping_method_get_identity_function},
{0, NULL}
};