Repository: dask/dask

Issue:
Improve user experience of interacting with numpy arrays
Currently when numpy arrays interact with dask arrays we swallow errors silently, revealing them only at computation time.

``` python
In [1]: import dask.array as da
In [2]: import numpy as np
In [3]: x = da.arange(15, chunks=5)
In [4]: y = np.arange(15)

In [5]: x + y
Out[5]: dask.array<x_1, shape=(15,), chunks=((5, 5, 5)), dtype=int64>

In [6]: _.compute()
ValueError: operands could not be broadcast together with shapes (5,) (15,) 
```

We should do one of the following things:
1.  Figure out automatic conversion of a numpy array into a dask array with appropriate shape to make the operation work (hard in general, maybe easy in a few key cases)
2.  Raise an informative error early on (maybe easier in the hard cases)



Discussion:
We might be interested in adding a `__numpy_ufunc__` method. It lets third party objects override ufuncs. More info here 

https://github.com/numpy/numpy/blob/master/doc/neps/ufunc-overrides.rst

I worked on adding this to NumPy in 2013, and continuing development on it after the Summer became unfeasible because it there was a lot of maintenance and I was in school. It's MRO is currently being discussed here: 

https://github.com/numpy/numpy/issues/5844

I don't think the state of NumPy ufunc is mature or stable enough to warrant this yet.

For now I think a simple thing to do would be to raise a `NotImplementedError` immediately instead of waiting until compute time to fail.


Resolving patch:
diff --git a/dask/array/core.py b/dask/array/core.py
--- a/dask/array/core.py
+++ b/dask/array/core.py
@@ -1417,8 +1417,10 @@ def elemwise(op, *args, **kwargs):
     expr_inds = tuple(range(out_ndim))[::-1]
 
     arrays = [arg for arg in args if isinstance(arg, Array)]
-    other = [(i, arg) for i, arg in enumerate(args) if not isinstance(arg, Array)]
-
+    other = [(i, a) for i, a in enumerate(args) if not isinstance(a, Array)]
+    if any(isinstance(arg, np.ndarray) for arg in args):
+        raise NotImplementedError("Dask.array operations only work on dask "
+                                  "arrays, not numpy arrays.")
     if 'dtype' in kwargs:
         dt = kwargs['dtype']
     elif not all(a._dtype is not None for a in arrays):


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
@@ -7,6 +7,7 @@
 
 from toolz import merge
 from toolz.curried import identity
+from numpy.testing import assert_raises
 
 import dask
 import dask.array as da
@@ -692,7 +693,7 @@ def eq(a, b):
     assert eq(b.std()._dtype, b.std().dtype)
     assert eq(a.argmin(axis=0)._dtype, a.argmin(axis=0).dtype)
 
-    assert eq(da.sin(z)._dtype, np.sin(c).dtype)
+    assert eq(da.sin(c)._dtype, np.sin(z).dtype)
     assert eq(da.exp(b)._dtype, np.exp(y).dtype)
     assert eq(da.floor(a)._dtype, np.floor(x).dtype)
     assert eq(da.isnan(b)._dtype, np.isnan(y).dtype)
@@ -1058,3 +1059,10 @@ def test_map_blocks3():
 
     assert eq(da.core.map_blocks(lambda a, b: a+2*b, d, f, dtype=d.dtype),
               x + 2*z)
+
+
+def test_numpy_compat_is_notimplemented():
+    a = np.arange(10)
+    x = da.from_array(a, chunks=5)
+
+    assert_raises(NotImplementedError, add, x, a)