Repository: pydata/sparse

Issue:
Failure on tensordot for 0-length array
A `reshape` error occurs when executing `tensordot` on two 0-length arrays:

```python
>>> import numpy as np
>>> import sparse
>>>
>>> x = np.ones((0, 0, 0))
>>> y = np.ones((0, 0, 0))
>>> xx = sparse.COO.from_numpy(x)
>>> yy = sparse.COO.from_numpy(y)
>>>
>>> print(np.tensordot(x, y))
[]
>>> print(sparse.tensordot(xx, yy))
/home/pentschev/miniconda3/envs/rapids/lib/python3.6/site-packages/sparse/coo/core.py:1702: RuntimeWarning: invalid value encountered in long_scalars
  np.prod([d for d in shape if d != -1]))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/pentschev/miniconda3/envs/rapids/lib/python3.6/site-packages/sparse/coo/common.py", line 151, in tensordot
    at = a.transpose(newaxes_a).reshape(newshape_a)
  File "/home/pentschev/miniconda3/envs/rapids/lib/python3.6/site-packages/sparse/coo/core.py", line 1702, in reshape
    np.prod([d for d in shape if d != -1]))
ValueError: cannot convert float NaN to integer
```

As per discussion in #254, the behavior of `reshape` seems to match that of NumPy. The failure occurs probably due to a different internal implementation in Sparse, which is utilizing `reshape` in a manner not expected by the NumPy standard.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/pydata/sparse/pull/256?src=pr&el=h1) Report
> Merging [#256](https://codecov.io/gh/pydata/sparse/pull/256?src=pr&el=desc) into [master](https://codecov.io/gh/pydata/sparse/commit/ff0ed8e2887edf50b9554500ff008ac1bc0cfeed?src=pr&el=desc) will **decrease** coverage by `0.05%`.
> The diff coverage is `83.33%`.


```diff
@@            Coverage Diff             @@
##           master     #256      +/-   ##
==========================================
- Coverage   97.33%   97.28%   -0.06%     
==========================================
  Files          10       10              
  Lines        1614     1620       +6     
==========================================
+ Hits         1571     1576       +5     
- Misses         43       44       +1
```




Patch:
diff --git a/sparse/coo/common.py b/sparse/coo/common.py
index d6b9d69..15a561a 100644
--- a/sparse/coo/common.py
+++ b/sparse/coo/common.py
@@ -1,4 +1,5 @@
 from functools import reduce, wraps
+from itertools import chain
 import operator
 import warnings
 from collections.abc import Iterable
@@ -148,6 +149,13 @@ def tensordot(a, b, axes=2):
     newshape_b = (N2, -1)
     oldb = [bs[axis] for axis in notin]
 
+    if any(dim == 0 for dim in chain(newshape_a, newshape_b)):
+        res = asCOO(np.empty(olda + oldb), check=False)
+        if isinstance(a, np.ndarray) or isinstance(b, np.ndarray):
+            res = res.todense()
+
+        return res
+
     at = a.transpose(newaxes_a).reshape(newshape_a)
     bt = b.transpose(newaxes_b).reshape(newshape_b)
     res = _dot(at, bt)


Test patch:
diff --git a/tests/test_coo.py b/tests/test_coo.py
index c9992d4..2630aae 100644
--- a/tests/test_coo.py
+++ b/tests/test_coo.py
@@ -324,6 +324,15 @@ def test_tensordot(a_shape, b_shape, axes):
     # assert isinstance(sparse.tensordot(a, sb, axes), COO)
 
 
+def test_tensordot_empty():
+    x1 = np.empty((0, 0, 0))
+    x2 = np.empty((0, 0, 0))
+    s1 = sparse.COO.from_numpy(x1)
+    s2 = sparse.COO.from_numpy(x2)
+
+    assert_eq(np.tensordot(x1, x2), sparse.tensordot(s1, s2))
+
+
 @pytest.mark.parametrize('a_shape, b_shape', [
     ((3, 1, 6, 5), (2, 1, 4, 5, 6)),
     ((2, 1, 4, 5, 6), (3, 1, 6, 5)),