summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/release/1.8.0-notes.rst6
-rw-r--r--doc/source/reference/arrays.indexing.rst4
-rw-r--r--numpy/core/include/numpy/ndarraytypes.h4
-rw-r--r--numpy/core/src/multiarray/iterators.c6
-rw-r--r--numpy/core/src/multiarray/iterators.h3
-rw-r--r--numpy/core/src/multiarray/mapping.c92
-rw-r--r--numpy/core/src/multiarray/multiarray_tests.c.src8
-rw-r--r--numpy/core/tests/test_indexing.py331
8 files changed, 400 insertions, 54 deletions
diff --git a/doc/release/1.8.0-notes.rst b/doc/release/1.8.0-notes.rst
index e65658ad3..7a203bf6d 100644
--- a/doc/release/1.8.0-notes.rst
+++ b/doc/release/1.8.0-notes.rst
@@ -110,6 +110,12 @@ New `invert` argument to `in1d`
The function `in1d` now accepts a `invert` argument which, when `True`,
causes the returned array to be inverted.
+Advanced indexing using `np.newaxis`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+It is now possible to use `np.newaxis`/`None` together with index
+arrays instead of only in simple indices. This means that
+``array[np.newaxis, [0, 1]]`` will now work as expected.
+
C-API
~~~~~
diff --git a/doc/source/reference/arrays.indexing.rst b/doc/source/reference/arrays.indexing.rst
index f8966f5c1..e759b6ff8 100644
--- a/doc/source/reference/arrays.indexing.rst
+++ b/doc/source/reference/arrays.indexing.rst
@@ -170,8 +170,8 @@ concepts to remember include:
.. data:: newaxis
- The :const:`newaxis` object can be used in the basic slicing syntax
- discussed above. :const:`None` can also be used instead of
+ The :const:`newaxis` object can be used in all slicing operations
+ as discussed above. :const:`None` can also be used instead of
:const:`newaxis`.
diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h
index 7cc37bff8..bb3f1065c 100644
--- a/numpy/core/include/numpy/ndarraytypes.h
+++ b/numpy/core/include/numpy/ndarraytypes.h
@@ -1275,6 +1275,10 @@ typedef struct {
npy_intp bscoord[NPY_MAXDIMS];
PyObject *indexobj; /* creating obj */
+ /*
+ * consec is first used to indicate wether fancy indices are
+ * consecutive and then denotes at which axis they are inserted
+ */
int consec;
char *dataptr;
diff --git a/numpy/core/src/multiarray/iterators.c b/numpy/core/src/multiarray/iterators.c
index ce2ef4659..abaff6c98 100644
--- a/numpy/core/src/multiarray/iterators.c
+++ b/numpy/core/src/multiarray/iterators.c
@@ -97,7 +97,8 @@ NPY_NO_EXPORT int
parse_index(PyArrayObject *self, PyObject *op,
npy_intp *out_dimensions,
npy_intp *out_strides,
- npy_intp *out_offset)
+ npy_intp *out_offset,
+ int check_index)
{
int i, j, n;
int nd_old, nd_new, n_add, n_ellipsis;
@@ -136,7 +137,8 @@ parse_index(PyArrayObject *self, PyObject *op,
start = parse_index_entry(op1, &step_size, &n_steps,
nd_old < PyArray_NDIM(self) ?
PyArray_DIMS(self)[nd_old] : 0,
- nd_old, nd_old < PyArray_NDIM(self));
+ nd_old, check_index ?
+ nd_old < PyArray_NDIM(self) : 0);
Py_DECREF(op1);
if (start == -1) {
break;
diff --git a/numpy/core/src/multiarray/iterators.h b/numpy/core/src/multiarray/iterators.h
index 8276a3ceb..dad345935 100644
--- a/numpy/core/src/multiarray/iterators.h
+++ b/numpy/core/src/multiarray/iterators.h
@@ -9,7 +9,8 @@ NPY_NO_EXPORT int
parse_index(PyArrayObject *self, PyObject *op,
npy_intp *out_dimensions,
npy_intp *out_strides,
- npy_intp *out_offset);
+ npy_intp *out_offset,
+ int check_index);
NPY_NO_EXPORT PyObject
*iter_subscript(PyArrayIterObject *, PyObject *);
diff --git a/numpy/core/src/multiarray/mapping.c b/numpy/core/src/multiarray/mapping.c
index 0b4022874..17089761d 100644
--- a/numpy/core/src/multiarray/mapping.c
+++ b/numpy/core/src/multiarray/mapping.c
@@ -25,7 +25,7 @@
#define SOBJ_LISTTUP 4
static PyObject *
-array_subscript_simple(PyArrayObject *self, PyObject *op);
+array_subscript_simple(PyArrayObject *self, PyObject *op, int check_index);
/******************************************************************************
*** IMPLEMENT MAPPING PROTOCOL ***
@@ -226,7 +226,7 @@ PyArray_MapIterSwapAxes(PyArrayMapIterObject *mit, PyArrayObject **ret, int getm
* (n2,...,n1+n2-1,0,...,n2-1,n1+n2,...n3-1)
*/
n1 = mit->iters[0]->nd_m1 + 1;
- n2 = mit->iteraxes[0];
+ n2 = mit->consec; /* axes to insert at */
n3 = mit->nd;
/* use n1 as the boundary if getting but n2 if setting */
@@ -303,9 +303,7 @@ PyArray_GetMap(PyArrayMapIterObject *mit)
/* check for consecutive axes */
if ((mit->subspace != NULL) && (mit->consec)) {
- if (mit->iteraxes[0] > 0) { /* then we need to swap */
- PyArray_MapIterSwapAxes(mit, &ret, 1);
- }
+ PyArray_MapIterSwapAxes(mit, &ret, 1);
}
return (PyObject *)ret;
}
@@ -338,11 +336,9 @@ PyArray_SetMap(PyArrayMapIterObject *mit, PyObject *op)
return -1;
}
if ((mit->subspace != NULL) && (mit->consec)) {
- if (mit->iteraxes[0] > 0) { /* then we need to swap */
- PyArray_MapIterSwapAxes(mit, &arr, 0);
- if (arr == NULL) {
- return -1;
- }
+ PyArray_MapIterSwapAxes(mit, &arr, 0);
+ if (arr == NULL) {
+ return -1;
}
}
@@ -556,7 +552,7 @@ fancy_indexing_check(PyObject *args)
*/
NPY_NO_EXPORT PyObject *
-array_subscript_simple(PyArrayObject *self, PyObject *op)
+array_subscript_simple(PyArrayObject *self, PyObject *op, int check_index)
{
npy_intp dimensions[NPY_MAXDIMS], strides[NPY_MAXDIMS];
npy_intp offset;
@@ -601,7 +597,7 @@ array_subscript_simple(PyArrayObject *self, PyObject *op)
/* Standard (view-based) Indexing */
nd = parse_index(self, op, dimensions,
- strides, &offset);
+ strides, &offset, check_index);
if (nd == -1) {
return NULL;
}
@@ -1222,7 +1218,7 @@ array_subscript_fromobject(PyArrayObject *self, PyObject *op)
return array_subscript_fancy(self, op, fancy);
}
else {
- return array_subscript_simple(self, op);
+ return array_subscript_simple(self, op, 1);
}
}
@@ -1256,9 +1252,10 @@ array_subscript(PyArrayObject *self, PyObject *op)
ret = array_subscript_fancy(self, op, fancy);
}
else {
- ret = array_subscript_simple(self, op);
+ ret = array_subscript_simple(self, op, 1);
}
}
+
if (ret == NULL) {
return NULL;
}
@@ -1301,7 +1298,7 @@ array_ass_sub_simple(PyArrayObject *self, PyObject *ind, PyObject *op)
/* Rest of standard (view-based) indexing */
if (PyArray_CheckExact(self)) {
- tmp = (PyArrayObject *)array_subscript_simple(self, ind);
+ tmp = (PyArrayObject *)array_subscript_simple(self, ind, 1);
if (tmp == NULL) {
return -1;
}
@@ -1665,7 +1662,7 @@ _nonzero_indices(PyObject *myBool, PyArrayIterObject **iters)
}
/* convert an indexing object to an INTP indexing array iterator
- if possible -- otherwise, it is a Slice or Ellipsis object
+ if possible -- otherwise, it is a Slice, Ellipsis or None object
and has to be interpreted on bind to a particular
array so leave it NULL for now.
*/
@@ -1675,7 +1672,7 @@ _convert_obj(PyObject *obj, PyArrayIterObject **iter)
PyArray_Descr *indtype;
PyObject *arr;
- if (PySlice_Check(obj) || (obj == Py_Ellipsis)) {
+ if (PySlice_Check(obj) || (obj == Py_Ellipsis) || (obj == Py_None)) {
return 0;
}
else if (PyArray_Check(obj) && PyArray_ISBOOL((PyArrayObject *)obj)) {
@@ -1811,7 +1808,7 @@ PyArray_MapIterBind(PyArrayMapIterObject *mit, PyArrayObject *arr)
{
int subnd;
PyObject *sub, *obj = NULL;
- int i, j, n, curraxis, ellipexp, noellip;
+ int i, j, n, curraxis, ellipexp, noellip, newaxes;
PyArrayIterObject *it;
npy_intp dimsize;
npy_intp *indptr;
@@ -1827,24 +1824,18 @@ PyArray_MapIterBind(PyArrayMapIterObject *mit, PyArrayObject *arr)
if (mit->ait == NULL) {
goto fail;
}
- /* no subspace iteration needed. Finish up and Return */
- if (subnd == 0) {
- n = PyArray_NDIM(arr);
- for (i = 0; i < n; i++) {
- mit->iteraxes[i] = i;
- }
- goto finish;
- }
/*
* all indexing arrays have been converted to 0
* therefore we can extract the subspace with a simple
- * getitem call which will use view semantics
+ * getitem call which will use view semantics, but
+ * without index checking since all original normal
+ * indexes are checked later as fancy ones.
*
* But, be sure to do it with a true array.
*/
if (PyArray_CheckExact(arr)) {
- sub = array_subscript_simple(arr, mit->indexobj);
+ sub = array_subscript_simple(arr, mit->indexobj, 0);
}
else {
Py_INCREF(arr);
@@ -1852,32 +1843,53 @@ PyArray_MapIterBind(PyArrayMapIterObject *mit, PyArrayObject *arr)
if (obj == NULL) {
goto fail;
}
- sub = array_subscript_simple((PyArrayObject *)obj, mit->indexobj);
+ sub = array_subscript_simple((PyArrayObject *)obj, mit->indexobj, 0);
Py_DECREF(obj);
}
if (sub == NULL) {
goto fail;
}
+
+ subnd = PyArray_NDIM(sub);
+ /* no subspace iteration needed. Finish up and Return */
+ if (subnd == 0) {
+ n = PyArray_NDIM(arr);
+ for (i = 0; i < n; i++) {
+ mit->iteraxes[i] = i;
+ }
+ goto finish;
+ }
+
mit->subspace = (PyArrayIterObject *)PyArray_IterNew(sub);
Py_DECREF(sub);
if (mit->subspace == NULL) {
goto fail;
}
+
+ if (mit->nd + subnd > NPY_MAXDIMS) {
+ PyErr_Format(PyExc_ValueError,
+ "number of dimensions must be within [0, %d], "
+ "indexed array has %d",
+ NPY_MAXDIMS, mit->nd + subnd);
+ goto fail;
+ }
+
/* Expand dimensions of result */
- n = PyArray_NDIM(mit->subspace->ao);
- for (i = 0; i < n; i++) {
+ for (i = 0; i < subnd; i++) {
mit->dimensions[mit->nd+i] = PyArray_DIMS(mit->subspace->ao)[i];
}
- mit->nd += n;
+ mit->nd += subnd;
/*
- * Now, we still need to interpret the ellipsis and slice objects
- * to determine which axes the indexing arrays are referring to
+ * Now, we still need to interpret the ellipsis, slice and None
+ * objects to determine which axes the indexing arrays are
+ * referring to
*/
n = PyTuple_GET_SIZE(mit->indexobj);
/* The number of dimensions an ellipsis takes up */
- ellipexp = PyArray_NDIM(arr) - n + 1;
+ newaxes = subnd - (PyArray_NDIM(arr) - mit->numiter);
+ ellipexp = PyArray_NDIM(arr) + newaxes - n + 1;
/*
* Now fill in iteraxes -- remember indexing arrays have been
* converted to 0's in mit->indexobj
@@ -1886,6 +1898,8 @@ PyArray_MapIterBind(PyArrayMapIterObject *mit, PyArrayObject *arr)
j = 0;
/* Only expand the first ellipsis */
noellip = 1;
+ /* count newaxes before iter axes */
+ newaxes = 0;
memset(mit->bscoord, 0, sizeof(npy_intp)*PyArray_NDIM(arr));
for (i = 0; i < n; i++) {
/*
@@ -1900,6 +1914,11 @@ PyArray_MapIterBind(PyArrayMapIterObject *mit, PyArrayObject *arr)
curraxis += ellipexp;
noellip = 0;
}
+ else if (obj == Py_None) {
+ if (j == 0) {
+ newaxes += 1;
+ }
+ }
else {
npy_intp start = 0;
npy_intp stop, step;
@@ -1924,6 +1943,9 @@ PyArray_MapIterBind(PyArrayMapIterObject *mit, PyArrayObject *arr)
curraxis += 1;
}
}
+ if (mit->consec) {
+ mit->consec = mit->iteraxes[0] + newaxes;
+ }
finish:
/* Here check the indexes (now that we have iteraxes) */
diff --git a/numpy/core/src/multiarray/multiarray_tests.c.src b/numpy/core/src/multiarray/multiarray_tests.c.src
index 4aa179b68..f22b7462d 100644
--- a/numpy/core/src/multiarray/multiarray_tests.c.src
+++ b/numpy/core/src/multiarray/multiarray_tests.c.src
@@ -471,11 +471,9 @@ map_increment(PyArrayMapIterObject *mit, PyObject *op, inplace_map_binop add_inp
}
if ((mit->subspace != NULL) && (mit->consec)) {
- if (mit->iteraxes[0] > 0) {
- PyArray_MapIterSwapAxes(mit, (PyArrayObject **)&arr, 0);
- if (arr == NULL) {
- return -1;
- }
+ PyArray_MapIterSwapAxes(mit, (PyArrayObject **)&arr, 0);
+ if (arr == NULL) {
+ return -1;
}
}
diff --git a/numpy/core/tests/test_indexing.py b/numpy/core/tests/test_indexing.py
index 6906dcf6b..0a81a70d0 100644
--- a/numpy/core/tests/test_indexing.py
+++ b/numpy/core/tests/test_indexing.py
@@ -1,21 +1,13 @@
from __future__ import division, absolute_import, print_function
import numpy as np
+from itertools import product
from numpy.compat import asbytes
from numpy.testing import *
import sys, warnings
-# The C implementation of fancy indexing is relatively complicated,
-# and has many seeming inconsistencies. It also appears to lack any
-# kind of test suite, making any changes to the underlying code difficult
-# because of its fragility.
-
-# This file is to remedy the test suite part a little bit,
-# but hopefully NumPy indexing can be changed to be more systematic
-# at some point in the future.
class TestIndexing(TestCase):
-
def test_none_index(self):
# `None` index adds newaxis
a = np.array([1, 2, 3])
@@ -117,5 +109,326 @@ class TestIndexing(TestCase):
[4, 0, 6],
[0, 8, 0]])
+
+class TestMultiIndexingAutomated(TestCase):
+ """
+ These test use code to mimic the C-Code indexing for selection.
+
+ NOTE: * This still lacks tests for complex item setting.
+ * If you change behavoir of indexing, you might want to modify
+ these tests to try more combinations.
+ * Behavior was written to match numpy version 1.8. (though a
+ first version matched 1.7.)
+ * Only tuple indicies are supported by the mimicing code.
+ (and tested as of writing this)
+ * Error types should match most of the time as long as there
+ is only one error. For multiple errors, what gets raised
+ will usually not be the same one. They are *not* tested.
+ """
+ def setUp(self):
+ self.a = np.arange(np.prod([3,1,5,6])).reshape(3,1,5,6)
+ self.b = np.empty((3,0,5,6))
+ self.complex_indices = ['skip', Ellipsis,
+ 0,
+ # Boolean indices, up to 3-d for some special cases of eating up
+ # dimensions, also need to test all False
+ np.array(False),
+ np.array([True, False, False]),
+ np.array([[True, False], [False, True]]),
+ np.array([[[False, False], [False, False]]]),
+ # Some slices:
+ slice(-5, 5, 2),
+ slice(1, 1, 100),
+ slice(4, -1, -2),
+ slice(None,None,-3),
+ # Some Fancy indexes:
+ np.empty((0,1,1), dtype=np.intp), # empty broadcastable
+ np.array([0,1,-2]),
+ np.array([[2],[0],[1]]),
+ np.array([[0,-1], [0,1]]),
+ np.array([2,-1]),
+ np.zeros([1]*31, dtype=int), # trigger too large array.
+ np.array([0., 1.])] # invalid datatype
+ # Some simpler indices that still cover a bit more
+ self.simple_indices = [Ellipsis, None, -1, [1], np.array([True]), 'skip']
+ # Very simple ones to fill the rest:
+ self.fill_indices = [slice(None,None), 'skip']
+
+
+ def _get_multi_index(self, arr, indices):
+ """Mimic multi dimensional indexing. Returns the indexed array and a
+ flag no_copy. If no_copy is True, np.may_share_memory(arr, arr[indicies])
+ should be True (though this may be wrong for 0-d arrays sometimes.
+ If this function raises an error it should most of the time match the
+ real error as long as there is exactly one error in the index.
+ """
+ in_indices = list(indices)
+ indices = []
+ # if False, this is a fancy or boolean index
+ no_copy = True
+ # number of fancy/scalar indexes that are not consecutive
+ num_fancy = 0
+ # number of dimensions indexed by a "fancy" index
+ fancy_dim = 0
+ # NOTE: This is a funny twist (and probably OK to change).
+ # The boolean array has illegal indexes, but this is
+ # allowed if the broadcasted fancy-indices are 0-sized.
+ # This variable is to catch that case.
+ error_unless_broadcast_to_empty = False
+
+ # We need to handle Ellipsis and make arrays from indices, also
+ # check if this is fancy indexing (set no_copy).
+ ndim = 0
+ ellipsis_pos = None # define here mostly to replace all but first.
+ for i, indx in enumerate(in_indices):
+ if indx is None:
+ continue
+ if isinstance(indx, np.ndarray) and indx.dtype == bool:
+ no_copy = False
+ if indx.ndim == 0:
+ raise IndexError
+ # boolean indices can have higher dimensions
+ ndim += indx.ndim
+ fancy_dim += indx.ndim
+ continue
+ if indx is Ellipsis:
+ if ellipsis_pos is None:
+ ellipsis_pos = i
+ continue # do not increment ndim counter
+ in_indices[i] = slice(None,None)
+ ndim += 1
+ continue
+ if isinstance(indx, slice):
+ ndim += 1
+ continue
+ if not isinstance(indx, np.ndarray):
+ # This could be open for changes in numpy.
+ # numpy should maybe raise an error if casting to intp
+ # is not safe. It rejects np.array([1., 2.]) but not
+ # [1., 2.] as index (same for ie. np.take).
+ # (Note the importance of empty lists if changing this here)
+ indx = np.array(indx, dtype=np.intp)
+ in_indices[i] = indx
+ elif indx.dtype.kind != 'b' and indx.dtype.kind != 'i':
+ raise IndexError('arrays used as indices must be of integer (or boolean) type')
+ if indx.ndim != 0:
+ no_copy = False
+ ndim += 1
+ fancy_dim += 1
+
+ if arr.ndim - ndim < 0:
+ # we can't take more dimensions then we have, not even for 0-d arrays.
+ # since a[()] makes sense, but not a[(),]. We will raise an error
+ # lateron, unless a broadcasting error occurs first.
+ raise IndexError
+
+ if ndim == 0 and not None in in_indices:
+ # Well we have no indexes or one Ellipsis. This is legal.
+ return arr.copy(), no_copy
+
+ if ellipsis_pos is not None:
+ in_indices[ellipsis_pos:ellipsis_pos+1] = [slice(None,None)] * (arr.ndim - ndim)
+
+ for ax, indx in enumerate(in_indices):
+ if isinstance(indx, slice):
+ # convert to an index array anways:
+ indx = np.arange(*indx.indices(arr.shape[ax]))
+ indices.append(['s', indx])
+ continue
+ elif indx is None:
+ # this is like taking a slice with one element from a new axis:
+ indices.append(['n', np.array([0], dtype=np.intp)])
+ arr = arr.reshape((arr.shape[:ax] + (1,) + arr.shape[ax:]))
+ continue
+ if isinstance(indx, np.ndarray) and indx.dtype == bool:
+ # This may be open for improvement in numpy.
+ # numpy should probably cast boolean lists to boolean indices
+ # instead of intp!
+
+ # Numpy supports for a boolean index with
+ # non-matching shape as long as the True values are not
+ # out of bounds. Numpy maybe should maybe not allow this,
+ # (at least not array that are larger then the original one).
+ try:
+ flat_indx = np.ravel_multi_index(np.nonzero(indx),
+ arr.shape[ax:ax+indx.ndim], mode='raise')
+ except:
+ error_unless_broadcast_to_empty = True
+ # fill with 0s instead, and raise error later
+ flat_indx = np.array([0]*indx.sum(), dtype=np.intp)
+ # concatenate axis into a single one:
+ if indx.ndim != 0:
+ arr = arr.reshape((arr.shape[:ax]
+ + (np.prod(arr.shape[ax:ax+indx.ndim]),)
+ + arr.shape[ax+indx.ndim:]))
+ indx = flat_indx
+ else:
+ raise IndexError
+ if len(indices) > 0 and indices[-1][0] == 'f' and ax != ellipsis_pos:
+ # NOTE: There could still have been a 0-sized Ellipsis
+ # between them. Checked that with ellipsis_pos.
+ indices[-1].append(indx)
+ else:
+ # We have a fancy index that is not after an existing one.
+ # NOTE: A 0-d array triggers this as well, while
+ # one may expect it to not trigger it, since a scalar
+ # would not be considered fancy indexing.
+ num_fancy += 1
+ indices.append(['f', indx])
+
+ if num_fancy > 1 and not no_copy:
+ # We have to flush the fancy indexes left
+ new_indices = indices[:]
+ axes = list(range(arr.ndim))
+ fancy_axes = []
+ new_indices.insert(0, ['f'])
+ ni = 0
+ ai = 0
+ for indx in indices:
+ ni += 1
+ if indx[0] == 'f':
+ new_indices[0].extend(indx[1:])
+ del new_indices[ni]
+ ni -= 1
+ for ax in range(ai, ai + len(indx[1:])):
+ fancy_axes.append(ax)
+ axes.remove(ax)
+ ai += len(indx) - 1 # axis we are at
+ indices = new_indices
+ # and now we need to transpose arr:
+ arr = arr.transpose(*(fancy_axes + axes))
+
+ # We only have one 'f' index now and arr is transposed accordingly.
+ # Now handle newaxes by reshaping...
+ ax = 0
+ for indx in indices:
+ if indx[0] == 'f':
+ if len(indx) == 1:
+ continue
+ # First of all, reshape arr to combine fancy axes into one:
+ orig_shape = arr.shape
+ orig_slice = orig_shape[ax:ax + len(indx[1:])]
+ arr = arr.reshape((arr.shape[:ax]
+ + (np.prod(orig_slice).astype(int),)
+ + arr.shape[ax + len(indx[1:]):]))
+
+ # Check if broadcasting works
+ if len(indx[1:]) != 1:
+ res = np.broadcast(*indx[1:]) # raises ValueError...
+ else:
+ res = indx[1]
+ # unfortunatly the indices might be out of bounds. So check
+ # that first, and use mode='wrap' then. However only if
+ # there are any indices...
+ if res.size != 0:
+ if error_unless_broadcast_to_empty:
+ raise IndexError
+ for _indx, _size in zip(indx[1:], orig_slice):
+ if _indx.size == 0:
+ continue
+ if np.any(_indx >= _size) or np.any(_indx < -_size):
+ raise IndexError
+ if len(indx[1:]) == len(orig_slice):
+ if np.product(orig_slice) == 0:
+ # Work around for a crash or IndexError with 'wrap'
+ # in some 0-sized cases.
+ try:
+ mi = np.ravel_multi_index(indx[1:], orig_slice, mode='raise')
+ except:
+ # This happens with 0-sized orig_slice (sometimes?)
+ # here it is a ValueError, but indexing gives a:
+ raise IndexError('invalid index into 0-sized')
+ else:
+ mi = np.ravel_multi_index(indx[1:], orig_slice, mode='wrap')
+ else:
+ # Maybe never happens...
+ raise ValueError
+ arr = arr.take(mi.ravel(), axis=ax)
+ arr = arr.reshape((arr.shape[:ax]
+ + mi.shape
+ + arr.shape[ax+1:]))
+ ax += mi.ndim
+ continue
+
+ # If we are here, we have a 1D array for take:
+ arr = arr.take(indx[1], axis=ax)
+ ax += 1
+
+ return arr, no_copy
+
+
+ def _check_multi_index(self, arr, index):
+ """Check mult index getting and simple setting. Input array
+ must be a reshaped arange for __setitem__ check for non-view
+ arrays to work. It then relies on .flat to work.
+ """
+ # Test item getting
+ try:
+ mimic_get, no_copy = self._get_multi_index(arr, index)
+ except Exception as e:
+ assert_raises(Exception, arr.__getitem__, index)
+ assert_raises(Exception, arr.__setitem__, index, 0)
+ return
+
+ arr = arr.copy()
+ indexed_arr = arr[index]
+ assert_array_equal(indexed_arr, mimic_get)
+ # Check if we got a view, unless its a 0-sized or 0-d array.
+ # (then its not a view, and that does not matter)
+ if indexed_arr.size != 0 and indexed_arr.ndim != 0:
+ assert_(np.may_share_memory(indexed_arr, arr) == no_copy)
+
+ sys.stdout.flush()
+ # Test non-broadcast setitem:
+ b = arr.copy()
+ b[index] = mimic_get + 1000
+ if b.size == 0:
+ return # nothing to compare here...
+ if no_copy and indexed_arr.ndim != 0:
+ # change indexed_arr in-place to manipulate original:
+ indexed_arr += 1000
+ assert_array_equal(arr, b)
+ return
+ # Use the fact that the array is originally an arange:
+ arr.flat[indexed_arr.ravel()] += 1000
+ assert_array_equal(arr, b)
+
+
+ def test_boolean(self):
+ a = np.array(5)
+ assert_equal(a[np.array(True)], 5)
+ a[np.array(True)] = 1
+ assert_equal(a, 1)
+ # NOTE: This is different from normal broadcasting, as
+ # arr[boolean_array] works like in a multi index. Which means
+ # it is aligned to the left. This is probably correct for
+ # consistency with arr[boolean_array,] also no broadcasting
+ # is done at all
+ self._check_multi_index(self.a, (np.zeros_like(self.a, dtype=bool),))
+ self._check_multi_index(self.a, (np.zeros_like(self.a, dtype=bool)[...,0],))
+ self._check_multi_index(self.a, (np.zeros_like(self.a, dtype=bool)[None,...],))
+
+
+ def test_multidim(self):
+ # Check all combinations of all inner 3x3 arrays. Since test None
+ # we also test the Ellipsis OK.
+ tocheck = [self.simple_indices, self.complex_indices] + [self.simple_indices]*2
+ for simple_pos in [0,2,3]:
+ tocheck = [self.fill_indices, self.complex_indices, self.fill_indices, self.fill_indices]
+ tocheck[simple_pos] = self.simple_indices
+ for index in product(*tocheck):
+ index = tuple(i for i in index if i != 'skip')
+ self._check_multi_index(self.a, index)
+ self._check_multi_index(self.b, index)
+ # Check very simple item getting:
+ self._check_multi_index(self.a, (0,0,0,0))
+ self._check_multi_index(self.b, (0,0,0,0))
+ # Also check (simple cases of) too many indices:
+ assert_raises(IndexError, self.a.__getitem__, (0,0,0,0,0))
+ assert_raises(IndexError, self.a.__setitem__, (0,0,0,0,0), 0)
+ assert_raises(IndexError, self.a.__getitem__, (0,0,[1],0,0))
+ assert_raises(IndexError, self.a.__setitem__, (0,0,[1],0,0), 0)
+
+
if __name__ == "__main__":
run_module_suite()