Repository: dask/dask

Issue:
Dask Array vindex doesn't allow identity
Seems like it would be good for Dask Array's `vindex` to support identity selections like `.vindex[:]` and similar. However it appears that using a trivial selection causes `vindex`. Would be nice if that was not the case though.

<details>
<summary>Example:</summary>

```python
In [1]: import numpy as np

In [2]: import dask.array as da

In [3]: a = da.random.random((110, 120), chunks=(11, 12))

In [4]: a.vindex[:, :]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-4-c7a331165a30> in <module>()
----> 1 a.vindex[:, :]

/zopt/conda2/envs/test/lib/python3.6/site-packages/dask/utils.py in __getitem__(self, key)
    182 
    183     def __getitem__(self, key):
--> 184         return self.fn(key)
    185 
    186 

/zopt/conda2/envs/test/lib/python3.6/site-packages/dask/array/core.py in _vindex(self, key)
   1328                 "vindex requires at least one non-slice to vectorize over. "
   1329                 "Use normal slicing instead when only using slices. Got: {}"
-> 1330                 .format(key))
   1331         return _vindex(self, *key)
   1332 

IndexError: vindex requires at least one non-slice to vectorize over. Use normal slicing instead when only using slices. Got: (slice(None, None, None), slice(None, None, None))
```

</details>

<details>
<summary>Environment:</summary>

```yaml
name: test
channels:
- conda-forge
- defaults
dependencies:
- appnope=0.1.0=py36_0
- blas=1.1=openblas
- bokeh=0.12.13=py36_0
- ca-certificates=2018.1.18=0
- certifi=2018.1.18=py36_0
- click=6.7=py_1
- cloudpickle=0.5.2=py_0
- dask=0.17.0=py_0
- dask-core=0.17.0=py_0
- decorator=4.1.2=py36_0
- distributed=1.21.0=py36_0
- heapdict=1.0.0=py36_0
- ipython=6.2.1=py36_1
- ipython_genutils=0.2.0=py36_0
- jedi=0.11.1=py36_0
- jinja2=2.10=py36_0
- libgfortran=3.0.0=0
- locket=0.2.0=py36_1
- markupsafe=1.0=py36_0
- msgpack-python=0.5.1=py36_0
- ncurses=5.9=10
- numpy=1.14.0=py36_blas_openblas_200
- openblas=0.2.20=7
- openssl=1.0.2n=0
- pandas=0.22.0=py36_0
- parso=0.1.1=py_0
- partd=0.3.8=py36_0
- pexpect=4.4.0=py36_0
- pickleshare=0.7.4=py36_0
- pip=9.0.1=py36_1
- prompt_toolkit=1.0.15=py36_0
- psutil=5.4.0=py36_0
- ptyprocess=0.5.2=py36_0
- pygments=2.2.0=py36_0
- python=3.6.4=0
- python-dateutil=2.6.1=py36_0
- pytz=2018.3=py_0
- pyyaml=3.12=py36_1
- readline=7.0=0
- setuptools=38.5.1=py36_0
- simplegeneric=0.8.1=py36_0
- six=1.11.0=py36_1
- sortedcontainers=1.5.7=py36_0
- sqlite=3.20.1=2
- tblib=1.3.2=py36_0
- tk=8.6.7=0
- toolz=0.8.2=py_2
- tornado=4.5.3=py36_0
- traitlets=4.3.2=py36_0
- wcwidth=0.1.7=py36_0
- wheel=0.30.0=py36_2
- xz=5.2.3=0
- yaml=0.1.7=0
- zict=0.1.3=py_0
- zlib=1.2.11=0
```

</details>


Discussion:

Resolving patch:
diff --git a/dask/array/core.py b/dask/array/core.py
--- a/dask/array/core.py
+++ b/dask/array/core.py
@@ -1342,8 +1342,12 @@ def _vindex(self, key):
                 "vindex does not support indexing with None (np.newaxis), "
                 "got {}".format(key))
         if all(isinstance(k, slice) for k in key):
+            if all(k.indices(d) == slice(0, d).indices(d)
+                   for k, d in zip(key, self.shape)):
+                return self
             raise IndexError(
-                "vindex requires at least one non-slice to vectorize over. "
+                "vindex requires at least one non-slice to vectorize over "
+                "when the slices are not over the entire array (i.e, x[:]). "
                 "Use normal slicing instead when only using slices. Got: {}"
                 .format(key))
         return _vindex(self, *key)


Test patch:
diff --git a/dask/array/tests/test_array_core.py b/dask/array/tests/test_array_core.py
--- a/dask/array/tests/test_array_core.py
+++ b/dask/array/tests/test_array_core.py
@@ -2317,7 +2317,6 @@ def test_vindex_negative():
 def test_vindex_errors():
     d = da.ones((5, 5, 5), chunks=(3, 3, 3))
     pytest.raises(IndexError, lambda: d.vindex[np.newaxis])
-    pytest.raises(IndexError, lambda: d.vindex[:5])
     pytest.raises(IndexError, lambda: d.vindex[[1, 2], [1, 2, 3]])
     pytest.raises(IndexError, lambda: d.vindex[[True] * 5])
     pytest.raises(IndexError, lambda: d.vindex[[0], [5]])
@@ -2335,6 +2334,25 @@ def test_vindex_merge():
                                                           [10, 20, 30]])).all()
 
 
+def test_vindex_identity():
+    rng = da.random.RandomState(42)
+    a, b = 10, 20
+
+    x = rng.random(a, chunks=a // 2)
+    assert x is x.vindex[:]
+    assert x is x.vindex[:a]
+    pytest.raises(IndexError, lambda: x.vindex[:a - 1])
+    pytest.raises(IndexError, lambda: x.vindex[1:])
+    pytest.raises(IndexError, lambda: x.vindex[0:a:2])
+
+    x = rng.random((a, b), chunks=(a // 2, b // 2))
+    assert x is x.vindex[:, :]
+    assert x is x.vindex[:a, :b]
+    pytest.raises(IndexError, lambda: x.vindex[:, :b - 1])
+    pytest.raises(IndexError, lambda: x.vindex[:, 1:])
+    pytest.raises(IndexError, lambda: x.vindex[:, 0:b:2])
+
+
 def test_empty_array():
     assert_eq(np.arange(0), da.arange(0, chunks=5))