Repository: pydata/sparse
Pull request #742 (2024-08-12 10:16:34)

depr, chore: Enforce deprecations and clean up warnings.
<!--
# Thanks for contributing a pull request!
## Please make sure you see our contribution guidelines: https://github.com/pydata/sparse/blob/main/docs/contributing.md

Your PR title should start with any of these abbreviatons: `build`, `chore`, `ci`, `depr`, `docs`, `feat`, `fix`, `perf`, `refactor`, `release`, `test`. Add a `!`at the end, if it is a breaking change.
-->

## What type of PR is this? (check all applicable)

- [x] 💾 Refactor
- [ ] 🪄 Feature
- [ ] 🐞 Bug Fix
- [x] 🔧 Optimization
- [ ] 📚 Documentation
- [ ] 🧪 Test
- [ ] 🛠️ Other

## Checklist

- [x] Code follows style guide
- [x] Tests added
- [x] Documented the changes

***

## Please explain your changes below.
This PR cleans up the deprecations introduced previously, and it makes sure no warnings are triggered during the tests.

There is still some Numba code using reflected lists (which are deprecated); that needs to be refactored.


codspeed-hq[bot]: ## [CodSpeed Performance Report](https://codspeed.io/pydata/sparse/branches/hameerabbasi:maint)

### Merging #742 will **not alter performance**

<sub>Comparing <code>hameerabbasi:maint</code> (57913dd) with <code>main</code> (29f9632)</sub>



### Summary

`✅ 28` untouched benchmarks






Patch:
diff --git a/examples/sparse_finch.ipynb b/examples/sparse_finch.ipynb
index 3684cf0..4d10021 100644
--- a/examples/sparse_finch.ipynb
+++ b/examples/sparse_finch.ipynb
@@ -24,7 +24,7 @@
     "import os\n",
     "\n",
     "os.environ[\"SPARSE_BACKEND\"] = \"Finch\"\n",
-    "CI_MODE = os.getenv(\"CI_MODE\", default=False)"
+    "CI_MODE = bool(int(os.getenv(\"CI_MODE\", default=\"0\")))"
    ]
   },
   {
@@ -42,9 +42,7 @@
     "\n",
     "import numpy as np\n",
     "import scipy.sparse as sps\n",
-    "import scipy.sparse.linalg as splin\n",
-    "\n",
-    "assert sparse.BackendType.Finch == sparse.BACKEND"
+    "import scipy.sparse.linalg as splin"
    ]
   },
   {
diff --git a/sparse/__init__.py b/sparse/__init__.py
index 4cf38ad..30fa195 100644
--- a/sparse/__init__.py
+++ b/sparse/__init__.py
@@ -7,32 +7,37 @@ from ._version import __version__, __version_tuple__  # noqa: F401
 __array_api_version__ = "2022.12"
 
 
-class BackendType(Enum):
+class _BackendType(Enum):
     Numba = "Numba"
     Finch = "Finch"
 
 
 _ENV_VAR_NAME = "SPARSE_BACKEND"
 
+
+class SparseFutureWarning(FutureWarning):
+    pass
+
+
 if os.environ.get(_ENV_VAR_NAME, "") != "":
     warnings.warn(
         "Changing back-ends is a development feature, please do not rely on it in production.",
-        FutureWarning,
+        SparseFutureWarning,
         stacklevel=1,
     )
     _backend_name = os.environ[_ENV_VAR_NAME]
 else:
-    _backend_name = BackendType.Numba.value
+    _backend_name = _BackendType.Numba.value
 
-if _backend_name not in {BackendType.Numba.value, BackendType.Finch.value}:
+if _backend_name not in {v.value for v in _BackendType}:
     warnings.warn(f"Invalid backend identifier: {_backend_name}. Selecting Numba backend.", UserWarning, stacklevel=1)
-    BACKEND = BackendType.Numba
+    _BACKEND = _BackendType.Numba
 else:
-    BACKEND = BackendType[_backend_name]
+    _BACKEND = _BackendType[_backend_name]
 
 del _backend_name
 
-if BackendType.Finch == BACKEND:
+if _BackendType.Finch == _BACKEND:
     from sparse.finch_backend import *  # noqa: F403
     from sparse.finch_backend import __all__
 else:
diff --git a/sparse/numba_backend/_common.py b/sparse/numba_backend/_common.py
index b30de11..dd08618 100644
--- a/sparse/numba_backend/_common.py
+++ b/sparse/numba_backend/_common.py
@@ -10,7 +10,7 @@ from numba import literal_unroll
 
 import numpy as np
 
-from ._coo.common import asCOO
+from ._coo import as_coo
 from ._sparse_array import SparseArray
 from ._utils import (
     _zero_of_dtype,
@@ -19,6 +19,9 @@ from ._utils import (
     normalize_axis,
 )
 
+_EINSUM_SYMBOLS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+_EINSUM_SYMBOLS_SET = set(_EINSUM_SYMBOLS)
+
 
 def _is_scipy_sparse_obj(x):
     """
@@ -189,7 +192,12 @@ def tensordot(a, b, axes=2, *, return_type=None):
     oldb = [bs[axis] for axis in notin]
 
     if builtins.any(dim == 0 for dim in chain(newshape_a, newshape_b)):
-        res = asCOO(np.empty(olda + oldb), check=False)
+        from sparse import COO
+
+        dt = np.result_type(a.dtype, b.dtype)
+        res = COO(
+            np.empty((len(olda) + len(oldb), 0), dtype=np.uintp), data=np.empty(0, dtype=dt), shape=tuple(olda + oldb)
+        )
         if isinstance(a, np.ndarray) or isinstance(b, np.ndarray):
             res = res.todense()
 
@@ -309,9 +317,9 @@ def dot(a, b):
 
     if a.ndim == 1 and b.ndim == 1:
         if isinstance(a, SparseArray):
-            a = asCOO(a)
+            a = as_coo(a)
         if isinstance(b, SparseArray):
-            b = asCOO(b)
+            b = as_coo(b)
         return (a * b).sum()
 
     a_axis = -1
@@ -1182,7 +1190,7 @@ def _parse_einsum_input(operands):
         for s in subscripts:
             if s in ".,->":
                 continue
-            if s not in np.core.einsumfunc.einsum_symbols:
+            if not s.isalpha():
                 raise ValueError(f"Character {s} is not a valid symbol.")
 
     else:
@@ -1206,7 +1214,7 @@ def _parse_einsum_input(operands):
                         s = index(s)
                     except TypeError as e:
                         raise TypeError("For this input type lists must contain either int or Ellipsis") from e
-                    subscripts += np.core.einsumfunc.einsum_symbols[s]
+                    subscripts += _EINSUM_SYMBOLS[s]
             if num != last:
                 subscripts += ","
 
@@ -1220,7 +1228,7 @@ def _parse_einsum_input(operands):
                         s = index(s)
                     except TypeError as e:
                         raise TypeError("For this input type lists must contain either int or Ellipsis") from e
-                    subscripts += np.core.einsumfunc.einsum_symbols[s]
+                    subscripts += _EINSUM_SYMBOLS[s]
     # Check for proper "->"
     if ("-" in subscripts) or (">" in subscripts):
         invalid = (subscripts.count("-") > 1) or (subscripts.count(">") > 1)
@@ -1230,7 +1238,7 @@ def _parse_einsum_input(operands):
     # Parse ellipses
     if "." in subscripts:
         used = subscripts.replace(".", "").replace(",", "").replace("->", "")
-        unused = list(np.core.einsumfunc.einsum_symbols_set - set(used))
+        unused = list(_EINSUM_SYMBOLS_SET - set(used))
         ellipse_inds = "".join(unused)
         longest = 0
 
@@ -1275,7 +1283,7 @@ def _parse_einsum_input(operands):
             output_subscript = ""
             tmp_subscripts = subscripts.replace(",", "")
             for s in sorted(set(tmp_subscripts)):
-                if s not in (np.core.einsumfunc.einsum_symbols):
+                if not s.isalpha():
                     raise ValueError(f"Character {s} is not a valid symbol.")
                 if tmp_subscripts.count(s) == 1:
                     output_subscript += s
@@ -1292,7 +1300,7 @@ def _parse_einsum_input(operands):
         tmp_subscripts = subscripts.replace(",", "")
         output_subscript = ""
         for s in sorted(set(tmp_subscripts)):
-            if s not in np.core.einsumfunc.einsum_symbols:
+            if not s.isalpha():
                 raise ValueError(f"Character {s} is not a valid symbol.")
             if tmp_subscripts.count(s) == 1:
                 output_subscript += s
@@ -1340,7 +1348,7 @@ def _einsum_single(lhs, rhs, operand):
 
     # else require COO for operations, but check if should convert back
     to_output_format = getattr(operand, "from_coo", lambda x: x)
-    operand = asCOO(operand)
+    operand = as_coo(operand)
 
     # check if repeated / 'trace' indices mean we are only taking a subset
     where = {}
diff --git a/sparse/numba_backend/_coo/core.py b/sparse/numba_backend/_coo/core.py
index 0a5baef..2b4b5a8 100644
--- a/sparse/numba_backend/_coo/core.py
+++ b/sparse/numba_backend/_coo/core.py
@@ -207,17 +207,18 @@ class COO(SparseArray, NDArrayOperatorsMixin):  # lgtm [py/missing-equals]
         fill_value=None,
         idx_dtype=None,
     ):
+        if isinstance(coords, COO):
+            self._make_shallow_copy_of(coords)
+            if data is not None or shape is not None:
+                raise ValueError("If `coords` is `COO`, then no other arguments should be provided.")
+            if fill_value is not None:
+                self.fill_value = self.data.dtype.type(fill_value)
+            return
+
         self._cache = None
         if cache:
             self.enable_caching()
 
-        if not isinstance(coords, np.ndarray):
-            warnings.warn(
-                "coords should be an ndarray. This will raise a ValueError in the future.",
-                DeprecationWarning,
-                stacklevel=1,
-            )
-
         if data is None:
             arr = as_coo(coords, shape=shape, fill_value=fill_value, idx_dtype=idx_dtype)
             self._make_shallow_copy_of(arr)
@@ -238,15 +239,10 @@ class COO(SparseArray, NDArrayOperatorsMixin):  # lgtm [py/missing-equals]
             self.data = np.broadcast_to(self.data, self.coords.shape[1])
 
         if self.data.ndim != 1:
-            raise ValueError("data must be a scalar or 1-dimensional.")
+            raise ValueError("`data` must be a scalar or 1-dimensional.")
 
         if shape is None:
-            warnings.warn(
-                "shape should be provided. This will raise a ValueError in the future.",
-                DeprecationWarning,
-                stacklevel=1,
-            )
-            shape = tuple(self.coords.max(axis=1) + 1) if self.coords.nbytes else ()
+            raise ValueError("`shape` was not provided.")
 
         if not isinstance(shape, Iterable):
             shape = (shape,)
@@ -256,7 +252,6 @@ class COO(SparseArray, NDArrayOperatorsMixin):  # lgtm [py/missing-equals]
 
         if shape and not self.coords.size:
             self.coords = np.zeros((len(shape) if isinstance(shape, Iterable) else 1, 0), dtype=np.intp)
-
         super().__init__(shape, fill_value=fill_value)
         if idx_dtype:
             if not can_store(idx_dtype, max(shape)):
@@ -417,11 +412,12 @@ class COO(SparseArray, NDArrayOperatorsMixin):  # lgtm [py/missing-equals]
         coords = tuple([self.coords[i, :] for i in range(self.ndim)])
         data = self.data
 
-        if coords != ():
+        if len(coords) != 0:
             x[coords] = data
         else:
             if len(data) != 0:
-                x[coords] = data
+                assert data.shape == (1,)
+                x[...] = data[0]
 
         return x
 
@@ -1157,52 +1153,6 @@ class COO(SparseArray, NDArrayOperatorsMixin):  # lgtm [py/missing-equals]
             fill_value=self.fill_value,
         )
 
-    def resize(self, *args, refcheck=True, coords_dtype=np.intp):
-        """
-        This method changes the shape and size of an array in-place.
-        Parameters
-        ----------
-        args : tuple, or series of integers
-            The desired shape of the output array.
-
-        See Also
-        --------
-        [`numpy.ndarray.resize`][] : The equivalent Numpy function.
-
-        """
-        warnings.warn("resize is deprecated on all SpraseArray objects.", DeprecationWarning, stacklevel=1)
-        if len(args) == 1 and isinstance(args[0], tuple):
-            shape = args[0]
-        elif all(isinstance(arg, int) for arg in args):
-            shape = tuple(args)
-        else:
-            raise ValueError("Invalid input")
-
-        if any(d < 0 for d in shape):
-            raise ValueError("negative dimensions not allowed")
-
-        new_size = reduce(operator.mul, shape, 1)
-
-        # TODO: this self.size enforces a 2**64 limit to array size
-        linear_loc = self.linear_loc()
-        end_idx = np.searchsorted(linear_loc, new_size, side="left")
-        linear_loc = linear_loc[:end_idx]
-
-        idx_dtype = self.coords.dtype
-        if shape != () and not can_store(idx_dtype, max(shape)):
-            idx_dtype = np.min_scalar_type(max(shape))
-        coords = np.empty((len(shape), len(linear_loc)), dtype=idx_dtype)
-        strides = 1
-        for i, d in enumerate(shape[::-1]):
-            coords[-(i + 1), :] = (linear_loc // strides) % d
-            strides *= d
-
-        self.shape = shape
-        self.coords = coords
-
-        if len(self.data) != len(linear_loc):
-            self.data = self.data[:end_idx].copy()
-
     def to_scipy_sparse(self, /, *, accept_fv=None):
         """
         Converts this [`sparse.COO`][] object into a [`scipy.sparse.coo_matrix`][].
diff --git a/sparse/numba_backend/_sparse_array.py b/sparse/numba_backend/_sparse_array.py
index b402f22..7f47c6e 100644
--- a/sparse/numba_backend/_sparse_array.py
+++ b/sparse/numba_backend/_sparse_array.py
@@ -170,7 +170,9 @@ class SparseArray:
         >>> s.density
         0.125
         """
-        return self.nnz / self.size
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore", category=RuntimeWarning)
+            return float(np.float64(self.nnz) / np.float64(self.size))
 
     def _repr_html_(self):
         """
diff --git a/sparse/numba_backend/_utils.py b/sparse/numba_backend/_utils.py
index 08b24b8..554cb25 100644
--- a/sparse/numba_backend/_utils.py
+++ b/sparse/numba_backend/_utils.py
@@ -1,8 +1,6 @@
 import functools
-import operator
 import warnings
 from collections.abc import Iterable
-from functools import reduce
 from numbers import Integral
 
 import numba
@@ -476,14 +474,12 @@ def html_table(arr):
     table = ["<table><tbody>"]
     headings = ["Format", "Data Type", "Shape", "nnz", "Density", "Read-only"]
 
-    density = np.float64(arr.nnz) / np.float64(arr.size)
-
     info = [
         type(arr).__name__.lower(),
         str(arr.dtype),
         str(arr.shape),
         str(arr.nnz),
-        str(density),
+        str(arr.density),
     ]
 
     # read-only
@@ -493,9 +489,10 @@ def html_table(arr):
         headings.append("Size")
         info.append(human_readable_size(arr.nbytes))
         headings.append("Storage ratio")
-        info.append(
-            f"{np.float64(arr.nbytes) / np.float64(reduce(operator.mul, arr.shape, 1) * arr.dtype.itemsize):.2f}"
-        )
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", category=RuntimeWarning)
+            ratio = float(np.float64(arr.nbytes) / np.float64(arr.size * arr.dtype.itemsize))
+        info.append(f"{ratio:.2f}")
 
     # compressed_axes
     if type(arr).__name__ == "GCXS":


Test patch:
diff --git a/sparse/numba_backend/tests/test_coo.py b/sparse/numba_backend/tests/test_coo.py
index ed1ef96..c929fff 100644
--- a/sparse/numba_backend/tests/test_coo.py
+++ b/sparse/numba_backend/tests/test_coo.py
@@ -207,40 +207,6 @@ def test_transpose_error(axis):
         x.transpose(axis)
 
 
-@pytest.mark.parametrize(
-    "a,b",
-    [
-        [(3, 4), (5, 5)],
-        [(12,), (3, 4)],
-        [(12,), (3, 6)],
-        [(5, 5, 5), (6, 6, 6)],
-        [(3, 4), (9, 4)],
-        [(5,), (4,)],
-        [(2, 3, 4, 5), (2, 3, 4, 5, 6)],
-        [(100,), (5, 5)],
-        [(2, 3, 4, 5), (20, 6)],
-        [(), ()],
-    ],
-)
-def test_resize(a, b):
-    s = sparse.random(a, density=0.5)
-    orig_size = s.size
-    x = s.todense()
-
-    x = np.resize(x, b)
-    s.resize(b)
-    temp = x.reshape(x.size)
-    temp[orig_size:] = s.fill_value
-    assert isinstance(s, sparse.SparseArray)
-    assert_eq(x, s)
-
-
-def test_resize_upcast():
-    s = sparse.random((10, 10, 10), density=0.5, format="coo", idx_dtype=np.uint8)
-    s.resize(600)
-    assert s.coords.dtype == np.uint16
-
-
 @pytest.mark.parametrize("axis1", [-3, -2, -1, 0, 1, 2])
 @pytest.mark.parametrize("axis2", [-3, -2, -1, 0, 1, 2])
 def test_swapaxes(axis1, axis2):
@@ -289,33 +255,6 @@ def test_moveaxis_error(source, destination):
         sparse.moveaxis(x, source, destination)
 
 
-@pytest.mark.parametrize(
-    "a,b",
-    [
-        [(3, 4), (5, 5)],
-        [(12,), (3, 4)],
-        [(12,), (3, 6)],
-        [(5, 5, 5), (6, 6, 6)],
-        [(3, 4), (9, 4)],
-        [(5,), (4,)],
-        [(2, 3, 4, 5), (2, 3, 4, 5, 6)],
-        [(100,), (5, 5)],
-        [(2, 3, 4, 5), (20, 6)],
-        [(), ()],
-    ],
-)
-def test_resize_2(a, b):
-    s = sparse.random(a, density=0.5)
-    orig_size = s.size
-    x = s.todense()
-    x = np.resize(x, b)
-    s.resize(b)
-    temp = x.reshape(x.size)
-    temp[orig_size:] = s.fill_value
-    assert isinstance(s, sparse.SparseArray)
-    assert_eq(x, s)
-
-
 @pytest.mark.parametrize(
     "a,b",
     [
@@ -342,12 +281,11 @@ def test_reshape(a, b, format):
 def test_large_reshape():
     n = 100
     m = 10
-    row = np.arange(n, dtype=np.uint16)  # np.random.randint(0, n, size=n, dtype=np.uint16)
-    col = row % m  # np.random.randint(0, m, size=n, dtype=np.uint16)
+    row = np.arange(n, dtype=np.uint16)
+    col = row % m
     data = np.ones(n, dtype=np.uint8)
 
-    x = COO((data, (row, col)), sorted=True, has_duplicates=False)
-
+    x = COO((data, (row, col)), shape=(100, 10), sorted=True, has_duplicates=False)
     assert_eq(x, x.reshape(x.shape))
 
 
@@ -682,7 +620,7 @@ def test_scalar_exponentiation():
 def test_create_with_lists_of_tuples():
     L = [((0, 0, 0), 1), ((1, 2, 1), 1), ((1, 1, 1), 2), ((1, 3, 2), 3)]
 
-    s = COO(L)
+    s = COO(L, shape=(2, 4, 3))
 
     x = np.zeros((2, 4, 3), dtype=np.asarray([1, 2, 3]).dtype)
     for ind, value in L:
@@ -708,8 +646,8 @@ def test_scipy_sparse_interface(rng):
 
     inp = (data, (row, col))
 
-    x = scipy.sparse.coo_matrix(inp)
-    xx = sparse.COO(inp)
+    x = scipy.sparse.coo_matrix(inp, shape=(n, m))
+    xx = sparse.COO(inp, shape=(n, m))
 
     assert_eq(x, xx, check_nnz=False)
     assert_eq(x.T, xx.T, check_nnz=False)
@@ -789,15 +727,8 @@ def test_cache_csr():
     assert s.tocsc() is s.tocsc()
 
 
-def test_empty_shape():
-    x = COO(np.empty((0, 1), dtype=np.int8), [1.0])
-    assert x.shape == ()
-    assert_eq(2 * x, np.float64(2.0))
-
-
 def test_single_dimension():
-    x = COO([1, 3], [1.0, 3.0])
-    assert x.shape == (4,)
+    x = COO([1, 3], [1.0, 3.0], shape=(4,))
     assert_eq(x, np.array([0, 1.0, 0, 3.0]))
 
 
@@ -809,27 +740,26 @@ def test_large_sum(rng):
 
     data = rng.random(n)
 
-    a = COO((x, y, z), data)
-    assert a.shape == (10000, 1000, 3)
+    a = COO((x, y, z), data, shape=(10000, 1000, 3))
 
     b = a.sum(axis=2)
     assert b.nnz > 100000
 
 
 def test_add_many_sparse_arrays():
-    x = COO({(1, 1): 1})
+    x = COO({(1, 1): 1}, shape=(2, 2))
     y = sum([x] * 100)
     assert y.nnz < np.prod(y.shape)
 
 
 def test_caching():
-    x = COO({(9, 9, 9): 1})
+    x = COO({(9, 9, 9): 1}, shape=(10, 10, 10))
     assert x[:].reshape((100, 10)).transpose().tocsr() is not x[:].reshape((100, 10)).transpose().tocsr()
 
-    x = COO({(9, 9, 9): 1}, cache=True)
+    x = COO({(9, 9, 9): 1}, shape=(10, 10, 10), cache=True)
     assert x[:].reshape((100, 10)).transpose().tocsr() is x[:].reshape((100, 10)).transpose().tocsr()
 
-    x = COO({(1, 1, 1, 1, 1, 1, 1, 2): 1}, cache=True)
+    x = COO({(1, 1, 1, 1, 1, 1, 1, 2): 1}, shape=(2, 2, 2, 2, 2, 2, 2, 3), cache=True)
 
     for _ in range(x.ndim):
         x.reshape(x.size)
@@ -1091,9 +1021,6 @@ def test_invalid_attrs_error():
     with pytest.raises(ValueError):
         sparse.as_coo(s, fill_value=0.0)
 
-    with pytest.raises(ValueError):
-        COO(s, fill_value=0.0)
-
 
 def test_invalid_iterable_error():
     with pytest.raises(ValueError):
@@ -1414,8 +1341,8 @@ def test_warn_on_too_dense():
 def test_prune_coo():
     coords = np.array([[0, 1, 2, 3]])
     data = np.array([1, 0, 1, 2])
-    s1 = COO(coords, data)
-    s2 = COO(coords, data, prune=True)
+    s1 = COO(coords, data, shape=(4,))
+    s2 = COO(coords, data, shape=(4,), prune=True)
     assert s2.nnz == 3
 
     # Densify s1 because it isn't canonical
diff --git a/sparse/tests/conftest.py b/sparse/tests/conftest.py
index 6ef1ca4..91d7e56 100644
--- a/sparse/tests/conftest.py
+++ b/sparse/tests/conftest.py
@@ -7,7 +7,7 @@ import numpy as np
 
 @pytest.fixture(scope="session")
 def backend():
-    yield sparse.BACKEND
+    yield sparse._BACKEND
 
 
 @pytest.fixture(scope="module")
diff --git a/sparse/tests/test_backends.py b/sparse/tests/test_backends.py
index 4c71275..c448362 100644
--- a/sparse/tests/test_backends.py
+++ b/sparse/tests/test_backends.py
@@ -1,3 +1,5 @@
+import warnings
+
 import sparse
 
 import pytest
@@ -15,7 +17,7 @@ def test_backends(backend):
     x = sparse.random((100, 10, 100), density=0.01, random_state=rng)
     y = sparse.random((100, 10, 100), density=0.01, random_state=rng)
 
-    if backend == sparse.BackendType.Finch:
+    if backend == sparse._BackendType.Finch:
         import finch
 
         def storage():
@@ -33,7 +35,7 @@ def test_backends(backend):
 
 
 def test_finch_lazy_backend(backend):
-    if backend != sparse.BackendType.Finch:
+    if backend != sparse._BackendType.Finch:
         pytest.skip("Tested only for Finch backend")
 
     import finch
@@ -88,7 +90,9 @@ def test_scipy_inv(backend, format, order):
     x = np.eye(10, order=order) * 2
     x_pydata = sparse.asarray(x, format=format)
 
-    actual = splin.inv(x_pydata)
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", category=sps.SparseEfficiencyWarning)
+        actual = splin.inv(x_pydata)
     expected = np.linalg.inv(x)
     assert_almost_equal(actual.todense(), expected)