diff options
Diffstat (limited to 'numpy')
| -rw-r--r-- | numpy/core/include/numpy/experimental_dtype_api.h | 68 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/array_method.c | 73 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/array_method.h | 53 | ||||
| -rw-r--r-- | numpy/core/src/umath/legacy_array_method.c | 110 | ||||
| -rw-r--r-- | numpy/core/src/umath/reduction.c | 89 | ||||
| -rw-r--r-- | numpy/core/src/umath/ufunc_object.h | 3 | ||||
| -rw-r--r-- | numpy/core/src/umath/wrapping_array_method.c | 7 |
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 = ©_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); } |
