Repository: dask/dask
Pull request: Allow mixing dask and numpy arrays in @guvectorize
- [x] Tests added / passed
- [x] Passes `black dask` / `flake8 dask`
- [x] Closes #6855 

I am not sure that this is the right time for adding a meta_nonempty for dask.array but I thought it might be? Also I suspect that this'll have repercussions for cupy @quasiben.


Problem statement:
Mixing of dask.array and numpy.ndarray in Numba @guvectorize
<!-- Please do a quick search of existing issues to make sure that this has not been asked before. -->

It appears that `dask.array` and `numpy.ndarray` cannot be mixed when using Numba's `@guvectorize` decorator - see the minimal example below. It would be a nice feature if dask could resolve this (or at least raise a more helpful exception!). Regardless, this is a truly helpful package!

**Minimal Example**
```python
import dask.array as da
import numpy as np
import numba as nb

# large array
x = np.random.random((300000, 100, 10))
y = da.from_array(x) # for the sake of the example

# small vector
v = np.ones(10)
w = da.from_array(v)

@nb.guvectorize([nb.void(nb.f8[:, :], nb.f8[:], nb.f8[:])],
                '(m, n), (n) -> (m)', target='parallel')
def test(arr, vec, out):
    # really just a test function
    out[:] = (arr + vec).sum(axis=1)

out1 = test(x, v) # works
out2 = test(y, w).compute() # works

out3 = test(y, v) # fails with ValueError
```
The following `ValueError` is raised ... (on dask version 2.30.0)
```
[...]
ValueError: `dtype` inference failed in `apply_gufunc`.

Please specify the dtype explicitly using the `output_dtypes` kwarg.

Original error is below:
------------------------
ValueError('test: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (m, n), (n) -> (m) (size 10 is different from 1)',)

Traceback:
---------
  File "<...>/miniconda3-latest/envs/<...>/lib/python3.6/site-packages/dask/array/core.py", line 377, in apply_infer_dtype
    o = func(*args, **kwargs)
```

Hints from issue discussion:
Thanks for opening this issue. I can reproduce on lastest master and this feels like something that should be allowed. I am opening a PR now with a proposed fix. 
Much appreciated! Agreed that it intuitively feels like this should work ... (the exception left me chasing my tail for a while)

Commit message: Make sure that args is an evaluated array

Patch to review:
diff --git a/dask/array/core.py b/dask/array/core.py
index e0123995311..664474dfce6 100644
--- a/dask/array/core.py
+++ b/dask/array/core.py
@@ -369,8 +369,15 @@ def apply_infer_dtype(func, args, kwargs, funcname, suggest_dtype="dtype", nout=
     : dtype or List of dtype
         One or many dtypes (depending on ``nout``)
     """
+    from .utils import ones_like_safe
+
+    # make sure that every arg is an evaluated array
     args = [
-        np.ones((1,) * x.ndim, dtype=x.dtype) if isinstance(x, Array) else x
+        ones_like_safe(
+            x._meta if isinstance(x, Array) else x, shape=((1,) * x.ndim), dtype=x.dtype
+        )
+        if is_arraylike(x)
+        else x
         for x in args
     ]
     try:
diff --git a/dask/array/tests/test_gufunc.py b/dask/array/tests/test_gufunc.py
index 11664bd28d9..fa3c481d621 100644
--- a/dask/array/tests/test_gufunc.py
+++ b/dask/array/tests/test_gufunc.py
@@ -319,6 +319,17 @@ def foo(x, y):
     assert_eq(x, 2 * np.ones((2, 8), dtype=int))
 
 
+def test_gufunc_mixed_inputs_vectorize():
+    def foo(x, y):
+        return (x + y).sum(axis=1)
+
+    a = da.ones((8, 3, 5), chunks=(2, 3, 5), dtype=int)
+    b = np.ones(5, dtype=int)
+    x = apply_gufunc(foo, "(m,n),(n)->(m)", a, b, vectorize=True)
+
+    assert_eq(x, np.full((8, 3), 10, dtype=int))
+
+
 def test_gufunc():
     x = da.random.normal(size=(10, 5), chunks=(2, 5))
 


Code review comments:
diff_hunk: @@ -369,8 +369,15 @@ def apply_infer_dtype(func, args, kwargs, funcname, suggest_dtype="dtype", nout=
     : dtype or List of dtype
         One or many dtypes (depending on ``nout``)
     """
+    from .utils import ones_like_safe
path: dask/array/core.py
text: @user1:
```suggestion
    from .utils import meta_from_array, ones_like_safe
```

diff_hunk: @@ -369,8 +369,15 @@ def apply_infer_dtype(func, args, kwargs, funcname, suggest_dtype="dtype", nout=
     : dtype or List of dtype
         One or many dtypes (depending on ``nout``)
     """
+    from .utils import ones_like_safe
+
+    # make sure that every arg is an evaluated array
     args = [
-        np.ones((1,) * x.ndim, dtype=x.dtype) if isinstance(x, Array) else x
+        ones_like_safe(
+            x._meta if isinstance(x, Array) else x, shape=((1,) * x.ndim), dtype=x.dtype
path: dask/array/core.py
text: @user1:
```suggestion
            meta_from_array(x), shape=((1,) * x.ndim), dtype=x.dtype
```

@author:
Ah true! I forgot about meta_from_array :facepalm: