summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/SConscript4
-rw-r--r--numpy/core/setup.py2
-rw-r--r--numpy/core/src/multiarray/boolean_ops.c.src380
-rw-r--r--numpy/core/src/multiarray/boolean_ops.h9
-rw-r--r--numpy/core/src/multiarray/item_selection.c13
-rw-r--r--numpy/core/src/multiarray/methods.c50
-rw-r--r--numpy/core/src/multiarray/reduction.c59
-rw-r--r--numpy/core/src/multiarray/reduction.h6
-rw-r--r--numpy/core/src/umath/ufunc_object.c3
-rw-r--r--numpy/matrixlib/defmatrix.py27
10 files changed, 508 insertions, 45 deletions
diff --git a/numpy/core/SConscript b/numpy/core/SConscript
index 7ed79fea3..918dc5b46 100644
--- a/numpy/core/SConscript
+++ b/numpy/core/SConscript
@@ -381,6 +381,8 @@ arraytypes_src = env.GenerateFromTemplate(
pjoin('src', 'multiarray', 'arraytypes.c.src'))
nditer_src = env.GenerateFromTemplate(
pjoin('src', 'multiarray', 'nditer_templ.c.src'))
+boolean_ops_src = env.GenerateFromTemplate(
+ pjoin('src', 'multiarray', 'boolean_ops.c.src'))
lowlevel_strided_loops_src = env.GenerateFromTemplate(
pjoin('src', 'multiarray', 'lowlevel_strided_loops.c.src'))
einsum_src = env.GenerateFromTemplate(pjoin('src', 'multiarray', 'einsum.c.src'))
@@ -446,6 +448,7 @@ if ENABLE_SEPARATE_COMPILATION:
pjoin('src', 'multiarray', 'array_assign.c'),
pjoin('src', 'multiarray', 'array_assign_scalar.c'),
pjoin('src', 'multiarray', 'array_assign_array.c'),
+ pjoin('src', 'multiarray', 'boolean_ops.c'),
pjoin('src', 'multiarray', 'datetime.c'),
pjoin('src', 'multiarray', 'datetime_strings.c'),
pjoin('src', 'multiarray', 'datetime_busday.c'),
@@ -482,6 +485,7 @@ if ENABLE_SEPARATE_COMPILATION:
multiarray_src.extend(arraytypes_src)
multiarray_src.extend(scalartypes_src)
multiarray_src.extend(lowlevel_strided_loops_src)
+ multiarray_src.extend(boolean_ops_src)
multiarray_src.extend(nditer_src)
multiarray_src.extend(einsum_src)
if PYTHON_HAS_UNICODE_WIDE:
diff --git a/numpy/core/setup.py b/numpy/core/setup.py
index 4622d16bb..e8f40e806 100644
--- a/numpy/core/setup.py
+++ b/numpy/core/setup.py
@@ -688,6 +688,7 @@ def configuration(parent_package='',top_path=None):
join(local_dir, subpath, 'arraytypes.c.src'),
join(local_dir, subpath, 'nditer_templ.c.src'),
join(local_dir, subpath, 'lowlevel_strided_loops.c.src'),
+ join(local_dir, subpath, 'boolean_ops.c.src'),
join(local_dir, subpath, 'einsum.c.src')]
# numpy.distutils generate .c from .c.src in weird directories, we have
@@ -757,6 +758,7 @@ def configuration(parent_package='',top_path=None):
join('src', 'multiarray', 'array_assign.c'),
join('src', 'multiarray', 'array_assign_scalar.c'),
join('src', 'multiarray', 'array_assign_array.c'),
+ join('src', 'multiarray', 'boolean_ops.c.src'),
join('src', 'multiarray', 'buffer.c'),
join('src', 'multiarray', 'calculation.c'),
join('src', 'multiarray', 'common.c'),
diff --git a/numpy/core/src/multiarray/boolean_ops.c.src b/numpy/core/src/multiarray/boolean_ops.c.src
new file mode 100644
index 000000000..4641f2089
--- /dev/null
+++ b/numpy/core/src/multiarray/boolean_ops.c.src
@@ -0,0 +1,380 @@
+/*
+ * This file implements some boolean methods which have special NA
+ * interactions, including np.any and np.all. Could later gain
+ * np.logical_or and np.logical_and, as well as np.bitwise_or and
+ * np.bitwise_and because they are often used for boolean operations.
+ *
+ * NOTE: These functions assume that the input boolean data is valid,
+ * i.e. that each boolean is either 0 or 1. Any deviation from this
+ * may produce incorrect answers.
+ *
+ * Written by Mark Wiebe (mwwiebe@gmail.com)
+ * Copyright (c) 2011 by Enthought, Inc.
+ *
+ * See LICENSE.txt for the license.
+ */
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+
+#define NPY_NO_DEPRECATED_API
+#define _MULTIARRAYMODULE
+#include <numpy/arrayobject.h>
+
+#include "npy_config.h"
+#include "numpy/npy_3kcompat.h"
+
+#include "reduction.h"
+
+/* Typedef for the reduction inner loop */
+typedef void (reduce_inner_loop)(char **, npy_intp *, npy_intp);
+
+static int
+assign_identity_any(PyArrayObject *result, int preservena, void *data)
+{
+ return PyArray_AssignZero(result, NULL, preservena, NULL);
+}
+
+static void
+any_inner_gen_gen(char **dataptr, npy_intp *strides, npy_intp count)
+{
+ char *data0 = dataptr[0], *data1 = dataptr[1];
+ npy_intp stride0 = strides[0], stride1 = strides[1];
+ npy_intp i;
+
+ for (i = 0; i < count; ++i) {
+ *data0 |= *data1;
+
+ data0 += stride0;
+ data1 += stride1;
+ }
+}
+
+static void
+any_inner_0stride_gen(char **dataptr, npy_intp *strides, npy_intp count)
+{
+ char *data0 = dataptr[0], *data1 = dataptr[1];
+ npy_intp stride1 = strides[1];
+ npy_intp i;
+ char value = *data0;
+
+ for (i = 0; i < count && !value; ++i) {
+ value |= *data1;
+
+ data1 += stride1;
+ }
+
+ *(npy_bool *)data0 = value;
+}
+
+static void
+any_inner_0stride_contig(char **dataptr, npy_intp *strides, npy_intp count)
+{
+ char *data0 = dataptr[0], *data1 = dataptr[1];
+ char value = *data0;
+
+ if (count > 0 && !value && memchr(data1, 1, count) != NULL) {
+ *(npy_bool *)data0 = 1;
+ }
+}
+
+static void
+any_inner_contig_contig(char **dataptr, npy_intp *strides, npy_intp count)
+{
+ char *data0 = dataptr[0], *data1 = dataptr[1];
+ npy_intp i;
+
+ for (i = 0; i < count; ++i) {
+ *data0 |= *data1;
+
+ ++data0;
+ ++data1;
+ }
+}
+
+static int
+reduce_any_loop(NpyIter *iter,
+ char **dataptr,
+ npy_intp *strides,
+ npy_intp *countptr,
+ NpyIter_IterNextFunc *iternext,
+ int needs_api,
+ npy_intp skip_first_count,
+ void *data)
+{
+ npy_intp fixed_strides[2];
+ reduce_inner_loop *inner_loop;
+ NPY_BEGIN_THREADS_DEF;
+
+ NpyIter_GetInnerFixedStrideArray(iter, fixed_strides);
+
+ if (!needs_api) {
+ NPY_BEGIN_THREADS;
+ }
+
+ /* Choose a loop specialized based on the strides */
+ if (fixed_strides[0] == 0) {
+ if (fixed_strides[1] == 1) {
+ inner_loop = &any_inner_0stride_contig;
+ }
+ else {
+ inner_loop = &any_inner_0stride_gen;
+ }
+ }
+ else {
+ if (fixed_strides[0] == 1 && fixed_strides[1] == 1) {
+ inner_loop = &any_inner_contig_contig;
+ }
+ else {
+ inner_loop = &any_inner_gen_gen;
+ }
+ }
+
+ /*
+ * 'skip_first_count' will always be 0 because we are doing a reduction
+ * with an identity.
+ */
+
+ do {
+ inner_loop(dataptr, strides, *countptr);
+ } while (iternext(iter));
+
+ if (!needs_api) {
+ NPY_END_THREADS;
+ }
+
+ return (needs_api && PyErr_Occurred()) ? -1 : 0;
+}
+
+static void
+any_masked_inner_gen_gen_gen(char **dataptr,
+ npy_intp *strides, npy_intp count)
+{
+ char *data0 = dataptr[0], *data1 = dataptr[1], *data2 = dataptr[2];
+ npy_intp stride0 = strides[0], stride1 = strides[1], stride2 = strides[2];
+ npy_intp i;
+
+ for (i = 0; i < count; ++i) {
+ *data0 |= *data1 & *data2;
+
+ data0 += stride0;
+ data1 += stride1;
+ data2 += stride2;
+ }
+}
+
+static void
+any_masked_inner_0stride_gen_gen(char **dataptr,
+ npy_intp *strides, npy_intp count)
+{
+ char *data0 = dataptr[0], *data1 = dataptr[1], *data2 = dataptr[2];
+ npy_intp stride1 = strides[1], stride2 = strides[2];
+ npy_intp i;
+ char value = *data0;
+
+ for (i = 0; i < count && !value; ++i) {
+ value |= *data1 & *data2;
+
+ data1 += stride1;
+ data2 += stride2;
+ }
+
+ *(npy_bool *)data0 = value;
+}
+
+static void
+any_masked_inner_0stride_gen_0stride(char **dataptr,
+ npy_intp *strides, npy_intp count)
+{
+ char *data0 = dataptr[0], *data1 = dataptr[1], *data2 = dataptr[2];
+ npy_intp stride1 = strides[1];
+ npy_intp i;
+ char value = *data0, maskvalue = *data2;
+
+ for (i = 0; i < count && !value; ++i) {
+ value |= *data1 & maskvalue;
+
+ data1 += stride1;
+ }
+
+ *(npy_bool *)data0 = value;
+}
+
+static int
+reduce_any_masked_loop(NpyIter *iter,
+ char **dataptr,
+ npy_intp *strides,
+ npy_intp *countptr,
+ NpyIter_IterNextFunc *iternext,
+ int needs_api,
+ npy_intp skip_first_count,
+ void *data)
+{
+ npy_intp fixed_strides[3];
+ reduce_inner_loop *inner_loop;
+ NPY_BEGIN_THREADS_DEF;
+
+ NpyIter_GetInnerFixedStrideArray(iter, fixed_strides);
+
+ if (!needs_api) {
+ NPY_BEGIN_THREADS;
+ }
+
+ /* Choose a loop specialized based on the strides */
+ if (fixed_strides[0] == 0) {
+ if (fixed_strides[2] == 0) {
+ inner_loop = &any_masked_inner_0stride_gen_0stride;
+ }
+ else {
+ inner_loop = &any_masked_inner_0stride_gen_gen;
+ }
+ }
+ else {
+ inner_loop = &any_masked_inner_gen_gen_gen;
+ }
+
+ /*
+ * 'skip_first_count' will always be 0 because we are doing a reduction
+ * with an identity.
+ */
+
+ do {
+ inner_loop(dataptr, strides, *countptr);
+ } while (iternext(iter));
+
+ if (!needs_api) {
+ NPY_END_THREADS;
+ }
+
+ return (needs_api && PyErr_Occurred()) ? -1 : 0;
+}
+
+static void
+any_adv_masked_inner_gen_gen_gen_gen(char **dataptr,
+ npy_intp *strides, npy_intp count)
+{
+ char *data0 = dataptr[0], *data1 = dataptr[1];
+ char *data2 = dataptr[2], *data3 = dataptr[3];
+ npy_intp stride0 = strides[0], stride1 = strides[1];
+ npy_intp stride2 = strides[2], stride3 = strides[3];
+ npy_intp i;
+
+ for (i = 0; i < count; ++i) {
+ /* Normal case */
+ if (*data2) {
+ *data0 |= *data1;
+ }
+ /* If the value is an exposed True, expose the result as well */
+ else if (*data1 & *data3) {
+ *data0 = 1;
+ *data2 = 1;
+ }
+
+ data0 += stride0;
+ data1 += stride1;
+ data2 += stride2;
+ data3 += stride3;
+ }
+}
+
+static void
+any_adv_masked_inner_0stride_gen_0stride_gen(char **dataptr,
+ npy_intp *strides, npy_intp count)
+{
+ char *data0 = dataptr[0], *data1 = dataptr[1];
+ char *data2 = dataptr[2], *data3 = dataptr[3];
+ npy_intp stride1 = strides[1], stride3 = strides[3];
+ npy_intp i;
+ char maskvalue = *data2;
+ char value = maskvalue ? *data0 : 0;
+
+ for (i = 0; i < count && !value; ++i) {
+ /* Normal case */
+ if (maskvalue) {
+ value |= *data1;
+ }
+ /* If the value is an exposed True, expose the result as well */
+ else if (*data1 & *data3) {
+ value = 1;
+ maskvalue = 1;
+ break;
+ }
+
+ data1 += stride1;
+ data3 += stride3;
+ }
+
+ if (maskvalue) {
+ *data0 = value;
+ *data2 = maskvalue;
+ }
+}
+
+static int
+reduce_any_advanced_masked_loop(NpyIter *iter,
+ char **dataptr,
+ npy_intp *strides,
+ npy_intp *countptr,
+ NpyIter_IterNextFunc *iternext,
+ int needs_api,
+ npy_intp skip_first_count,
+ void *data)
+{
+ npy_intp fixed_strides[4];
+ reduce_inner_loop *inner_loop;
+ NPY_BEGIN_THREADS_DEF;
+
+ NpyIter_GetInnerFixedStrideArray(iter, fixed_strides);
+
+ if (!needs_api) {
+ NPY_BEGIN_THREADS;
+ }
+
+ /* Choose a loop specialized based on the strides */
+ if (fixed_strides[0] == 0 && fixed_strides[2] == 0) {
+ inner_loop = &any_adv_masked_inner_0stride_gen_0stride_gen;
+ }
+ else {
+ inner_loop = &any_adv_masked_inner_gen_gen_gen_gen;
+ }
+
+ /*
+ * 'skip_first_count' will always be 0 because we are doing a reduction
+ * with an identity.
+ */
+
+ do {
+ inner_loop(dataptr, strides, *countptr);
+ } while (iternext(iter));
+
+ if (!needs_api) {
+ NPY_END_THREADS;
+ }
+
+ return (needs_api && PyErr_Occurred()) ? -1 : 0;
+}
+
+NPY_NO_EXPORT PyArrayObject *
+PyArray_ReduceAny(PyArrayObject *arr, PyArrayObject *out,
+ npy_bool *axis_flags, int skipna, int keepdims)
+{
+ PyArrayObject *result;
+ PyArray_Descr *bool_dtype;
+
+ bool_dtype = PyArray_DescrFromType(NPY_BOOL);
+ if (bool_dtype == NULL) {
+ return NULL;
+ }
+
+ result = PyArray_ReduceWrapper(arr, out, NULL,
+ bool_dtype, bool_dtype,
+ NPY_UNSAFE_CASTING,
+ axis_flags, 1, skipna, NULL, keepdims, 1,
+ &assign_identity_any,
+ &reduce_any_loop,
+ &reduce_any_masked_loop,
+ &reduce_any_advanced_masked_loop,
+ NULL, 0, "any");
+ Py_DECREF(bool_dtype);
+ return result;
+}
diff --git a/numpy/core/src/multiarray/boolean_ops.h b/numpy/core/src/multiarray/boolean_ops.h
new file mode 100644
index 000000000..b3cf41a6d
--- /dev/null
+++ b/numpy/core/src/multiarray/boolean_ops.h
@@ -0,0 +1,9 @@
+#ifndef _NPY_PRIVATE__BOOLEAN_OPS_H_
+#define _NPY_PRIVATE__BOOLEAN_OPS_H_
+
+NPY_NO_EXPORT PyArrayObject *
+PyArray_ReduceAny(PyArrayObject *arr, PyArrayObject *out,
+ npy_bool *axis_flags, int skipna, int keepdims);
+
+
+#endif
diff --git a/numpy/core/src/multiarray/item_selection.c b/numpy/core/src/multiarray/item_selection.c
index 5d8b99489..11f506c8c 100644
--- a/numpy/core/src/multiarray/item_selection.c
+++ b/numpy/core/src/multiarray/item_selection.c
@@ -1923,6 +1923,11 @@ reduce_count_nonzero_loop(NpyIter *iter,
NPY_BEGIN_THREADS;
}
+ /*
+ * 'skip_first_count' will always be 0 because we are doing a reduction
+ * with an identity.
+ */
+
do {
char *data0 = dataptr[0], *data1 = dataptr[1];
npy_intp stride0 = strides[0], stride1 = strides[1];
@@ -1963,6 +1968,11 @@ reduce_count_nonzero_masked_loop(NpyIter *iter,
NPY_BEGIN_THREADS;
}
+ /*
+ * 'skip_first_count' will always be 0 because we are doing a reduction
+ * with an identity.
+ */
+
do {
char *data0 = dataptr[0], *data1 = dataptr[1], *data2 = dataptr[2];
npy_intp stride0 = strides[0], stride1 = strides[1],
@@ -2018,10 +2028,11 @@ PyArray_ReduceCountNonzero(PyArrayObject *arr, PyArrayObject *out,
result = PyArray_ReduceWrapper(arr, out, NULL,
PyArray_DESCR(arr), dtype,
NPY_SAME_KIND_CASTING,
- axis_flags, 1, skipna, NULL, keepdims,
+ axis_flags, 1, skipna, NULL, keepdims, 0,
&assign_reduce_identity_zero,
&reduce_count_nonzero_loop,
&reduce_count_nonzero_masked_loop,
+ NULL,
nonzero, 0, "count_nonzero");
Py_DECREF(dtype);
if (out == NULL && result != NULL) {
diff --git a/numpy/core/src/multiarray/methods.c b/numpy/core/src/multiarray/methods.c
index b99425969..eb57e6db4 100644
--- a/numpy/core/src/multiarray/methods.c
+++ b/numpy/core/src/multiarray/methods.c
@@ -20,6 +20,7 @@
#include "item_selection.h"
#include "conversion_utils.h"
#include "shape.h"
+#include "boolean_ops.h"
#include "methods.h"
@@ -1993,51 +1994,60 @@ array_dot(PyArrayObject *self, PyObject *args, PyObject *kwds)
static PyObject *
-array_any(PyArrayObject *self, PyObject *args, PyObject *kwds)
+array_any(PyArrayObject *array, PyObject *args, PyObject *kwds)
{
- int axis = MAX_DIMS;
+ static char *kwlist[] = {"axis", "out", "skipna", "keepdims", NULL};
+
+ PyObject *axis_in = NULL;
PyArrayObject *out = NULL;
- static char *kwlist[] = {"axis", "out", NULL};
+ PyArrayObject *ret = NULL;
+ npy_bool axis_flags[NPY_MAXDIMS];
+ int skipna = 0, keepdims = 0;
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&O&", kwlist,
- PyArray_AxisConverter, &axis,
- PyArray_OutputConverter, &out))
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ "|OO&ii:count_reduce_items", kwlist,
+ &axis_in,
+ &PyArray_OutputConverter, &out,
+ &skipna,
+ &keepdims)) {
return NULL;
+ }
+
+ if (PyArray_ConvertMultiAxis(axis_in, PyArray_NDIM(array),
+ axis_flags) != NPY_SUCCEED) {
+ Py_DECREF(array);
+ return NULL;
+ }
- return PyArray_Any(self, axis, out);
+ ret = PyArray_ReduceAny(array, out, axis_flags, skipna, keepdims);
+
+ if (out == NULL) {
+ return PyArray_Return(ret);
+ }
+ else {
+ return (PyObject *)ret;
+ }
}
static PyObject *
array_all(PyArrayObject *self, PyObject *args, PyObject *kwds)
{
- int axis = MAX_DIMS;
- PyArrayObject *out = NULL;
- static char *kwlist[] = {"axis", "out", NULL};
-
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&O&", kwlist,
- PyArray_AxisConverter, &axis,
- PyArray_OutputConverter, &out))
- return NULL;
-
- return PyArray_All(self, axis, out);
+ NPY_FORWARD_NDARRAY_METHOD("_all");
}
-
static PyObject *
array_stddev(PyArrayObject *self, PyObject *args, PyObject *kwds)
{
NPY_FORWARD_NDARRAY_METHOD("_std");
}
-
static PyObject *
array_variance(PyArrayObject *self, PyObject *args, PyObject *kwds)
{
NPY_FORWARD_NDARRAY_METHOD("_var");
}
-
static PyObject *
array_compress(PyArrayObject *self, PyObject *args, PyObject *kwds)
{
diff --git a/numpy/core/src/multiarray/reduction.c b/numpy/core/src/multiarray/reduction.c
index d8978ad3b..b17e3ca6e 100644
--- a/numpy/core/src/multiarray/reduction.c
+++ b/numpy/core/src/multiarray/reduction.c
@@ -36,7 +36,7 @@
*/
static PyArrayObject *
allocate_reduce_result(PyArrayObject *arr, npy_bool *axis_flags,
- PyArray_Descr *dtype)
+ PyArray_Descr *dtype, int subok)
{
npy_intp strides[NPY_MAXDIMS], stride;
npy_intp shape[NPY_MAXDIMS], *arr_shape = PyArray_DIMS(arr);
@@ -67,9 +67,10 @@ allocate_reduce_result(PyArrayObject *arr, npy_bool *axis_flags,
}
/* Finally, allocate the array */
- return (PyArrayObject *)PyArray_NewFromDescr(&PyArray_Type, dtype,
- ndim, shape, strides,
- NULL, 0, NULL);
+ return (PyArrayObject *)PyArray_NewFromDescr(
+ subok ? Py_TYPE(arr) : &PyArray_Type,
+ dtype, ndim, shape, strides,
+ NULL, 0, subok ? (PyObject *)arr : NULL);
}
/*
@@ -201,6 +202,9 @@ conform_reduce_result(int ndim, npy_bool *axis_flags,
* type conversion/validity check for 'out'. When 'need_namask' is true,
* raises an exception if 'out' doesn't have an NA mask.
*
+ * If 'subok' is true, creates a result with the subtype of 'operand',
+ * otherwise creates on with the base ndarray class.
+ *
* If 'out' is NULL, it allocates a new array whose shape matches
* that of 'operand', except for at the reduction axes. An NA mask
* is added if 'need_namask' is true. If 'dtype' is NULL, the dtype
@@ -209,13 +213,14 @@ conform_reduce_result(int ndim, npy_bool *axis_flags,
NPY_NO_EXPORT PyArrayObject *
PyArray_CreateReduceResult(PyArrayObject *operand, PyArrayObject *out,
PyArray_Descr *dtype, npy_bool *axis_flags,
- int need_namask, int keepdims, const char *funcname)
+ int need_namask, int keepdims, int subok,
+ const char *funcname)
{
PyArrayObject *result;
if (out == NULL) {
/* This function steals the reference to 'dtype' */
- result = allocate_reduce_result(operand, axis_flags, dtype);
+ result = allocate_reduce_result(operand, axis_flags, dtype, subok);
/* Allocate an NA mask if necessary */
if (need_namask && result != NULL) {
@@ -761,11 +766,20 @@ PyArray_InitializeReduceResult(
* breaking API compatibility. Pass in NULL.
* keepdims : If true, leaves the reduction dimensions in the result
* with size one.
+ * subok : If true, the result uses the subclass of operand, otherwise
+ * it is always a base class ndarray.
* assign_identity : If NULL, PyArray_InitializeReduceResult is used, otherwise
* this function is called to initialize the result to
* the reduction's unit.
* loop : The loop which does the reduction.
* masked_loop : The loop which does the reduction with a mask.
+ * advanced_masked_loop: If non-NULL, this is a loop which uses a mask from
+ * both the operand and the result. The 'result' is
+ * initialized to a usual reduction of the operand's mask,
+ * but both the operand's mask and the result's mask
+ * are provided so that the loop may decide to expose
+ * elements, which normally would not be exposed by the
+ * normal NA propagation rules, based on the input data.
* data : Data which is passed to assign_identity and the inner loop.
* buffersize : Buffer size for the iterator. For the default, pass in 0.
* funcname : The name of the reduction function, for error messages.
@@ -778,9 +792,11 @@ PyArray_ReduceWrapper(PyArrayObject *operand, PyArrayObject *out,
NPY_CASTING casting,
npy_bool *axis_flags, int reorderable,
int skipna, npy_bool *skipwhichna, int keepdims,
+ int subok,
PyArray_AssignReduceIdentityFunc *assign_identity,
PyArray_ReduceLoopFunc *loop,
PyArray_ReduceLoopFunc *masked_loop,
+ PyArray_ReduceLoopFunc *advanced_masked_loop,
void *data, npy_intp buffersize, const char *funcname)
{
int use_maskna;
@@ -836,7 +852,7 @@ PyArray_ReduceWrapper(PyArrayObject *operand, PyArrayObject *out,
Py_INCREF(result_dtype);
result = PyArray_CreateReduceResult(operand, out,
result_dtype, axis_flags, !skipna && use_maskna,
- keepdims, funcname);
+ keepdims, subok, funcname);
if (result == NULL) {
goto fail;
}
@@ -851,8 +867,12 @@ PyArray_ReduceWrapper(PyArrayObject *operand, PyArrayObject *out,
goto fail;
}
- /* Short circuit any calculation if the result is 0-dim NA */
- if (PyArray_SIZE(result) == 1 &&
+ /*
+ * Short circuit any calculation if the result is a 0-dim NA
+ * and the advanced masked loop which could expose it isn't
+ * provided.
+ */
+ if (advanced_masked_loop == NULL && PyArray_SIZE(result) == 1 &&
!NpyMaskValue_IsExposed(
(npy_mask)*PyArray_MASKNA_DATA(result))) {
goto finish;
@@ -923,8 +943,14 @@ PyArray_ReduceWrapper(PyArrayObject *operand, PyArrayObject *out,
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;
+ if (advanced_masked_loop == NULL) {
+ /* Input's mask is already incorporated in the output's mask */
+ op_flags[1] |= NPY_ITER_IGNORE_MASKNA;
+ }
+ else {
+ /* The reduction wants to use the operand's mask as well */
+ op_flags[1] |= NPY_ITER_USE_MASKNA;
+ }
}
}
else {
@@ -977,7 +1003,14 @@ PyArray_ReduceWrapper(PyArrayObject *operand, PyArrayObject *out,
goto fail;
}
}
- /* Masked reduction */
+ /* Masked reduction with both masks */
+ else if (!skipna && advanced_masked_loop != NULL) {
+ if (advanced_masked_loop(iter, dataptr, strideptr, countptr,
+ iternext, needs_api, skip_first_count, data) < 0) {
+ goto fail;
+ }
+ }
+ /* Regular masked reduction with just one mask */
else {
if (masked_loop == NULL) {
PyErr_Format(PyExc_RuntimeError,
@@ -1099,7 +1132,7 @@ PyArray_CountReduceItems(PyArrayObject *operand,
}
result = PyArray_CreateReduceResult(operand, NULL,
result_dtype, axis_flags, 0,
- keepdims, "count_reduce_items");
+ keepdims, 0, "count_reduce_items");
if (result == NULL) {
return NULL;
}
diff --git a/numpy/core/src/multiarray/reduction.h b/numpy/core/src/multiarray/reduction.h
index 95193ad28..268fdaf29 100644
--- a/numpy/core/src/multiarray/reduction.h
+++ b/numpy/core/src/multiarray/reduction.h
@@ -93,6 +93,9 @@ PyArray_InitializeReduceResult(
* type conversion/validity check for 'out'. When 'need_namask' is true,
* raises an exception if 'out' doesn't have an NA mask.
*
+ * If 'subok' is true, creates a result with the subtype of 'operand',
+ * otherwise creates on with the base ndarray class.
+ *
* If 'out' is NULL, it allocates a new array whose shape matches
* that of 'operand', except for at the reduction axes. An NA mask
* is added if 'need_namask' is true. If 'dtype' is NULL, the dtype
@@ -101,6 +104,7 @@ PyArray_InitializeReduceResult(
NPY_NO_EXPORT PyArrayObject *
PyArray_CreateReduceResult(PyArrayObject *operand, PyArrayObject *out,
PyArray_Descr *dtype, npy_bool *axis_flags,
- int need_namask, int keepdims, const char *funcname);
+ int need_namask, int keepdims, int subok,
+ const char *funcname);
#endif
diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c
index ae82f6130..ae8169166 100644
--- a/numpy/core/src/umath/ufunc_object.c
+++ b/numpy/core/src/umath/ufunc_object.c
@@ -2862,10 +2862,11 @@ PyUFunc_Reduce(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *out,
result = PyArray_ReduceWrapper(arr, out, NULL, dtype, dtype,
NPY_UNSAFE_CASTING,
axis_flags, reorderable,
- skipna, NULL, keepdims,
+ skipna, NULL, keepdims, 0,
assign_identity,
reduce_loop,
masked_reduce_loop,
+ NULL,
ufunc, buffersize, ufunc_name);
Py_DECREF(dtype);
diff --git a/numpy/matrixlib/defmatrix.py b/numpy/matrixlib/defmatrix.py
index aac123e59..dbf2909fd 100644
--- a/numpy/matrixlib/defmatrix.py
+++ b/numpy/matrixlib/defmatrix.py
@@ -375,6 +375,15 @@ class matrix(N.ndarray):
else:
raise ValueError("unsupported axis")
+ def _collapse(self, axis):
+ """A convenience function for operations that want to collapse
+ to a scalar like _align, but are using keepdims=True
+ """
+ if axis is None:
+ return self[0,0]
+ else:
+ return self
+
# Necessary because base-class tolist expects dimension
# reduction by x[0]
def tolist(self):
@@ -432,7 +441,7 @@ class matrix(N.ndarray):
[ 7.]])
"""
- return N.ndarray.sum(self, axis, dtype, out)._align(axis)
+ return N.ndarray.sum(self, axis, dtype, out, keepdims=True)._collapse(axis)
def mean(self, axis=None, dtype=None, out=None):
"""
@@ -466,7 +475,7 @@ class matrix(N.ndarray):
[ 9.5]])
"""
- return N.ndarray.mean(self, axis, dtype, out)._align(axis)
+ return N.ndarray.mean(self, axis, dtype, out, keepdims=True)._collapse(axis)
def std(self, axis=None, dtype=None, out=None, ddof=0):
"""
@@ -500,7 +509,7 @@ class matrix(N.ndarray):
[ 1.11803399]])
"""
- return N.ndarray.std(self, axis, dtype, out, ddof)._align(axis)
+ return N.ndarray.std(self, axis, dtype, out, ddof, keepdims=True)._collapse(axis)
def var(self, axis=None, dtype=None, out=None, ddof=0):
"""
@@ -534,7 +543,7 @@ class matrix(N.ndarray):
[ 1.25]])
"""
- return N.ndarray.var(self, axis, dtype, out, ddof)._align(axis)
+ return N.ndarray.var(self, axis, dtype, out, ddof, keepdims=True)._collapse(axis)
def prod(self, axis=None, dtype=None, out=None):
"""
@@ -567,7 +576,7 @@ class matrix(N.ndarray):
[7920]])
"""
- return N.ndarray.prod(self, axis, dtype, out)._align(axis)
+ return N.ndarray.prod(self, axis, dtype, out, keepdims=True)._collapse(axis)
def any(self, axis=None, out=None):
"""
@@ -590,7 +599,7 @@ class matrix(N.ndarray):
returns `ndarray`
"""
- return N.ndarray.any(self, axis, out)._align(axis)
+ return N.ndarray.any(self, axis, out, keepdims=True)._collapse(axis)
def all(self, axis=None, out=None):
"""
@@ -630,7 +639,7 @@ class matrix(N.ndarray):
[False]], dtype=bool)
"""
- return N.ndarray.all(self, axis, out)._align(axis)
+ return N.ndarray.all(self, axis, out, keepdims=True)._collapse(axis)
def max(self, axis=None, out=None):
"""
@@ -665,7 +674,7 @@ class matrix(N.ndarray):
[11]])
"""
- return N.ndarray.max(self, axis, out)._align(axis)
+ return N.ndarray.max(self, axis, out, keepdims=True)._collapse(axis)
def argmax(self, axis=None, out=None):
"""
@@ -735,7 +744,7 @@ class matrix(N.ndarray):
[-11]])
"""
- return N.ndarray.min(self, axis, out)._align(axis)
+ return N.ndarray.min(self, axis, out, keepdims=True)._collapse(axis)
def argmin(self, axis=None, out=None):
"""