Skip to content

Commit c57fed2

Browse files
authored
Make SciPy a soft dependency (#632)
* Make SciPy a soft dependency. * Fix packaging issue. * Add scipy to doc deps. * Add docs + __class__ -> __module__.
1 parent 16b46cd commit c57fed2

File tree

9 files changed

+51
-40
lines changed

9 files changed

+51
-40
lines changed

MANIFEST.in

Lines changed: 0 additions & 15 deletions
This file was deleted.

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ name = "sparse"
77
dynamic = ["version"]
88
description = "Sparse n-dimensional arrays for the PyData ecosystem"
99
readme = "README.rst"
10-
dependencies = ["numpy>=1.17", "scipy>=0.19", "numba>=0.49"]
10+
dependencies = ["numpy>=1.17", "numba>=0.49"]
1111
maintainers = [{ name = "Hameer Abbasi", email = "[email protected]" }]
1212
requires-python = ">=3.8"
1313
license = { file = "LICENSE" }
@@ -28,12 +28,13 @@ classifiers = [
2828
]
2929

3030
[project.optional-dependencies]
31-
docs = ["sphinx", "sphinx_rtd_theme"]
31+
docs = ["sphinx", "sphinx_rtd_theme", "scipy"]
3232
tests = [
3333
"dask[array]",
3434
"pytest>=3.5",
3535
"pytest-cov",
3636
"pre-commit",
37+
"scipy",
3738
]
3839
tox = ["sparse[tests]", "tox"]
3940
all = ["sparse[docs,tox]", "matrepr"]

sparse/_common.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
from numba import literal_unroll
1010

1111
import numpy as np
12-
import scipy.sparse
13-
from scipy.sparse import spmatrix
1412

1513
from ._coo.common import asCOO
1614
from ._sparse_array import SparseArray
@@ -22,6 +20,22 @@
2220
)
2321

2422

23+
def _is_scipy_sparse_obj(x):
24+
"""
25+
Tests if the supplied argument is a SciPy sparse object.
26+
"""
27+
if hasattr(x, "__module__") and x.__module__.startswith("scipy.sparse"):
28+
return True
29+
return False
30+
31+
32+
def _is_sparse(x):
33+
"""
34+
Tests if the supplied argument is a SciPy sparse object, or one from this library.
35+
"""
36+
return isinstance(x, SparseArray) or _is_scipy_sparse_obj(x)
37+
38+
2539
@numba.njit
2640
def nan_check(*args):
2741
"""
@@ -61,7 +75,7 @@ def check_class_nan(test):
6175

6276
if isinstance(test, (GCXS, COO)):
6377
return nan_check(test.fill_value, test.data)
64-
if isinstance(test, spmatrix):
78+
if _is_scipy_sparse_obj(test):
6579
return nan_check(test.data)
6680
return nan_check(test)
6781

@@ -99,9 +113,9 @@ def tensordot(a, b, axes=2, *, return_type=None):
99113
# Please see license at https://github.com/numpy/numpy/blob/main/LICENSE.txt
100114
check_zero_fill_value(a, b)
101115

102-
if scipy.sparse.issparse(a):
116+
if _is_scipy_sparse_obj(a):
103117
a = GCXS.from_scipy_sparse(a)
104-
if scipy.sparse.issparse(b):
118+
if _is_scipy_sparse_obj(b):
105119
b = GCXS.from_scipy_sparse(b)
106120

107121
try:
@@ -2047,6 +2061,7 @@ def asarray(obj, /, *, dtype=None, format="coo", backend="pydata", device=None,
20472061
>>> sparse.asarray(x, format="COO")
20482062
<COO: shape=(8, 8), dtype=int64, nnz=8, fill_value=0>
20492063
"""
2064+
20502065
if format not in {"coo", "dok", "gcxs"}:
20512066
raise ValueError(f"{format} format not supported.")
20522067

@@ -2065,7 +2080,7 @@ def asarray(obj, /, *, dtype=None, format="coo", backend="pydata", device=None,
20652080
if isinstance(obj, (COO, DOK, GCXS)):
20662081
return obj.asformat(format)
20672082

2068-
if isinstance(obj, spmatrix):
2083+
if _is_scipy_sparse_obj(obj):
20692084
sparse_obj = format_dict[format].from_scipy_sparse(obj)
20702085
if dtype is None:
20712086
dtype = sparse_obj.dtype

sparse/_compressed/compressed.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from functools import reduce
55

66
import numpy as np
7-
import scipy.sparse as ss
87
from numpy.lib.mixins import NDArrayOperatorsMixin
98

109
from .._coo.common import linear_loc
@@ -140,7 +139,9 @@ def __init__(
140139
fill_value=0,
141140
idx_dtype=None,
142141
):
143-
if isinstance(arg, ss.spmatrix):
142+
from .._common import _is_scipy_sparse_obj
143+
144+
if _is_scipy_sparse_obj(arg):
144145
arg = self.from_scipy_sparse(arg)
145146

146147
if isinstance(arg, np.ndarray):
@@ -482,16 +483,17 @@ def to_scipy_sparse(self):
482483
ValueError
483484
If all the array doesn't zero fill-values.
484485
"""
486+
import scipy.sparse
485487

486488
check_zero_fill_value(self)
487489

488490
if self.ndim != 2:
489491
raise ValueError("Can only convert a 2-dimensional array to a Scipy sparse matrix.")
490492

491493
if 0 in self.compressed_axes:
492-
return ss.csr_matrix((self.data, self.indices, self.indptr), shape=self.shape)
494+
return scipy.sparse.csr_matrix((self.data, self.indices, self.indptr), shape=self.shape)
493495

494-
return ss.csc_matrix((self.data, self.indices, self.indptr), shape=self.shape)
496+
return scipy.sparse.csc_matrix((self.data, self.indices, self.indptr), shape=self.shape)
495497

496498
def asformat(self, format, **kwargs):
497499
"""

sparse/_coo/common.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import numba
88

99
import numpy as np
10-
import scipy.sparse
1110

1211
from .._sparse_array import SparseArray
1312
from .._utils import (
@@ -43,9 +42,10 @@ def asCOO(x, name="asCOO", check=True):
4342
ValueError
4443
If ``check`` is true and a dense input is supplied.
4544
"""
45+
from .._common import _is_sparse
4646
from .core import COO
4747

48-
if check and not isinstance(x, (SparseArray, scipy.sparse.spmatrix)):
48+
if check and not _is_sparse(x):
4949
raise ValueError(f"Performing this operation would produce a dense result: {name}")
5050

5151
if not isinstance(x, COO):
@@ -94,13 +94,14 @@ def kron(a, b):
9494
[0, 0, 0, 1, 2, 3, 0, 0, 0],
9595
[0, 0, 0, 0, 0, 0, 1, 2, 3]], dtype=int64)
9696
"""
97+
from .._common import _is_sparse
9798
from .._umath import _cartesian_product
9899
from .core import COO
99100

100101
check_zero_fill_value(a, b)
101102

102-
a_sparse = isinstance(a, (SparseArray, scipy.sparse.spmatrix))
103-
b_sparse = isinstance(b, (SparseArray, scipy.sparse.spmatrix))
103+
a_sparse = _is_sparse(a)
104+
b_sparse = _is_sparse(b)
104105
a_ndim = np.ndim(a)
105106
b_ndim = np.ndim(b)
106107

@@ -1346,9 +1347,10 @@ def take(x, indices, /, *, axis=None):
13461347

13471348

13481349
def _validate_coo_input(x: Any):
1350+
from .._common import _is_scipy_sparse_obj
13491351
from .core import COO
13501352

1351-
if isinstance(x, scipy.sparse.spmatrix):
1353+
if _is_scipy_sparse_obj(x):
13521354
x = COO.from_scipy_sparse(x)
13531355
elif not isinstance(x, SparseArray):
13541356
raise ValueError(f"Input must be an instance of SparseArray, but it's {type(x)}.")

sparse/_coo/core.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import numba
99

1010
import numpy as np
11-
import scipy.sparse
1211
from numpy.lib.mixins import NDArrayOperatorsMixin
1312

1413
from .._sparse_array import SparseArray
@@ -1177,6 +1176,8 @@ def to_scipy_sparse(self):
11771176
COO.tocsr : Convert to a :obj:`scipy.sparse.csr_matrix`.
11781177
COO.tocsc : Convert to a :obj:`scipy.sparse.csc_matrix`.
11791178
"""
1179+
import scipy.sparse
1180+
11801181
check_zero_fill_value(self)
11811182

11821183
if self.ndim != 2:
@@ -1187,6 +1188,8 @@ def to_scipy_sparse(self):
11871188
return result
11881189

11891190
def _tocsr(self):
1191+
import scipy.sparse
1192+
11901193
if self.ndim != 2:
11911194
raise ValueError("This array must be two-dimensional for this conversion to work.")
11921195
row, col = self.coords
@@ -1557,6 +1560,8 @@ def as_coo(x, shape=None, fill_value=None, idx_dtype=None):
15571560
COO.from_iter :
15581561
Convert an iterable to :obj:`COO`.
15591562
"""
1563+
from .._common import _is_scipy_sparse_obj
1564+
15601565
if hasattr(x, "shape") and shape is not None:
15611566
raise ValueError("Cannot provide a shape in combination with something that already has a shape.")
15621567

@@ -1569,7 +1574,7 @@ def as_coo(x, shape=None, fill_value=None, idx_dtype=None):
15691574
if isinstance(x, np.ndarray) or np.isscalar(x):
15701575
return COO.from_numpy(x, fill_value=fill_value, idx_dtype=idx_dtype)
15711576

1572-
if isinstance(x, scipy.sparse.spmatrix):
1577+
if _is_scipy_sparse_obj(x):
15731578
return COO.from_scipy_sparse(x)
15741579

15751580
if isinstance(x, (Iterable, Iterator)):

sparse/_dok.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from numbers import Integral
33

44
import numpy as np
5-
import scipy.sparse
65
from numpy.lib.mixins import NDArrayOperatorsMixin
76

87
from ._slicing import normalize_index
@@ -92,6 +91,7 @@ class DOK(SparseArray, NDArrayOperatorsMixin):
9291
"""
9392

9493
def __init__(self, shape, data=None, dtype=None, fill_value=None):
94+
from ._common import _is_scipy_sparse_obj
9595
from ._coo import COO
9696

9797
self.data = {}
@@ -106,7 +106,7 @@ def __init__(self, shape, data=None, dtype=None, fill_value=None):
106106
self._make_shallow_copy_of(ar)
107107
return
108108

109-
if isinstance(shape, scipy.sparse.spmatrix):
109+
if _is_scipy_sparse_obj(shape):
110110
ar = DOK.from_scipy_sparse(shape)
111111
self._make_shallow_copy_of(ar)
112112
return

sparse/_sparse_array.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from typing import Callable
99

1010
import numpy as np
11-
import scipy.sparse as ss
1211

1312
from ._umath import elemwise
1413
from ._utils import _zero_of_dtype, equivalent, html_table, normalize_axis
@@ -298,10 +297,12 @@ def __array_function__(self, func, types, args, kwargs):
298297

299298
@staticmethod
300299
def _reduce(method, *args, **kwargs):
300+
from ._common import _is_scipy_sparse_obj
301+
301302
assert len(args) == 1
302303

303304
self = args[0]
304-
if isinstance(self, ss.spmatrix):
305+
if _is_scipy_sparse_obj(self):
305306
self = type(self).from_scipy_sparse(self)
306307

307308
return self.reduce(method, **kwargs)

sparse/_umath.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import numba
55

66
import numpy as np
7-
import scipy.sparse
87

98
from ._utils import _zero_of_dtype, equivalent, isscalar
109

@@ -399,6 +398,7 @@ def __init__(self, func, *args, **kwargs):
399398
**kwargs : dict
400399
Extra arguments to pass to the function.
401400
"""
401+
from ._common import _is_scipy_sparse_obj
402402
from ._compressed import GCXS
403403
from ._coo import COO
404404
from ._dok import DOK
@@ -422,7 +422,7 @@ def __init__(self, func, *args, **kwargs):
422422
out_type = COO
423423

424424
for arg in args:
425-
if isinstance(arg, scipy.sparse.spmatrix):
425+
if _is_scipy_sparse_obj(arg):
426426
processed_args.append(COO.from_scipy_sparse(arg))
427427
elif isscalar(arg) or isinstance(arg, np.ndarray):
428428
# Faster and more reliable to pass ()-shaped ndarrays as scalars.

0 commit comments

Comments
 (0)