summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/include/numpy/experimental_dtype_api.h68
-rw-r--r--numpy/core/src/multiarray/array_method.c73
-rw-r--r--numpy/core/src/multiarray/array_method.h53
-rw-r--r--numpy/core/src/umath/legacy_array_method.c110
-rw-r--r--numpy/core/src/umath/reduction.c89
-rw-r--r--numpy/core/src/umath/ufunc_object.h3
-rw-r--r--numpy/core/src/umath/wrapping_array_method.c7
7 files changed, 218 insertions, 185 deletions
diff --git a/numpy/core/include/numpy/experimental_dtype_api.h b/numpy/core/include/numpy/experimental_dtype_api.h
index 1664c1a9a..d4f545b14 100644
--- a/numpy/core/include/numpy/experimental_dtype_api.h
+++ b/numpy/core/include/numpy/experimental_dtype_api.h
@@ -182,16 +182,21 @@ typedef struct {
*/
typedef enum {
/* Flag for whether the GIL is required */
- NPY_METH_REQUIRES_PYAPI = 1 << 1,
+ NPY_METH_REQUIRES_PYAPI = 1 << 0,
/*
* Some functions cannot set floating point error flags, this flag
* gives us the option (not requirement) to skip floating point error
* setup/check. No function should set error flags and ignore them
* since it would interfere with chaining operations (e.g. casting).
*/
- NPY_METH_NO_FLOATINGPOINT_ERRORS = 1 << 2,
+ NPY_METH_NO_FLOATINGPOINT_ERRORS = 1 << 1,
/* Whether the method supports unaligned access (not runtime) */
- NPY_METH_SUPPORTS_UNALIGNED = 1 << 3,
+ NPY_METH_SUPPORTS_UNALIGNED = 1 << 2,
+ /*
+ * Used for reductions to allow reordering the operation. At this point
+ * assume that if set, it also applies to normal operations though!
+ */
+ NPY_METH_IS_REORDERABLE = 1 << 3,
/* All flags which can change at runtime */
NPY_METH_RUNTIME_FLAGS = (
@@ -200,6 +205,7 @@ typedef enum {
} NPY_ARRAYMETHOD_FLAGS;
+
/*
* The main object for creating a new ArrayMethod. We use the typical `slots`
* mechanism used by the Python limited API (see below for the slot defs).
@@ -321,46 +327,28 @@ typedef int (PyArrayMethod_StridedLoop)(PyArrayMethod_Context *context,
NpyAuxData *transferdata);
-/*
- * 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_reduction_initial 4
-
-typedef enum {
- /* 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_REDUCTION_FLAGS;
-
-/*
- * 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
- * for `np.sum()` while the empty sum returns 0).
- * The `NPY_METH_IS_REORDERABLE` flag should be set if the operation is
- * reorderable.
- *
- * NOTE: `item` can be `NULL` when a user passed a custom initial value, in
- * this case only the `reorderable` flag is valid.
+/**
+ * Query an ArrayMethod for the initial value for use in reduction.
+ *
+ * @param context The arraymethod context, mainly to access the descriptors.
+ * @param initial Pointer to initial data to be filled (if possible)
+ * @param reduction_is_empty Whether the reduction is empty, when it is the
+ * default value is required, otherwise an identity value to start the
+ * the reduction. These might differ, examples:
+ * - `0.0` as default for `sum([])`. But `-0.0` would be the correct
+ * identity as it preserves the sign for `sum([-0.0])`.
+ * - We use no identity for object, but `0` and `1` for sum and prod.
+ * - `-inf` or `INT_MIN` for `max` is an identity, but at least `INT_MIN`
+ * not a good *default* when there are no items.
+ *
+ * @returns -1, 0, or 1 indicating error, no initial value, and initial being
+ * successfully filled. Errors must not be given where 0 is correct, NumPy
+ * may call this even when not strictly necessary.
*/
+ #define NPY_METH_get_reduction_initial 4
typedef int (get_reduction_intial_function)(
PyArrayMethod_Context *context, char *initial,
- NPY_ARRAYMETHOD_REDUCTION_FLAGS *flags);
+ npy_bool reduction_is_empty);
/*
diff --git a/numpy/core/src/multiarray/array_method.c b/numpy/core/src/multiarray/array_method.c
index 08e0e5f6d..eeebf2d9b 100644
--- a/numpy/core/src/multiarray/array_method.c
+++ b/numpy/core/src/multiarray/array_method.c
@@ -166,77 +166,6 @@ npy_default_get_strided_loop(
}
-/* TODO: Declared in `ufunc_object.c`, should be included more directly */
-NPY_NO_EXPORT PyObject *
-PyUFunc_GetIdentity(PyUFuncObject *ufunc, npy_bool *reorderable);
-
-/*
- * The default `get_reduction_initial` attempts to look up the identity
- * from the calling ufunc.
- */
-static int
-default_get_reduction_initial(PyArrayMethod_Context *context,
- char *initial, NPY_ARRAYMETHOD_REDUCTION_FLAGS *flags)
-{
- *flags = 0;
-
- PyUFuncObject *ufunc = (PyUFuncObject *)context->caller;
- if (!PyObject_TypeCheck(ufunc, &PyUFunc_Type)) {
- /*
- * Could also just report no identity/reorderable, but NumPy would
- * never get here and it is unclear that anyone else will either.
- */
- PyErr_SetString(PyExc_NotImplementedError,
- "default `get_reduction_initial` requires a ufunc context; "
- "Please contact the NumPy developers if you have questions.");
- return -1;
- }
-
- npy_bool reorderable;
- PyObject *identity_obj = PyUFunc_GetIdentity(ufunc, &reorderable);
-
- if (identity_obj == NULL) {
- return -1;
- }
- if (reorderable) {
- *flags |= NPY_METH_IS_REORDERABLE;
- }
-
- 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.
- */
- Py_DECREF(identity_obj);
- return 0;
- }
- if (PyTypeNum_ISUNSIGNED(context->descriptors[2]->type_num)
- && PyLong_CheckExact(identity_obj)) {
- /*
- * This is a bit of a hack until we have truly loop specific
- * identities. Python -1 cannot be cast to unsigned so convert
- * it to a NumPy scalar, but we use -1 for bitwise functions to
- * signal all 1s.
- * (A builtin identity would not overflow here, although we may
- * unnecessary convert 0 and 1.)
- */
- Py_SETREF(identity_obj, PyObject_CallFunctionObjArgs(
- (PyObject *)&PyLongArrType_Type, identity_obj, NULL));
- if (identity_obj == NULL) {
- return -1;
- }
- }
- /* Report the default value and identity unless object dtype */
- *flags |= NPY_METH_INITIAL_IS_DEFAULT;
- if (context->descriptors[0]->type_num != NPY_OBJECT) {
- *flags |= NPY_METH_INITIAL_IS_IDENTITY;
- }
- int res = PyArray_Pack(context->descriptors[0], initial, identity_obj);
- Py_DECREF(identity_obj);
- return res;
-}
-
-
/**
* Validate that the input is usable to create a new ArrayMethod.
*
@@ -322,7 +251,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_reduction_initial = &default_get_reduction_initial;
+ meth->get_reduction_initial = NULL; /* no initial/identity by default */
/* Fill in the slots passed by the user */
/*
diff --git a/numpy/core/src/multiarray/array_method.h b/numpy/core/src/multiarray/array_method.h
index 493bae5e1..ab27cdc62 100644
--- a/numpy/core/src/multiarray/array_method.h
+++ b/numpy/core/src/multiarray/array_method.h
@@ -13,21 +13,21 @@ extern "C" {
typedef enum {
/* Flag for whether the GIL is required */
- NPY_METH_REQUIRES_PYAPI = 1 << 1,
+ NPY_METH_REQUIRES_PYAPI = 1 << 0,
/*
* Some functions cannot set floating point error flags, this flag
* gives us the option (not requirement) to skip floating point error
* setup/check. No function should set error flags and ignore them
* since it would interfere with chaining operations (e.g. casting).
*/
+ NPY_METH_NO_FLOATINGPOINT_ERRORS = 1 << 1,
+ /* Whether the method supports unaligned access (not runtime) */
+ NPY_METH_SUPPORTS_UNALIGNED = 1 << 2,
/*
- * TODO: Change this into a positive flag? That would make "combing"
- * multiple methods easier. OTOH, if we add more flags, the default
- * would be 0 just like it is here.
+ * Used for reductions to allow reordering the operation. At this point
+ * assume that if set, it also applies to normal operations though!
*/
- NPY_METH_NO_FLOATINGPOINT_ERRORS = 1 << 2,
- /* Whether the method supports unaligned access (not runtime) */
- NPY_METH_SUPPORTS_UNALIGNED = 1 << 3,
+ NPY_METH_IS_REORDERABLE = 1 << 3,
/*
* Private flag for now for *logic* functions. The logical functions
* `logical_or` and `logical_and` can always cast the inputs to booleans
@@ -104,32 +104,27 @@ typedef int (get_loop_function)(
NPY_ARRAYMETHOD_FLAGS *flags);
-typedef enum {
- /* 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_REDUCTION_FLAGS;
-
-/*
- * Query an ArrayMethod for its identity (for use with reductions) and whether
- * its operation is reorderable (commutative). These are not always the same:
- * Matrix multiplication is non-commutative, but does have an identity.
+/**
+ * Query an ArrayMethod for the initial value for use in reduction.
*
- * The function should fill `item` and flag whether this value may be used as
- * a default and/or identity.
- * (Normally, an identity is always a valid default. However, NumPy makes an
- * exception for `object` dtypes to ensure type-safety of the result.)
- * If neither `NPY_METH_ITEM_IS_DEFAULT` or `NPY_METH_ITEM_IS_IDENTITY` is
- * given, the value should be left uninitialized (no cleanup will be done).
+ * @param context The arraymethod context, mainly to access the descriptors.
+ * @param initial Pointer to initial data to be filled (if possible)
+ * @param reduction_is_empty Whether the reduction is empty, when it is the
+ * default value is required, otherwise an identity value to start the
+ * the reduction. These might differ, examples:
+ * - `0.0` as default for `sum([])`. But `-0.0` would be the correct
+ * identity as it preserves the sign for `sum([-0.0])`.
+ * - We use no identity for object, but `0` and `1` for sum and prod.
+ * - `-inf` or `INT_MIN` for `max` is an identity, but at least `INT_MIN`
+ * not a good *default* when there are no items.
*
- * The function must return 0 on success and -1 on error (and clean up `item`).
+ * @returns -1, 0, or 1 indicating error, no initial value, and initial being
+ * successfully filled. Errors must not be given where 0 is correct, NumPy
+ * may call this even when not strictly necessary.
*/
typedef int (get_reduction_intial_function)(
PyArrayMethod_Context *context, char *initial,
- NPY_ARRAYMETHOD_REDUCTION_FLAGS *flags);
+ npy_bool reduction_is_empty);
/*
* The following functions are only used be the wrapping array method defined
@@ -231,6 +226,8 @@ typedef struct PyArrayMethodObject_tag {
PyArray_DTypeMeta **wrapped_dtypes;
translate_given_descrs_func *translate_given_descrs;
translate_loop_descrs_func *translate_loop_descrs;
+ /* Chunk used by the legacy fallback arraymethod mainly */
+ char initial[sizeof(npy_clongdouble)]; /* initial value storage */
} PyArrayMethodObject;
diff --git a/numpy/core/src/umath/legacy_array_method.c b/numpy/core/src/umath/legacy_array_method.c
index a8627d55c..435afd6e6 100644
--- a/numpy/core/src/umath/legacy_array_method.c
+++ b/numpy/core/src/umath/legacy_array_method.c
@@ -13,10 +13,13 @@
#include "convert_datatype.h"
#include "array_method.h"
+#include "array_coercion.h"
#include "dtype_transfer.h"
#include "legacy_array_method.h"
#include "dtypemeta.h"
+#include "ufunc_object.h"
+
typedef struct {
NpyAuxData base;
@@ -233,6 +236,89 @@ get_wrapped_legacy_ufunc_loop(PyArrayMethod_Context *context,
}
+
+/*
+ * We can shave off a bit of time by just caching the initial and this is
+ * trivial for all numeric types. (Wrapped ufuncs never use byte-swapping.)
+ */
+static int
+copy_cached_initial(
+ PyArrayMethod_Context *context, char *initial,
+ npy_bool NPY_UNUSED(reduction_is_empty))
+{
+ memcpy(initial, context->method->initial, sizeof(context->descriptors[0]->elsize));
+ return 0;
+}
+
+
+/*
+ * The default `get_reduction_initial` attempts to look up the identity
+ * from the calling ufunc.
+ */
+static int
+get_initial_from_ufunc(
+ PyArrayMethod_Context *context, char *initial,
+ npy_bool reduction_is_empty)
+{
+ if (context->caller == NULL
+ || !PyObject_TypeCheck(context->caller, &PyUFunc_Type)) {
+ /* Impossible in NumPy 1.24; guard in case it becomes possible. */
+ PyErr_SetString(PyExc_ValueError,
+ "getting initial failed because it can only done for legacy "
+ "ufunc loops when the ufunc is provided.");
+ return -1;
+ }
+ npy_bool reorderable;
+ PyObject *identity_obj = PyUFunc_GetIdentity(
+ (PyUFuncObject *)context->caller, &reorderable);
+ if (identity_obj == NULL) {
+ return -1;
+ }
+ if (identity_obj == Py_None) {
+ /* UFunc has no idenity (should not happen) */
+ Py_DECREF(identity_obj);
+ return 0;
+ }
+ if (PyTypeNum_ISUNSIGNED(context->descriptors[0]->type_num)
+ && PyLong_CheckExact(identity_obj)) {
+ /*
+ * This is a bit of a hack until we have truly loop specific
+ * identities. Python -1 cannot be cast to unsigned so convert
+ * it to a NumPy scalar, but we use -1 for bitwise functions to
+ * signal all 1s.
+ * (A builtin identity would not overflow here, although we may
+ * unnecessary convert 0 and 1.)
+ */
+ Py_SETREF(identity_obj, PyObject_CallFunctionObjArgs(
+ (PyObject *)&PyLongArrType_Type, identity_obj, NULL));
+ if (identity_obj == NULL) {
+ return -1;
+ }
+ }
+ else if (context->descriptors[0]->type_num == NPY_OBJECT
+ && !reduction_is_empty) {
+ /* Allow `sum([object()])` to work, but use 0 when empty */
+ Py_DECREF(identity_obj);
+ return 0;
+ }
+
+ int res = PyArray_Pack(context->descriptors[0], initial, identity_obj);
+ Py_DECREF(identity_obj);
+ if (res < 0) {
+ return -1;
+ }
+
+ if (PyTypeNum_ISNUMBER(context->descriptors[0]->type_num)) {
+ /* From now on, simply use the cached version */
+ memcpy(context->method->initial, initial, context->descriptors[0]->elsize);
+ context->method->get_reduction_initial = &copy_cached_initial;
+ }
+
+ /* Reduction can use the initial value */
+ return 1;
+}
+
+
/*
* Get the unbound ArrayMethod which wraps the instances of the ufunc.
* Note that this function stores the result on the ufunc and then only
@@ -272,6 +358,27 @@ PyArray_NewLegacyWrappingArrayMethod(PyUFuncObject *ufunc,
flags = _NPY_METH_FORCE_CAST_INPUTS;
}
+ get_reduction_intial_function *get_reduction_intial = NULL;
+ if (ufunc->nin == 2 && ufunc->nout == 1) {
+ npy_bool reorderable = NPY_FALSE;
+ PyObject *identity_obj = PyUFunc_GetIdentity(ufunc, &reorderable);
+ if (identity_obj == NULL) {
+ return NULL;
+ }
+ /*
+ * TODO: For object, "reorderable" is needed(?), because otherwise
+ * we disable multi-axis reductions `arr.sum(0, 1)`. But for
+ * `arr = array([["a", "b"], ["c", "d"]], dtype="object")`
+ * it isn't actually reorderable (order changes result).
+ */
+ if (reorderable) {
+ flags |= NPY_METH_IS_REORDERABLE;
+ }
+ if (identity_obj != Py_None) {
+ /* NOTE: We defer, just in case it fails in weird cases: */
+ get_reduction_intial = &get_initial_from_ufunc;
+ }
+ }
for (int i = 0; i < ufunc->nin+ufunc->nout; i++) {
if (signature[i]->singleton->flags & (
NPY_ITEM_REFCOUNT | NPY_ITEM_IS_POINTER | NPY_NEEDS_PYAPI)) {
@@ -282,9 +389,10 @@ PyArray_NewLegacyWrappingArrayMethod(PyUFuncObject *ufunc,
}
}
- PyType_Slot slots[3] = {
+ PyType_Slot slots[4] = {
{NPY_METH_get_loop, &get_wrapped_legacy_ufunc_loop},
{NPY_METH_resolve_descriptors, &simple_legacy_resolve_descriptors},
+ {NPY_METH_get_reduction_initial, get_reduction_intial},
{0, NULL},
};
if (any_output_flexible) {
diff --git a/numpy/core/src/umath/reduction.c b/numpy/core/src/umath/reduction.c
index 5e7b8224e..f5fad83dc 100644
--- a/numpy/core/src/umath/reduction.c
+++ b/numpy/core/src/umath/reduction.c
@@ -19,6 +19,7 @@
#include "npy_pycompat.h"
#include "array_assign.h"
+#include "array_coercion.h"
#include "ctors.h"
#include "numpy/ufuncobject.h"
@@ -197,30 +198,11 @@ PyUFunc_ReduceWrapper(PyArrayMethod_Context *context,
op_dtypes[0] = context->descriptors[0];
op_dtypes[1] = context->descriptors[1];
- /*
- * 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_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_REDUCTION_FLAGS reduction_flags = 0;
+ /* Buffer to use when we need an initial value */
char *initial_buf = NULL;
- if (initial == NULL) {
- /* Always init buffer (only necessary if it holds references) */
- initial_buf = PyMem_Calloc(1, op_dtypes[0]->elsize);
- if (initial_buf == NULL) {
- PyErr_NoMemory();
- goto fail;
- }
- }
- if (context->method->get_reduction_initial(
- context, initial_buf, &reduction_flags) < 0) {
- goto fail;
- }
+
/* More than one axis means multiple orders are possible */
- if (!(reduction_flags & NPY_METH_IS_REORDERABLE)
+ if (!(context->method->flags & NPY_METH_IS_REORDERABLE)
&& count_axes(PyArray_NDIM(operand), axis_flags) > 1) {
PyErr_Format(PyExc_ValueError,
"reduction operation '%s' is not reorderable, "
@@ -236,7 +218,7 @@ PyUFunc_ReduceWrapper(PyArrayMethod_Context *context,
NPY_ITER_REFS_OK |
NPY_ITER_DELAY_BUFALLOC |
NPY_ITER_COPY_IF_OVERLAP;
- if (!(reduction_flags & NPY_METH_IS_REORDERABLE)) {
+ if (!(context->method->flags & NPY_METH_IS_REORDERABLE)) {
it_flags |= NPY_ITER_DONT_NEGATE_STRIDES;
}
op_flags[0] = NPY_ITER_READWRITE |
@@ -306,10 +288,48 @@ PyUFunc_ReduceWrapper(PyArrayMethod_Context *context,
goto fail;
}
+ npy_bool empty_iteration = NpyIter_GetIterSize(iter) == 0;
result = NpyIter_GetOperandArray(iter)[0];
- npy_bool empty_reduce = NpyIter_GetIterSize(iter) == 0;
- if (empty_reduce) {
- //empty_reduce = PyArray_SIZE(result) != 0;
+
+ /*
+ * Get the initial value (if it may exists). If the iteration is empty
+ * then we assume the reduction is (in the other case, we do not need
+ * the initial value anyway).
+ */
+ if ((initial == NULL && context->method->get_reduction_initial == NULL)
+ || initial == Py_None) {
+ /* There is no initial value, or initial value was explicitly unset */
+ }
+ else {
+ /* Not all functions will need initialization, but init always: */
+ initial_buf = PyMem_Calloc(1, op_dtypes[0]->elsize);
+ if (initial_buf == NULL) {
+ PyErr_NoMemory();
+ goto fail;
+ }
+ if (initial != NULL) {
+ /* must use user provided initial value */
+ if (PyArray_Pack(op_dtypes[0], initial_buf, initial) < 0) {
+ goto fail;
+ }
+ }
+ else {
+ /*
+ * Fetch initial from ArrayMethod, we pretend the reduction is
+ * empty when the iteration is. This may be wrong, but when it is,
+ * we will not need the identity as the result is also empty.
+ */
+ int res = context->method->get_reduction_initial(
+ context, initial_buf, empty_iteration);
+ if (res < 0) {
+ goto fail;
+ }
+ if (!res) {
+ /* We have no initial value available, free buffer to indicate */
+ PyMem_FREE(initial_buf);
+ initial_buf = NULL;
+ }
+ }
}
PyArrayMethod_StridedLoop *strided_loop;
@@ -326,20 +346,7 @@ PyUFunc_ReduceWrapper(PyArrayMethod_Context *context,
* Initialize the result to the reduction unit if possible,
* otherwise copy the initial values and get a view to the rest.
*/
- if (initial != NULL && initial != Py_None) {
- /*
- * User provided an `initial` value and it is not `None`.
- * NOTE: It may make sense to accept array-valued `initial`,
- * this would subtly (but rarely) change the coercion of
- * `initial`. But it would be perfectly fine otherwise.
- */
- if (PyArray_FillWithScalar(result, initial) < 0) {
- goto fail;
- }
- }
- 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))) {
+ if (initial_buf != NULL) {
/* Loop provided an identity or default value, assign to result. */
int ret = raw_array_assign_scalar(
PyArray_NDIM(result), PyArray_DIMS(result),
@@ -395,7 +402,7 @@ PyUFunc_ReduceWrapper(PyArrayMethod_Context *context,
}
}
- if (!empty_reduce) {
+ if (!empty_iteration) {
NpyIter_IterNextFunc *iternext;
char **dataptr;
npy_intp *strideptr;
diff --git a/numpy/core/src/umath/ufunc_object.h b/numpy/core/src/umath/ufunc_object.h
index 32af6c58e..ea18b7246 100644
--- a/numpy/core/src/umath/ufunc_object.h
+++ b/numpy/core/src/umath/ufunc_object.h
@@ -12,6 +12,9 @@ ufunc_seterr(PyObject *NPY_UNUSED(dummy), PyObject *args);
NPY_NO_EXPORT const char*
ufunc_get_name_cstr(PyUFuncObject *ufunc);
+NPY_NO_EXPORT PyObject *
+PyUFunc_GetIdentity(PyUFuncObject *ufunc, npy_bool *reorderable);
+
/* strings from umathmodule.c that are interned on umath import */
NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_array_ufunc;
NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_array_prepare;
diff --git a/numpy/core/src/umath/wrapping_array_method.c b/numpy/core/src/umath/wrapping_array_method.c
index 688f71fde..2a8ae14bf 100644
--- a/numpy/core/src/umath/wrapping_array_method.c
+++ b/numpy/core/src/umath/wrapping_array_method.c
@@ -184,8 +184,9 @@ wrapping_method_get_loop(
* We assume again that translating the descriptors is quick.
*/
static int
-wrapping_method_get_identity_function(PyArrayMethod_Context *context,
- char *item, NPY_ARRAYMETHOD_REDUCTION_FLAGS *flags)
+wrapping_method_get_identity_function(
+ PyArrayMethod_Context *context, char *item,
+ npy_bool reduction_is_empty)
{
/* Copy the context, and replace descriptors: */
PyArrayMethod_Context orig_context = *context;
@@ -201,7 +202,7 @@ wrapping_method_get_identity_function(PyArrayMethod_Context *context,
return -1;
}
int res = context->method->wrapped_meth->get_reduction_initial(
- &orig_context, item, flags);
+ &orig_context, item, reduction_is_empty);
for (int i = 0; i < nin + nout; i++) {
Py_DECREF(orig_descrs);
}