Repository: dask/dask

Issue:
`from_array` no longer detects dask arrays
## Problem

Not sure if this needs to be supported, but I had some old code that accidentally did a `da.from_array(existing_dask_arr)`. This is obviously unnecessary, but the code ran fine. Starting with dask 2.2 this no longer seems to work and will result in `map_blocks` passing a dask array to the underlying function instead of a numpy array.

## Example Code

```
import numpy as np; import dask.array as da

a = np.zeros((5, 5), dtype=np.float)
a = da.from_array(a, chunks=2)
b = da.from_array(a, chunks=2)

def test(arr): 
    print(type(arr)) 
    return arr 

print("Running a")
a.map_blocks(test, dtype=a.dtype).compute() 
print("Running b")
b.map_blocks(test, dtype=b.dtype).compute() 
```

## Output

```
Running a
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
Running b
<class 'numpy.ndarray'>
<class 'dask.array.core.Array'>
<class 'dask.array.core.Array'>
<class 'dask.array.core.Array'>
<class 'dask.array.core.Array'>
<class 'dask.array.core.Array'>
<class 'dask.array.core.Array'>
<class 'dask.array.core.Array'>
<class 'dask.array.core.Array'>
<class 'dask.array.core.Array'>
```

## Expected

```
Running a
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
Running b
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
```


Discussion:
This looks a bit strange. 

I think that passing `b = da.from_array(a, chunks=2, asarray=True)` fixes things, but maybe that shouldn't be necessary for dask arrays?

cc @jakirkham, @pentschev if you have thoughts.

I think there is a good chance that this only worked before by nesting a (disconnected) dask array wrapping the inner dask array. This tends to be pretty problematic, because dask's scheduler doesn't understand how the arrays are connected.

The possible solutions are to either:
1. automagically convert `from_array` into `rechunk` on dask array (xarray actually exposes a single `chunk` API for both)
2. raise an error and require the user to be explicit
In general one should use `da.asarray` to ensure a Dask Array is given/used.

I don't have strong thoughts about fixing this as this doesn't seem like a use case that needs to be supported here (there are better recommended strategies). Though maybe we could error more clearly.

Feel free to push back though if there is some need I've overlooked in this thought process.
I agree that `asarray` would be better in most cases. This accidental code was in a unit test for a function that expects a DataArray with dask underneath. I had created a numpy array with certain values and converted it to a dask array. This was not in a function/method that would be part of any normal use case. That's why I'm not sure this is something that needs to be fixed. Developers using dask should be using `asarray` if they really need a dask array as input. 
I think we should definitely error more clearly here.

On Mon, Aug 12, 2019 at 9:13 AM David Hoese <notifications@github.com>
wrote:

> I agree that asarray would be better in most cases. This accidental code
> was in a unit test for a function that expects a DataArray with dask
> underneath. I had created a numpy array with certain values and converted
> it to a dask array. This was not in a function/method that would be part of
> any normal use case. That's why I'm not sure this is something that needs
> to be fixed. Developers using dask should be using asarray if they really
> need a dask array as input.
>
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> <https://github.com/dask/dask/issues/5262?email_source=notifications&email_token=AAJJFVTGSKOX4S3UM5K72V3QEGDY5A5CNFSM4ILCDGIKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD4DBDNA#issuecomment-520491444>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AAJJFVX36MS6IZKGGFSIXILQEGDY5ANCNFSM4ILCDGIA>
> .
>

@djhoese, any interest in submitting a PR?
Sure. I was just talking to people about how I needed more merged dask PRs.

You ok with a simple `isinstance` with a `raise ValueError`? Message could be something like:

> Array is already a dask array. Use 'asarray' or 'rechunk' instead.

Resolving patch:
diff --git a/dask/array/core.py b/dask/array/core.py
--- a/dask/array/core.py
+++ b/dask/array/core.py
@@ -2634,6 +2634,10 @@ def from_array(
     >>> a = da.from_array(x, chunks='100 MiB')  # doctest: +SKIP
     >>> a = da.from_array(x)  # doctest: +SKIP
     """
+    if isinstance(x, Array):
+        raise ValueError(
+            "Array is already a dask array. Use 'asarray' or " "'rechunk' instead."
+        )
     if isinstance(x, (list, tuple, memoryview) + np.ScalarType):
         x = np.array(x)
 
@@ -3576,7 +3580,7 @@ def asarray(a, **kwargs):
     elif hasattr(a, "to_dask_array"):
         return a.to_dask_array()
     elif isinstance(a, (list, tuple)) and any(isinstance(i, Array) for i in a):
-        a = stack(a)
+        return stack(a)
     elif not isinstance(getattr(a, "shape", None), Iterable):
         a = np.asarray(a)
     return from_array(a, chunks=a.shape, getitem=getter_inline, **kwargs)


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
@@ -2325,6 +2325,13 @@ def test_from_array_copy():
     assert y.compute() is not y_c.compute()
 
 
+def test_from_array_dask_array():
+    x = np.array([[1, 2], [3, 4]])
+    dx = da.from_array(x, chunks=(1, 2))
+    with pytest.raises(ValueError):
+        da.from_array(dx)
+
+
 def test_asarray():
     assert_eq(da.asarray([1, 2, 3]), np.asarray([1, 2, 3]))