summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--numpy/core/src/multiarray/item_selection.c308
-rw-r--r--numpy/core/src/multiarray/reduction.c233
-rw-r--r--numpy/core/src/multiarray/reduction.h113
3 files changed, 443 insertions, 211 deletions
diff --git a/numpy/core/src/multiarray/item_selection.c b/numpy/core/src/multiarray/item_selection.c
index 28fc7e7fa..88b81f43d 100644
--- a/numpy/core/src/multiarray/item_selection.c
+++ b/numpy/core/src/multiarray/item_selection.c
@@ -19,6 +19,7 @@
#include "ctors.h"
#include "lowlevel_strided_loops.h"
#include "na_singleton.h"
+#include "reduction.h"
#include "item_selection.h"
@@ -1891,6 +1892,90 @@ count_boolean_trues(int ndim, char *data, npy_intp *ashape, npy_intp *astrides)
return count;
}
+static int
+assign_reduce_unit_zero(PyArrayObject *result, int preservena, void *data)
+{
+ return PyArray_AssignZero(result, NULL, preservena, NULL);
+}
+
+static void
+reduce_count_nonzero_inner_loop(NpyIter *iter,
+ char **dataptr,
+ npy_intp *strideptr,
+ npy_intp *countptr,
+ NpyIter_IterNextFunc *iternext,
+ int needs_api,
+ void *data)
+{
+ PyArray_NonzeroFunc *nonzero = (PyArray_NonzeroFunc *)data;
+ PyArrayObject *arr = NpyIter_GetOperandArray(iter)[1];
+
+ NPY_BEGIN_THREADS_DEF;
+
+ if (!needs_api) {
+ NPY_BEGIN_THREADS;
+ }
+
+ do {
+ char *data0 = dataptr[0], *data1 = dataptr[1];
+ npy_intp stride0 = strideptr[0], stride1 = strideptr[1];
+ npy_intp count = *countptr;
+
+ while (count--) {
+ if (nonzero(data1, arr)) {
+ ++(*(npy_intp *)data0);
+ }
+ data0 += stride0;
+ data1 += stride1;
+ }
+ } while (iternext(iter));
+
+ if (!needs_api) {
+ NPY_END_THREADS;
+ }
+}
+
+static void
+reduce_count_nonzero_masked_inner_loop(NpyIter *iter,
+ char **dataptr,
+ npy_intp *strideptr,
+ npy_intp *countptr,
+ NpyIter_IterNextFunc *iternext,
+ int needs_api,
+ void *data)
+{
+ PyArray_NonzeroFunc *nonzero = (PyArray_NonzeroFunc *)data;
+ PyArrayObject *arr = NpyIter_GetOperandArray(iter)[1];
+
+ NPY_BEGIN_THREADS_DEF;
+
+ if (!needs_api) {
+ NPY_BEGIN_THREADS;
+ }
+
+ do {
+ char *data0 = dataptr[0], *data1 = dataptr[1], *data2 = dataptr[2];
+ npy_intp stride0 = strideptr[0], stride1 = strideptr[1],
+ stride2 = strideptr[2];
+ npy_intp count = *countptr;
+
+ while (count--) {
+ if (NpyMaskValue_IsExposed((npy_mask)*data2) &&
+ nonzero(data1, arr)) {
+ ++(*(npy_intp *)data0);
+ }
+ data0 += stride0;
+ data1 += stride1;
+ data2 += stride2;
+ }
+ } while (iternext(iter));
+
+ if (!needs_api) {
+ NPY_END_THREADS;
+ }
+}
+
+
/*
* A full reduction version of PyArray_CountNonzero, supporting
* an 'out' parameter and doing the count as a reduction along
@@ -1902,15 +1987,8 @@ PyArray_ReduceCountNonzero(PyArrayObject *arr, PyArrayObject *out,
npy_bool *axis_flags, int skipna)
{
PyArray_NonzeroFunc *nonzero;
- int use_maskna;
+ PyArrayObject *result;
PyArray_Descr *dtype;
- PyArrayObject *result = NULL;
-
- /* Iterator parameters */
- NpyIter *iter = NULL;
- PyArrayObject *op[2];
- PyArray_Descr *op_dtypes[2];
- npy_uint32 flags, op_flags[2];
nonzero = PyArray_DESCR(arr)->f->nonzero;
if (nonzero == NULL) {
@@ -1920,217 +1998,25 @@ PyArray_ReduceCountNonzero(PyArrayObject *arr, PyArrayObject *out,
return NULL;
}
- use_maskna = PyArray_HASMASKNA(arr);
-
- /*
- * If 'arr' has an NA mask, but 'out' doesn't, validate that 'arr'
- * contains no NA values so we can ignore the mask entirely.
- */
- if (use_maskna && !skipna && out != NULL && !PyArray_HASMASKNA(out)) {
- if (PyArray_ContainsNA(arr)) {
- PyErr_SetString(PyExc_ValueError,
- "Cannot assign NA value to an array which "
- "does not support NAs");
- return NULL;
- }
- else {
- use_maskna = 0;
- }
- }
-
- /* This reference gets stolen by PyArray_CreateReduceResult */
dtype = PyArray_DescrFromType(NPY_INTP);
if (dtype == NULL) {
return NULL;
}
- /* This either conforms 'out' to the ndim of 'arr', or allocates
- * a new array appropriate for this reduction.
- */
- result = PyArray_CreateReduceResult(arr, out,
- dtype, axis_flags, !skipna && use_maskna,
- "count_nonzero");
- if (result == NULL) {
- return NULL;
- }
- /*
- * Do the reduction on the NA mask before the data. This way
- * we can avoid modifying the outputs which end up masked, obeying
- * the required NA masking semantics.
- */
- if (use_maskna && !skipna) {
- if (PyArray_ReduceMaskNAArray(result, arr) < 0) {
- Py_DECREF(result);
- return NULL;
- }
-
- /* Short circuit any calculation if the result is 0-dim NA */
- if (PyArray_SIZE(result) == 1 &&
- !NpyMaskValue_IsExposed(
- (npy_mask)*PyArray_MASKNA_DATA(result))) {
- goto finish;
- }
- }
-
- /*
- * Initialize the result to all zeros, except for the NAs
- * when skipna is False.
- */
- if (PyArray_AssignZero(result, NULL, !skipna, NULL) < 0) {
- Py_DECREF(result);
- return NULL;
- }
-
- /* Set up the iterator */
- op[0] = result;
- op[1] = arr;
- op_dtypes[0] = PyArray_DescrFromType(NPY_INTP);
- if (op_dtypes[0] == NULL) {
- Py_DECREF(result);
- return NULL;
- }
- op_dtypes[1] = NULL;
-
- flags = NPY_ITER_BUFFERED |
- NPY_ITER_EXTERNAL_LOOP |
- NPY_ITER_DONT_NEGATE_STRIDES |
- NPY_ITER_ZEROSIZE_OK |
- NPY_ITER_REDUCE_OK |
- NPY_ITER_REFS_OK;
- op_flags[0] = NPY_ITER_READWRITE |
- NPY_ITER_ALIGNED |
- NPY_ITER_NO_SUBTYPE;
- op_flags[1] = NPY_ITER_READONLY |
- NPY_ITER_ALIGNED;
-
- /* Add mask-related flags */
- if (use_maskna) {
- if (skipna) {
- /* The output's mask has been set to all exposed already */
- op_flags[0] |= NPY_ITER_IGNORE_MASKNA;
- /* Need the input's mask to determine what to skip */
- op_flags[1] |= NPY_ITER_USE_MASKNA;
- }
- else {
- /* Iterate over the output's mask */
- op_flags[0] |= NPY_ITER_USE_MASKNA;
- /* The input's mask is already incorporated in the output's mask */
- op_flags[1] |= NPY_ITER_IGNORE_MASKNA;
- }
- }
- else {
- /*
- * If 'out' had no mask, and 'arr' did, we checked that 'arr'
- * contains no NA values and can ignore the masks.
- */
- op_flags[0] |= NPY_ITER_IGNORE_MASKNA;
- op_flags[1] |= NPY_ITER_IGNORE_MASKNA;
- }
-
- iter = NpyIter_MultiNew(2, op, flags,
- NPY_KEEPORDER, NPY_UNSAFE_CASTING,
- op_flags,
- op_dtypes);
- if (iter == NULL) {
- Py_DECREF(result);
- Py_DECREF(op_dtypes[0]);
- return NULL;
- }
-
- if (NpyIter_GetIterSize(iter) != 0) {
- int needs_api;
- NpyIter_IterNextFunc *iternext;
- char **dataptr;
- npy_intp *strideptr;
- npy_intp *countptr;
- NPY_BEGIN_THREADS_DEF;
-
- iternext = NpyIter_GetIterNext(iter, NULL);
- if (iternext == NULL) {
- Py_DECREF(result);
- Py_DECREF(op_dtypes[0]);
- NpyIter_Deallocate(iter);
- return NULL;
- }
- dataptr = NpyIter_GetDataPtrArray(iter);
- strideptr = NpyIter_GetInnerStrideArray(iter);
- countptr = NpyIter_GetInnerLoopSizePtr(iter);
-
- needs_api = NpyIter_IterationNeedsAPI(iter) ||
- PyDataType_REFCHK(PyArray_DESCR(arr));
-
- if (!needs_api) {
- NPY_BEGIN_THREADS;
- }
-
- /* Straightforward reduction */
- if (!use_maskna) {
- do {
- char *data0 = dataptr[0];
- char *data1 = dataptr[1];
- npy_intp stride0 = strideptr[0];
- npy_intp stride1 = strideptr[1];
- npy_intp count = *countptr;
-
- while (count--) {
- if (nonzero(data1, arr)) {
- ++(*(npy_intp *)data0);
- }
- data0 += stride0;
- data1 += stride1;
- }
- } while (iternext(iter));
- }
- /* Masked reduction */
- else {
- do {
- char *data0 = dataptr[0];
- char *data1 = dataptr[1];
- char *data2 = dataptr[2];
- npy_intp stride0 = strideptr[0];
- npy_intp stride1 = strideptr[1];
- npy_intp stride2 = strideptr[2];
- npy_intp count = *countptr;
-
- while (count--) {
- if (NpyMaskValue_IsExposed((npy_mask)*data2) &&
- nonzero(data1, arr)) {
- ++(*(npy_intp *)data0);
- }
- data0 += stride0;
- data1 += stride1;
- data2 += stride2;
- }
- } while (iternext(iter));
- }
-
- if (!needs_api) {
- NPY_END_THREADS;
- }
-
- if (needs_api && PyErr_Occurred()) {
- Py_DECREF(result);
- Py_DECREF(op_dtypes[0]);
- NpyIter_Deallocate(iter);
- return NULL;
- }
- }
-
- Py_DECREF(op_dtypes[0]);
- NpyIter_Deallocate(iter);
-
-finish:
- /* Strip out the extra 'one' dimensions in the result */
- if (out == NULL) {
- PyArray_RemoveAxesInPlace(result, axis_flags);
+ result = PyArray_ReduceWrapper(arr, out,
+ PyArray_DESCR(arr), dtype,
+ axis_flags, skipna,
+ &assign_reduce_unit_zero,
+ &reduce_count_nonzero_inner_loop,
+ &reduce_count_nonzero_masked_inner_loop,
+ nonzero, "count_nonzero");
+ Py_DECREF(dtype);
+ if (out == NULL && result != NULL) {
+ return PyArray_Return(result);
}
else {
- Py_DECREF(result);
- result = out;
- Py_INCREF(result);
+ return (PyObject *)result;
}
-
- return PyArray_Return(result);
}
/*NUMPY_API
diff --git a/numpy/core/src/multiarray/reduction.c b/numpy/core/src/multiarray/reduction.c
index 09070cb72..30fb33bed 100644
--- a/numpy/core/src/multiarray/reduction.c
+++ b/numpy/core/src/multiarray/reduction.c
@@ -400,3 +400,236 @@ PyArray_InitializeReduceResult(
return op_view;
}
+
+/*
+ * This function executes all the standard NumPy reduction function
+ * boilerplate code, just calling assign_unit and the appropriate
+ * inner loop function where necessary.
+ *
+ * operand : The array to be reduced.
+ * out : NULL, or the array into which to place the result.
+ * operand_dtype : The dtype the inner loop expects for the operand.
+ * result_dtype : The dtype the inner loop expects for the result.
+ * axis_flags : Flags indicating the reduction axes of 'operand'.
+ * skipna : If true, NAs are skipped instead of propagating.
+ * assign_unit : If NULL, PyArray_InitializeReduceResult is used, otherwise
+ * this function is called to initialize the result to
+ * the reduction's unit.
+ * inner_loop : The inner loop which does the reduction.
+ * masked_inner_loop: The inner loop which does the reduction with a mask.
+ * data : Data which is passed to assign_unit and the inner loop.
+ * funcname : The name of the reduction function, for error messages.
+ */
+NPY_NO_EXPORT PyArrayObject *
+PyArray_ReduceWrapper(PyArrayObject *operand, PyArrayObject *out,
+ PyArray_Descr *operand_dtype,
+ PyArray_Descr *result_dtype,
+ npy_bool *axis_flags, int skipna,
+ PyArray_AssignReduceUnitFunc *assign_unit,
+ PyArray_ReduceInnerLoopFunc *inner_loop,
+ PyArray_ReduceInnerLoopFunc *masked_inner_loop,
+ void *data, const char *funcname)
+{
+ int use_maskna;
+ PyArrayObject *result = NULL, *op_view = NULL;
+
+ /* Iterator parameters */
+ NpyIter *iter = NULL;
+ PyArrayObject *op[2];
+ PyArray_Descr *op_dtypes[2];
+ npy_uint32 flags, op_flags[2];
+
+ use_maskna = PyArray_HASMASKNA(operand);
+
+ /*
+ * If 'operand' has an NA mask, but 'out' doesn't, validate that 'operand'
+ * contains no NA values so we can ignore the mask entirely.
+ */
+ if (use_maskna && !skipna && out != NULL && !PyArray_HASMASKNA(out)) {
+ if (PyArray_ContainsNA(operand)) {
+ PyErr_SetString(PyExc_ValueError,
+ "Cannot assign NA value to an array which "
+ "does not support NAs");
+ goto fail;
+ }
+ else {
+ use_maskna = 0;
+ }
+ }
+
+ /*
+ * This either conforms 'out' to the ndim of 'operand', or allocates
+ * a new array appropriate for this reduction.
+ */
+ Py_INCREF(result_dtype);
+ result = PyArray_CreateReduceResult(operand, out,
+ result_dtype, axis_flags, !skipna && use_maskna,
+ funcname);
+ if (result == NULL) {
+ goto fail;
+ }
+
+ /*
+ * Do the reduction on the NA mask before the data. This way
+ * we can avoid modifying the outputs which end up masked, obeying
+ * the required NA masking semantics.
+ */
+ if (use_maskna && !skipna) {
+ if (PyArray_ReduceMaskNAArray(result, operand) < 0) {
+ goto fail;
+ }
+
+ /* Short circuit any calculation if the result is 0-dim NA */
+ if (PyArray_SIZE(result) == 1 &&
+ !NpyMaskValue_IsExposed(
+ (npy_mask)*PyArray_MASKNA_DATA(result))) {
+ goto finish;
+ }
+ }
+
+ /*
+ * Initialize the result to the reduction unit if possible,
+ * otherwise copy the initial values and get a view to the rest.
+ */
+ if (assign_unit != NULL) {
+ if (assign_unit(result, !skipna, data) < 0) {
+ goto fail;
+ }
+ op_view = operand;
+ Py_INCREF(op_view);
+ }
+ else {
+ op_view = PyArray_InitializeReduceResult(result, operand,
+ axis_flags, skipna, funcname);
+ if (op_view == NULL) {
+ Py_DECREF(result);
+ return NULL;
+ }
+ }
+
+ /* Set up the iterator */
+ op[0] = result;
+ op[1] = op_view;
+ op_dtypes[0] = result_dtype;
+ op_dtypes[1] = operand_dtype;
+
+ flags = NPY_ITER_BUFFERED |
+ NPY_ITER_EXTERNAL_LOOP |
+ NPY_ITER_DONT_NEGATE_STRIDES |
+ NPY_ITER_ZEROSIZE_OK |
+ NPY_ITER_REDUCE_OK |
+ NPY_ITER_REFS_OK;
+ op_flags[0] = NPY_ITER_READWRITE |
+ NPY_ITER_ALIGNED |
+ NPY_ITER_NO_SUBTYPE;
+ op_flags[1] = NPY_ITER_READONLY |
+ NPY_ITER_ALIGNED;
+
+ /* Add mask-related flags */
+ if (use_maskna) {
+ if (skipna) {
+ /* The output's mask has been set to all exposed already */
+ op_flags[0] |= NPY_ITER_IGNORE_MASKNA;
+ /* Need the input's mask to determine what to skip */
+ op_flags[1] |= NPY_ITER_USE_MASKNA;
+ }
+ else {
+ /* Iterate over the output's mask */
+ op_flags[0] |= NPY_ITER_USE_MASKNA;
+ /* The input's mask is already incorporated in the output's mask */
+ op_flags[1] |= NPY_ITER_IGNORE_MASKNA;
+ }
+ }
+ else {
+ /*
+ * If 'out' had no mask, and 'operand' did, we checked that 'operand'
+ * contains no NA values and can ignore the masks.
+ */
+ op_flags[0] |= NPY_ITER_IGNORE_MASKNA;
+ op_flags[1] |= NPY_ITER_IGNORE_MASKNA;
+ }
+
+ iter = NpyIter_MultiNew(2, op, flags,
+ NPY_KEEPORDER, NPY_SAME_KIND_CASTING,
+ op_flags,
+ op_dtypes);
+ if (iter == NULL) {
+ Py_DECREF(result);
+ Py_DECREF(op_dtypes[0]);
+ return NULL;
+ }
+
+ if (NpyIter_GetIterSize(iter) != 0) {
+ NpyIter_IterNextFunc *iternext;
+ char **dataptr;
+ npy_intp *strideptr;
+ npy_intp *countptr;
+ int needs_api;
+
+ iternext = NpyIter_GetIterNext(iter, NULL);
+ if (iternext == NULL) {
+ Py_DECREF(result);
+ Py_DECREF(op_dtypes[0]);
+ NpyIter_Deallocate(iter);
+ return NULL;
+ }
+ dataptr = NpyIter_GetDataPtrArray(iter);
+ strideptr = NpyIter_GetInnerStrideArray(iter);
+ countptr = NpyIter_GetInnerLoopSizePtr(iter);
+
+ needs_api = NpyIter_IterationNeedsAPI(iter);
+
+ /* Straightforward reduction */
+ if (!use_maskna) {
+ if (inner_loop == NULL) {
+ PyErr_Format(PyExc_RuntimeError,
+ "reduction operation %s did not supply an "
+ "unmasked inner loop function", funcname);
+ goto fail;
+ }
+
+ inner_loop(iter, dataptr, strideptr, countptr,
+ iternext, needs_api, data);
+ }
+ /* Masked reduction */
+ else {
+ if (masked_inner_loop == NULL) {
+ PyErr_Format(PyExc_RuntimeError,
+ "reduction operation %s did not supply a "
+ "masked inner loop function", funcname);
+ goto fail;
+ }
+
+ masked_inner_loop(iter, dataptr, strideptr, countptr,
+ iternext, needs_api, data);
+ }
+
+ if (PyErr_Occurred()) {
+ goto fail;
+ }
+ }
+
+ NpyIter_Deallocate(iter);
+
+finish:
+ /* Strip out the extra 'one' dimensions in the result */
+ if (out == NULL) {
+ PyArray_RemoveAxesInPlace(result, axis_flags);
+ }
+ else {
+ Py_DECREF(result);
+ result = out;
+ Py_INCREF(result);
+ }
+
+ return result;
+
+fail:
+ Py_XDECREF(result);
+ Py_XDECREF(op_view);
+ if (iter != NULL) {
+ NpyIter_Deallocate(iter);
+ }
+
+ return NULL;
+}
diff --git a/numpy/core/src/multiarray/reduction.h b/numpy/core/src/multiarray/reduction.h
index f19909dbc..b2334f896 100644
--- a/numpy/core/src/multiarray/reduction.h
+++ b/numpy/core/src/multiarray/reduction.h
@@ -1,4 +1,117 @@
#ifndef _NPY_PRIVATE__REDUCTION_H_
#define _NPY_PRIVATE__REDUCTION_H_
+/*
+ * This is a function for assigning a reduction unit to the result,
+ * before doing the reduction computation. If 'preservena' is True,
+ * any masked NA values in 'result' should not be overwritten. The
+ * value in 'data' is passed through from PyArray_ReduceWrapper.
+ *
+ * This function could, for example, simply be a call like
+ * return PyArray_AssignZero(result, NULL, preservena, NULL);
+ *
+ * It should return -1 on failure, or 0 on success.
+ */
+typedef int (PyArray_AssignReduceUnitFunc)(PyArrayObject *result,
+ int preservena, void *data);
+
+/*
+ * This is a function for the inner reduce loop. Both the unmasked and
+ * masked variants have the same prototype, but should behave differently.
+ *
+ * The needs_api parameter indicates whether it's ok to release the GIL during
+ * the inner loop, such as when the iternext() function never calls
+ * a function which could raise a Python exception.
+ *
+ * The unmasked inner loop gets two data pointers and two strides, and should
+ * look roughly like this:
+ * NPY_BEGIN_THREADS_DEF;
+ * if (!needs_api) {
+ * NPY_BEGIN_THREADS;
+ * }
+ * do {
+ * char *data0 = dataptr[0], *data1 = dataptr[1];
+ * npy_intp stride0 = strideptr[0], stride1 = strideptr[1];
+ * npy_intp count = *countptr;
+ *
+ * while (count--) {
+ * *(result_t *)data0 = my_reduce_op(*(result_t *)data0,
+ * *(operand_t *)data1);
+ * data0 += stride0;
+ * data1 += stride1;
+ * }
+ * } while (iternext(iter));
+ * if (!needs_api) {
+ * NPY_END_THREADS;
+ * }
+ *
+ * The masked inner loop gets three data pointers and three strides, and
+ * should look roughly like this:
+ * NPY_BEGIN_THREADS_DEF;
+ * if (!needs_api) {
+ * NPY_BEGIN_THREADS;
+ * }
+ * do {
+ * char *data0 = dataptr[0], *data1 = dataptr[1], *data2 = dataptr[2];
+ * npy_intp stride0 = strideptr[0], stride1 = strideptr[1],
+ * stride2 = strideptr[2];
+ * npy_intp count = *countptr;
+ *
+ * while (count--) {
+ * if (NpyMaskValue_IsExposed((npy_mask)*data2)) {
+ * *(result_t *)data0 = my_reduce_op(*(result_t *)data0,
+ * *(operand_t *)data1);
+ * }
+ * data0 += stride0;
+ * data1 += stride1;
+ * data2 += stride2;
+ * }
+ * } while (iternext(iter));
+ * if (!needs_api) {
+ * NPY_END_THREADS;
+ * }
+ *
+ * Once the inner loop is finished, PyArray_ReduceWrapper calls
+ * PyErr_Occurred to check if any exception was raised during the
+ * computation.
+ */
+typedef void (PyArray_ReduceInnerLoopFunc)(NpyIter *iter,
+ char **dataptr,
+ npy_intp *strideptr,
+ npy_intp *countptr,
+ NpyIter_IterNextFunc *iternext,
+ int needs_api,
+ void *data);
+
+/*
+ * This function executes all the standard NumPy reduction function
+ * boilerplate code, just calling assign_unit and the appropriate
+ * inner loop function where necessary.
+ *
+ * operand : The array to be reduced.
+ * out : NULL, or the array into which to place the result.
+ * operand_dtype : The dtype the inner loop expects for the operand.
+ * result_dtype : The dtype the inner loop expects for the result.
+ * axis_flags : Flags indicating the reduction axes of 'operand'.
+ * skipna : If true, NAs are skipped instead of propagating.
+ * assign_unit : If NULL, PyArray_InitializeReduceResult is used, otherwise
+ * this function is called to initialize the result to
+ * the reduction's unit.
+ * inner_loop : The inner loop which does the reduction.
+ * masked_inner_loop: The inner loop which does the reduction with a mask.
+ * data : Data which is passed to assign_unit and the inner loop.
+ * needs_api : Should be 1 if the inner loop might call a Python API
+ * function like PyErr_SetString.
+ * funcname : The name of the reduction function, for error messages.
+ */
+NPY_NO_EXPORT PyArrayObject *
+PyArray_ReduceWrapper(PyArrayObject *operand, PyArrayObject *out,
+ PyArray_Descr *operand_dtype,
+ PyArray_Descr *result_dtype,
+ npy_bool *axis_flags, int skipna,
+ PyArray_AssignReduceUnitFunc *assign_unit,
+ PyArray_ReduceInnerLoopFunc *inner_loop,
+ PyArray_ReduceInnerLoopFunc *masked_inner_loop,
+ void *data, const char *funcname);
+
#endif