Repository: dask/dask

Issue:
Reshape with empty dimensions
Reshaping with zero dimensions fails

```python
def test_reshape_empty():
    x = da.ones((0, 10), chunks=(5, 5))
    y = x.reshape((0, 5, 2))
    assert_eq(x, x) 
```

I've tracked this down to the fact that this line returns a list with one empty list

```python
def intersect_chunks(...):
    ...
    old_to_new = [_intersect_1d(_breakpoints(old, new))
                  for old, new in zip(cmo, cmn)]
```

So the subsequent call to `itertools.product` returns an empty result.

I tried diving into `_intersect_1d` but had difficulty understanding the intent or the expected output.

@pitrou if you feel like looking this I would appreciate it.  Totally OK if not.  Otherwise I will resolve it later today.

Discussion:
mrocklin: OK with the assert_eq change?
pitrou: Yes, of course.

Patch:
diff --git a/dask/array/rechunk.py b/dask/array/rechunk.py
index de5da0f68..eea5470ff 100644
--- a/dask/array/rechunk.py
+++ b/dask/array/rechunk.py
@@ -18,6 +18,7 @@ from toolz import accumulate, reduce
 
 from ..base import tokenize
 from .core import concatenate3, Array, normalize_chunks
+from .wrap import empty
 from .. import sharedict
 
 
@@ -484,6 +485,10 @@ def plan_rechunk(old_chunks, new_chunks, itemsize,
 def _compute_rechunk(x, chunks):
     """ Compute the rechunk of *x* to the given *chunks*.
     """
+    if x.size == 0:
+        # Special case for empty array, as the algorithm below does not behave correctly
+        return empty(x.shape, chunks=chunks, dtype=x.dtype)
+
     ndim = x.ndim
     crossed = intersect_chunks(x.chunks, chunks)
     x2 = dict()


Test patch:
diff --git a/dask/array/tests/test_array_core.py b/dask/array/tests/test_array_core.py
index 86686843b..a68e45d72 100644
--- a/dask/array/tests/test_array_core.py
+++ b/dask/array/tests/test_array_core.py
@@ -788,6 +788,10 @@ def test_roll(chunks, shift, axis):
     ((4, 64), (4, 8, 4, 2), (2, 16)),
     ((4, 8, 4, 2), (2, 1, 2, 32, 2), (2, 4, 2, 2)),
     ((4, 1, 4), (4, 4), (2, 1, 2)),
+    ((0, 10), (0, 5, 2), (5, 5)),
+    ((5, 0, 2), (0, 10), (5, 2, 2)),
+    ((0,), (2, 0, 2), (4,)),
+    ((2, 0, 2), (0,), (4, 4, 4)),
 ])
 def test_reshape(original_shape, new_shape, chunks):
     x = np.random.randint(10, size=original_shape)
diff --git a/dask/array/tests/test_rechunk.py b/dask/array/tests/test_rechunk.py
index 01fdcee7a..967f6ce9c 100644
--- a/dask/array/tests/test_rechunk.py
+++ b/dask/array/tests/test_rechunk.py
@@ -6,6 +6,7 @@ np = pytest.importorskip('numpy')
 
 import dask
 from dask.utils import funcname
+from dask.array.utils import assert_eq
 from dask.array.rechunk import intersect_chunks, rechunk, normalize_chunks
 from dask.array.rechunk import cumdims_label, _breakpoints, _intersect_1d
 from dask.array.rechunk import plan_rechunk, divide_to_width, merge_to_number
@@ -184,6 +185,13 @@ def test_rechunk_0d():
     assert y.compute() == a
 
 
+def test_rechunk_empty():
+    x = da.ones((0, 10), chunks=(5, 5))
+    y = x.rechunk((2, 2))
+    assert y.chunks == ((0,), (2,) * 5)
+    assert_eq(x, y)
+
+
 def test_rechunk_same():
     x = da.ones((24, 24), chunks=(4, 8))
     y = x.rechunk(x.chunks)