Repository: dask/dask

Issue:
Array copy behaviour
The following behaviour when copying a dask array created using `from_array` and then computing the result isn't what I'd expect:

```python
import dask.array as da
import numpy as np

d = da.from_array(np.array([0, 1]), chunks=2,)
dc = d.copy()

d is dc
False
d.compute() is dc.compute()
True
```

A dask array not created using `from_array` does not show this behaviour:

```python
d1 = da.ones(10, chunks=2,)

d1c = d1.copy()
d1 is d1c
False
d1.compute() is d1c.compute()
False
```

For reference, this behaviour seems to be tripping up [an Iris test](https://github.com/SciTools/iris/blob/master/lib/iris/tests/unit/cube/test_Cube.py#L1432-L1434), resulting in [this test failure](https://travis-ci.org/SciTools/iris/jobs/397436711#L5120-L5128). 

I'm using Python 3.6.5 and dask 0.18.1 (this behaviour doesn't appear in v0.18.0).



Discussion:
Hi @dkillick, thanks for opening up this issue! I believe the source of this difference is coming from a recent modification to `from_array` that added

https://github.com/dask/dask/blob/a95be8d16428671dda934f4c9217a6fc6023b89f/dask/array/core.py#L2331-L2333

in #3568. `d.compute() is dc.compute()` being `True` in your above example with dask 0.18.1 is because the `d` and `dc` only have a single chunk. For example, 

```python
import dask.array as da
import numpy as np

x = np.arange(10)
for name, chunks in zip(['single chunk', 'multiple chunks'], [-1, 2]):
    d = da.from_array(x, chunks=chunks)
    d_copy = d.copy()
    print(name)
    print('chunks = {}'.format(d.chunks))
    print(d is d_copy)
    print(d.compute() is d_copy.compute())
    print('\n')
```

should output something like

```
single chunk
chunks = ((10,),)
False
True

multiple chunks
chunks = ((2, 2, 2, 2, 2),)
False
False
```

One potential solution would be to update the `chunks` parameter in your test such that the dask array you're dealing with have more than a single chunk. Would this be a reasonable solution to fix your test failure? 

We could also consider calling `self.map_blocks(np.copy)` if
self.npartitions == 1 within the copy function.


On Fri, Jul 13, 2018 at 1:19 PM, James Bourbeau <notifications@github.com>
wrote:

> Hi @dkillick <https://github.com/dkillick>, thanks for opening up this
> issue! I believe the source of this difference is coming from a recent
> modification to from_array that added
>
> https://github.com/dask/dask/blob/a95be8d16428671dda934f4c9217a6
> fc6023b89f/dask/array/core.py#L2331-L2333
>
> in #3568 <https://github.com/dask/dask/pull/3568>. d.compute() is
> dc.compute() being True in your above example with dask 0.18.1 is because
> the d and dc only have a single chunk. For example,
>
> import dask.array as daimport numpy as np
>
> x = np.arange(10)for name, chunks in zip(['single chunk', 'multiple chunks'], [-1, 2]):
>     d = da.from_array(x, chunks=chunks)
>     d_copy = d.copy()
>     print(name)
>     print('chunks = {}'.format(d.chunks))
>     print(d is d_copy)
>     print(d.compute() is d_copy.compute())
>     print('\n')
>
> should output something like
>
> single chunk
> chunks = ((10,),)
> False
> True
>
> multiple chunks
> chunks = ((2, 2, 2, 2, 2),)
> False
> False
>
> One potential solution would be to update the chunks parameter in your
> test such that the dask array you're dealing with have more than a single
> chunk. Would this be a reasonable solution to fix your test failure?
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/dask/dask/issues/3751#issuecomment-404913371>, or mute
> the thread
> <https://github.com/notifications/unsubscribe-auth/AASszIOkgnqKBrl49M6QB1WmXdEDTLo3ks5uGOSggaJpZM4VOru2>
> .
>


Resolving patch:
diff --git a/dask/array/core.py b/dask/array/core.py
--- a/dask/array/core.py
+++ b/dask/array/core.py
@@ -1958,7 +1958,10 @@ def copy(self):
         """
         Copy array.  This is a no-op for dask.arrays, which are immutable
         """
-        return Array(self.dask, self.name, self.chunks, self.dtype)
+        if self.npartitions == 1:
+            return self.map_blocks(np.copy)
+        else:
+            return Array(self.dask, self.name, self.chunks, self.dtype)
 
     def __deepcopy__(self, memo):
         c = self.copy()


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
@@ -2120,6 +2120,16 @@ def test_from_array_minus_one():
     assert_eq(x, y)
 
 
+def test_from_array_copy():
+    # Regression test for https://github.com/dask/dask/issues/3751
+    x = np.arange(10)
+    y = da.from_array(x, -1)
+    assert y.npartitions == 1
+    y_c = y.copy()
+    assert y is not y_c
+    assert y.compute() is not y_c.compute()
+
+
 def test_asarray():
     assert_eq(da.asarray([1, 2, 3]), np.asarray([1, 2, 3]))