Repository: scipy/scipy

Issue:
 Comparisons between scipy spmatrix and can sparse.SparseArray return scalar bool
This issue is about interop with scipy sparse and pydata sparse. `scipy.sparse` doesn't let `pydata/sparse` overload `__eq__` according to https://github.com/pydata/sparse/issues/305#issuecomment-560317183 (I'm still a little fuzzy on when `__array_priority__` and `__array_ufunc__` get used). This ends up returning a scalar for `!=` or `==`.

#### Reproducing code example:

```python
In [1]: import sparse                                                                               
sra 
In [2]: import scipy                                                                                

In [3]: scipy.__version__                                                                           
Out[3]: '1.5.0.dev0+5958e9f'

In [4]: coo = sparse.random((10, 10))                                                               

In [5]: csr = coo.tocsr()                                                                           

In [6]: csr == coo                                                                                  
Out[6]: False
```

I'd expected this to broadcast and not be False, especially since interop seems to be a goal (#10901).


Discussion:
If someone can tell me where `scipy.sparse.spmatrix.__eq__` and so on are defined, I can try to make this change.
I don't remember outright where it is, but I guess grep should point you to about the right place. The spmatrix comparisons are probably quite old code, and might not follow sane convention.


On December 2, 2019 10:21:24 AM UTC, Hameer Abbasi <notifications@github.com> wrote:
>If someone can tell me where `scipy.sparse.spmatrix.__eq__` and so on
>are defined, I can try to make this change.

It looks like it's effectively under `sparse/compressed.py`

https://github.com/scipy/scipy/blob/2ba80dc11ffe931495ec054070e601ae3d8edd03/scipy/sparse/base.py#L369-L373

https://github.com/scipy/scipy/blob/2ba80dc11ffe931495ec054070e601ae3d8edd03/scipy/sparse/compressed.py#L217
Looks like it's specialized to each format, which isn't surprising. I'll try to figure this out, but I might not get to it too soon.

Resolving patch:
diff --git a/scipy/sparse/compressed.py b/scipy/sparse/compressed.py
--- a/scipy/sparse/compressed.py
+++ b/scipy/sparse/compressed.py
@@ -21,7 +21,7 @@
 from .sputils import (upcast, upcast_char, to_native, isdense, isshape,
                       getdtype, isscalarlike, isintlike, get_index_dtype,
                       downcast_intp_index, get_sum_dtype, check_shape,
-                      matrix, asmatrix)
+                      matrix, asmatrix, is_pydata_spmatrix)
 
 
 class _cs_matrix(_data_matrix, _minmax_mixin, IndexMixin):
@@ -232,6 +232,9 @@ def __eq__(self, other):
         # Dense other.
         elif isdense(other):
             return self.todense() == other
+        # Pydata sparse other.
+        elif is_pydata_spmatrix(other):
+            return NotImplemented
         # Sparse other.
         elif isspmatrix(other):
             warn("Comparing sparse matrices using == is inefficient, try using"
@@ -267,6 +270,9 @@ def __ne__(self, other):
         # Dense other.
         elif isdense(other):
             return self.todense() != other
+        # Pydata sparse other.
+        elif is_pydata_spmatrix(other):
+            return NotImplemented
         # Sparse other.
         elif isspmatrix(other):
             # TODO sparse broadcasting


Test patch:
diff --git a/scipy/sparse/linalg/tests/test_pydata_sparse.py b/scipy/sparse/linalg/tests/test_pydata_sparse.py
--- a/scipy/sparse/linalg/tests/test_pydata_sparse.py
+++ b/scipy/sparse/linalg/tests/test_pydata_sparse.py
@@ -21,12 +21,33 @@
 sparse_params = [pytest.param("COO"),
                  pytest.param("DOK", marks=[pytest.mark.xfail(reason=msg)])]
 
+scipy_sparse_classes = [
+    sp.bsr_matrix,
+    sp.csr_matrix,
+    sp.coo_matrix,
+    sp.csc_matrix,
+    sp.dia_matrix,
+    sp.dok_matrix
+]
+
 
 @pytest.fixture(params=sparse_params)
 def sparse_cls(request):
     return getattr(sparse, request.param)
 
 
+@pytest.fixture(params=scipy_sparse_classes)
+def sp_sparse_cls(request):
+    return request.param
+
+
+@pytest.fixture
+def same_matrix(sparse_cls, sp_sparse_cls):
+    np.random.seed(1234)
+    A_dense = np.random.rand(9, 9)
+    return sp_sparse_cls(A_dense), sparse_cls(A_dense)
+
+
 @pytest.fixture
 def matrices(sparse_cls):
     np.random.seed(1234)
@@ -202,3 +223,13 @@ def test_expm_multiply(matrices):
     x0 = splin.expm_multiply(A_dense, b)
     x = splin.expm_multiply(A_sparse, b)
     assert_allclose(x, x0)
+
+
+def test_eq(same_matrix):
+    sp_sparse, pd_sparse = same_matrix
+    assert (sp_sparse == pd_sparse).all()
+
+
+def test_ne(same_matrix):
+    sp_sparse, pd_sparse = same_matrix
+    assert not (sp_sparse != pd_sparse).any()