Repository: chainer/chainer

Issue:
Support basic indexing in Variable class
Currently `Variable` partially supports `__getitem__`. I need full support of basic indexing.
- [x] int index #1343 
- [x] slices #1154
- [x] new axis #1257 
- [x] elipsis #1343 

see also: http://docs.scipy.org/doc/numpy/reference/arrays.indexing.html#basic-slicing-and-indexing



Discussion:

Resolving patch:
diff --git a/chainer/functions/array/get_item.py b/chainer/functions/array/get_item.py
--- a/chainer/functions/array/get_item.py
+++ b/chainer/functions/array/get_item.py
@@ -1,7 +1,11 @@
 import collections
 
+import numpy
+
+import chainer
 from chainer import cuda
 from chainer import function
+from chainer import utils
 from chainer.utils import type_check
 from chainer import variable
 
@@ -13,16 +17,29 @@ class GetItem(function.Function):
     def __init__(self, slices):
         if not isinstance(slices, collections.Iterable):
             slices = tuple([slices])
+
+        if chainer.is_debug():
+            n_ellipses = 0
+            for s in slices:
+                if numpy.isscalar(s) or s is None or isinstance(s, slice):
+                    pass
+                elif s is Ellipsis:
+                    n_ellipses += 1
+                else:
+                    raise ValueError('Only basic indexing is supported')
+            if n_ellipses > 1:
+                raise ValueError('Only one Ellipsis is allowed')
+
         self.slices = slices
 
     def check_type_forward(self, in_types):
         type_check.expect(in_types.size() == 1)
         valid_slice = len(self.slices) - self.slices.count(None)
-        type_check.expect(in_types[0].ndim == valid_slice)
+        type_check.expect(in_types[0].ndim >= valid_slice)
 
     def forward(self, xs):
         ary = xs[0]
-        return ary[tuple(self.slices)],
+        return utils.force_array(ary[tuple(self.slices)]),
 
     def backward(self, xs, gys):
         xp = cuda.get_array_module(*xs)
@@ -37,12 +54,19 @@ def get_item(x, slices):
 
     Args:
         x (tuple of Variables): Variable to be sliced.
-        slices (slice or tuple of slices): Slice objects to slice variable.
+        slices (int, slice, None or Ellipsis or tuple of them): Basic slicing
+            to slice a variable. It supports ``int``, ``slice``, ``newaxis``
+            (equivalent to ``None``) and ``Ellipsis``.
 
     Returns:
         Variable: :class:`~chainer.Variable` object
             which contains sliced array of ``x``.
 
+    .. note::
+
+       See NumPy document for details of `indexing
+       <http://docs.scipy.org/doc/numpy/reference/arrays.indexing.html>`_.
+
     """
     return GetItem(slices)(x)
 


Test patch:
diff --git a/tests/chainer_tests/functions_tests/array_tests/test_get_item.py b/tests/chainer_tests/functions_tests/array_tests/test_get_item.py
--- a/tests/chainer_tests/functions_tests/array_tests/test_get_item.py
+++ b/tests/chainer_tests/functions_tests/array_tests/test_get_item.py
@@ -9,6 +9,7 @@
 from chainer import testing
 from chainer.testing import attr
 from chainer.testing import parameterize
+from chainer.utils import type_check
 
 
 @parameterize(
@@ -19,6 +20,10 @@
     {'axes': [], 'offsets': 0, 'new_axes': 0, 'sliced_shape': (1, 4, 3, 2)},
     {'axes': [], 'offsets': 0, 'new_axes': 2, 'sliced_shape': (4, 3, 1, 2)},
     {'axes': [], 'offsets': 0, 'new_axes': 3, 'sliced_shape': (4, 3, 2, 1)},
+    {'slices': (1, -1, 0), 'sliced_shape': ()},
+    {'slices': (1, -1), 'sliced_shape': (2,)},
+    {'slices': (1, Ellipsis, -1), 'sliced_shape': (3,)},
+    {'slices': (1, None, Ellipsis, None, -1), 'sliced_shape': (1, 3, 1)},
 )
 class TestGetItem(unittest.TestCase):
 
@@ -26,20 +31,23 @@ def setUp(self):
         self.x_data = numpy.random.uniform(-1, 1, (4, 3, 2))
         self.shape = (4, 2, 1)
         self.gy_data = numpy.random.uniform(-1, 1, self.sliced_shape)
-        # Convert axes, offsets and shape to slices
-        if isinstance(self.offsets, int):
-            self.offsets = tuple([self.offsets] * len(self.shape))
-        if isinstance(self.axes, int):
-            self.axes = tuple([self.axes])
-        self.slices = [slice(None)] * len(self.shape)
-        for axis in self.axes:
-            self.slices[axis] = slice(self.offsets[axis],
-                                      self.offsets[axis]+self.shape[axis])
 
-        if hasattr(self, 'new_axes'):
-            self.slices.insert(self.new_axes, None)
+        if not hasattr(self, 'slices'):
+            # Convert axes, offsets and shape to slices
+            if isinstance(self.offsets, int):
+                self.offsets = tuple([self.offsets] * len(self.shape))
+            if isinstance(self.axes, int):
+                self.axes = tuple([self.axes])
 
-        self.slices = tuple(self.slices)
+            self.slices = [slice(None)] * len(self.shape)
+            for axis in self.axes:
+                self.slices[axis] = slice(self.offsets[axis],
+                                          self.offsets[axis]+self.shape[axis])
+
+            if hasattr(self, 'new_axes'):
+                self.slices.insert(self.new_axes, None)
+
+            self.slices = tuple(self.slices)
 
     def check_forward(self, x_data):
         x = chainer.Variable(x_data)
@@ -68,4 +76,28 @@ def test_backward_gpu(self):
                             cuda.to_gpu(self.gy_data))
 
 
+class TestInvalidGetItem(unittest.TestCase):
+
+    def setUp(self):
+        self.default_debug = chainer.is_debug()
+        chainer.set_debug(True)
+
+        self.x_data = numpy.random.uniform(-1, 1, (4, 3, 2))
+
+    def tearDown(self):
+        chainer.set_debug(self.default_debug)
+
+    def test_advanced_indexing(self):
+        with self.assertRaises(ValueError):
+            functions.get_item(self.x_data, ([0, 0, 0],))
+
+    def test_multiple_ellipsis(self):
+        with self.assertRaises(ValueError):
+            functions.get_item(self.x_data, (Ellipsis, Ellipsis))
+
+    def test_too_many_indices(self):
+        with self.assertRaises(type_check.InvalidType):
+            functions.get_item(self.x_data, (0, 0, 0, 0))
+
+
 testing.run_module(__name__, __file__)