Repository: chainer/chainer

Issue:
Fix to make it consistent with numpy.split and cupy.split
Fix to make it consistent with numpy.split and cupy.split

before
```
>>> import numpy as np
>>> import cupy as cp
>>> import chainer.functions as F
>>> np.split(np.arange(4), indices_or_sections=[0,1,1], axis=0)
[array([], dtype=int64), array([0]), array([], dtype=int64), array([1, 2, 3])]
>>> cp.split(cp.arange(4), indices_or_sections=[0,1,1], axis=0)
[array([], dtype=int64), array([0]), array([], dtype=int64), array([1, 2, 3])]
>>> F.split_axis(np.arange(4), indices_or_sections=[0,1,1], axis=0)
(variable([]), variable([0]), variable([]), variable([1, 2, 3]))
>>> np.split(np.arange(4), indices_or_sections=[0,1,1,4], axis=0)
[array([], dtype=int64), array([0]), array([], dtype=int64), array([1, 2, 3]), array([], dtype=int64)]
>>> cp.split(cp.arange(4), indices_or_sections=[0,1,1,4], axis=0)
[array([], dtype=int64), array([0]), array([], dtype=int64), array([1, 2, 3]), array([], dtype=int64)]
>>> F.split_axis(np.arange(4), indices_or_sections=[0,1,1,4], axis=0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/***/chainer/functions/array/split_axis.py", line 92, in split_axis
    res = SplitAxis(indices_or_sections, axis).apply((x,))
  File "/***/chainer/function_node.py", line 230, in apply
    self._check_data_type_forward(in_data)
  File "/***/chainer/function_node.py", line 298, in _check_data_type_forward
    self.check_type_forward(in_type)
  File "/***/chainer/functions/array/split_axis.py", line 37, in check_type_forward
    type_check.expect(in_types[0].shape[self.axis] > max_index)
  File "/***/chainer/utils/type_check.py", line 524, in expect
    expr.expect()
  File "/***/chainer/utils/type_check.py", line 482, in expect
    '{0} {1} {2}'.format(left, self.inv, right))
chainer.utils.type_check.InvalidType:
Invalid operation is performed in: SplitAxis (Forward)

Expect: in_types[0].shape[0] > max_index
Actual: 4 <= 4
```

after
```
>>> import numpy as np
>>> import chainer.functions as F
>>> F.split_axis(np.arange(4), indices_or_sections=[0,1,1,4], axis=0)
(variable([]), variable([0]), variable([]), variable([1, 2, 3]), variable([]))
```


Discussion:
Jenkins, test this please.
Could you add a test case in `tests/chainer_tests/functions_tests/array_tests/test_split_axis.py`?
jenkins, test this please.
This PR fixes #4333.
Cloud you resolve conflicts?
Thanks!
jenkins, test this please.
jenkins, test this please.
Test was failed on NumPy 1.9.3 environment.
Please skip some test case?
@ken-nakanishi Do you have time to fix this PR?
Sorry for the late notice. I will start this task immediately.
Thanks!
jenkins, test this please.
Noted with thanks. Now I fixed it.
Thanks!

jenkins, test this please.
LGTM!

Resolving patch:
diff --git a/chainer/functions/array/split_axis.py b/chainer/functions/array/split_axis.py
--- a/chainer/functions/array/split_axis.py
+++ b/chainer/functions/array/split_axis.py
@@ -10,6 +10,25 @@
 from chainer.utils import type_check
 
 
+_numpy_split_ok = numpy.lib.NumpyVersion(numpy.__version__) >= '1.11.0'
+
+
+def _fix_numpy_split(ys, x, indices_or_sections, axis):
+    """Make the output of np.split compatible with numpy >= 1.11"""
+    if all(y.ndim == x.ndim for y in ys):
+        return ys
+    tmp = [len(t) for t in numpy.split(
+        numpy.empty(x.shape[axis], dtype=numpy.int8), indices_or_sections, 0)]
+    shape = list(x.shape)
+    for i, t in enumerate(tmp):
+        y = ys[i]
+        if y.ndim != x.ndim:
+            assert y.size == 0
+            shape[axis] = t
+            ys[i] = y.reshape(shape)
+    return ys
+
+
 def _get_indices_or_sections(indices_or_sections):
     """Checks and convert ``indices_or_sections`` argument
 
@@ -93,9 +112,11 @@ def forward(self, inputs):
             indices_or_sections = self.indices
         else:
             indices_or_sections = self.sections
-        ret = tuple(self._xp.split(x, indices_or_sections, self.axis))
+        ret = self._xp.split(x, indices_or_sections, self.axis)
+        if self._xp == numpy and not _numpy_split_ok:
+            ret = _fix_numpy_split(ret, x, indices_or_sections, self.axis)
         self._shapes = [r.shape for r in ret]
-        return ret
+        return tuple(ret)
 
     def _ideep_is_supported(self, inputs):
         # Returns True if iDeep supports current configuration of inputs and
@@ -176,11 +197,6 @@ def split_axis(x, indices_or_sections, axis, force_tuple=True):
         When ``force_tuple`` is ``True``, returned value is always a tuple
         regardless of the number of outputs.
 
-    .. note::
-        This function raises :class:`ValueError` if at least
-        one of the outputs is split to zero-size
-        (i.e. ``axis``-th value of its shape is zero).
-
     """
     res = SplitAxis(indices_or_sections, axis).apply((x,))
     if force_tuple or len(res) != 1:


Test patch:
diff --git a/tests/chainer_tests/functions_tests/array_tests/test_split_axis.py b/tests/chainer_tests/functions_tests/array_tests/test_split_axis.py
--- a/tests/chainer_tests/functions_tests/array_tests/test_split_axis.py
+++ b/tests/chainer_tests/functions_tests/array_tests/test_split_axis.py
@@ -31,6 +31,8 @@ def inject_backend_tests(method_names):
              (slice(None), slice(5, None))]},
         {'shape': (7, 3), 'axis': 0, 'ys_section': [2, 5],
          'slices': [slice(None, 2), slice(2, 5), slice(5, None)]},
+        {'shape': (7, 0), 'axis': 0, 'ys_section': [2, 5],
+         'slices': [slice(None, 2), slice(2, 5), slice(5, None)]},
         {'shape': (2, 9, 3), 'axis': 1, 'ys_section': 3,
          'slices': [
              (slice(None), slice(None, 3)),
@@ -128,7 +130,6 @@ def inject_backend_tests(method_names):
         {'dtype': numpy.float64},
     ],
 ))
-@testing.with_requires('numpy>=1.11')
 @inject_backend_tests(['test_forward', 'test_backward'])
 class TestSplitAxis(unittest.TestCase):