Skip to content

Commit 889ac33

Browse files
authored
depr, chore: Enforce deprecations and clean up warnings. (#742)
1 parent 847023e commit 889ac33

File tree

9 files changed

+76
-185
lines changed

9 files changed

+76
-185
lines changed

examples/sparse_finch.ipynb

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"import os\n",
2525
"\n",
2626
"os.environ[\"SPARSE_BACKEND\"] = \"Finch\"\n",
27-
"CI_MODE = os.getenv(\"CI_MODE\", default=False)"
27+
"CI_MODE = bool(int(os.getenv(\"CI_MODE\", default=\"0\")))"
2828
]
2929
},
3030
{
@@ -42,9 +42,7 @@
4242
"\n",
4343
"import numpy as np\n",
4444
"import scipy.sparse as sps\n",
45-
"import scipy.sparse.linalg as splin\n",
46-
"\n",
47-
"assert sparse.BackendType.Finch == sparse.BACKEND"
45+
"import scipy.sparse.linalg as splin"
4846
]
4947
},
5048
{

sparse/__init__.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,37 @@
77
__array_api_version__ = "2022.12"
88

99

10-
class BackendType(Enum):
10+
class _BackendType(Enum):
1111
Numba = "Numba"
1212
Finch = "Finch"
1313

1414

1515
_ENV_VAR_NAME = "SPARSE_BACKEND"
1616

17+
18+
class SparseFutureWarning(FutureWarning):
19+
pass
20+
21+
1722
if os.environ.get(_ENV_VAR_NAME, "") != "":
1823
warnings.warn(
1924
"Changing back-ends is a development feature, please do not rely on it in production.",
20-
FutureWarning,
25+
SparseFutureWarning,
2126
stacklevel=1,
2227
)
2328
_backend_name = os.environ[_ENV_VAR_NAME]
2429
else:
25-
_backend_name = BackendType.Numba.value
30+
_backend_name = _BackendType.Numba.value
2631

27-
if _backend_name not in {BackendType.Numba.value, BackendType.Finch.value}:
32+
if _backend_name not in {v.value for v in _BackendType}:
2833
warnings.warn(f"Invalid backend identifier: {_backend_name}. Selecting Numba backend.", UserWarning, stacklevel=1)
29-
BACKEND = BackendType.Numba
34+
_BACKEND = _BackendType.Numba
3035
else:
31-
BACKEND = BackendType[_backend_name]
36+
_BACKEND = _BackendType[_backend_name]
3237

3338
del _backend_name
3439

35-
if BackendType.Finch == BACKEND:
40+
if _BackendType.Finch == _BACKEND:
3641
from sparse.finch_backend import * # noqa: F403
3742
from sparse.finch_backend import __all__
3843
else:

sparse/numba_backend/_common.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import numpy as np
1212

13-
from ._coo.common import asCOO
13+
from ._coo import as_coo
1414
from ._sparse_array import SparseArray
1515
from ._utils import (
1616
_zero_of_dtype,
@@ -19,6 +19,9 @@
1919
normalize_axis,
2020
)
2121

22+
_EINSUM_SYMBOLS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
23+
_EINSUM_SYMBOLS_SET = set(_EINSUM_SYMBOLS)
24+
2225

2326
def _is_scipy_sparse_obj(x):
2427
"""
@@ -189,7 +192,12 @@ def tensordot(a, b, axes=2, *, return_type=None):
189192
oldb = [bs[axis] for axis in notin]
190193

191194
if builtins.any(dim == 0 for dim in chain(newshape_a, newshape_b)):
192-
res = asCOO(np.empty(olda + oldb), check=False)
195+
from sparse import COO
196+
197+
dt = np.result_type(a.dtype, b.dtype)
198+
res = COO(
199+
np.empty((len(olda) + len(oldb), 0), dtype=np.uintp), data=np.empty(0, dtype=dt), shape=tuple(olda + oldb)
200+
)
193201
if isinstance(a, np.ndarray) or isinstance(b, np.ndarray):
194202
res = res.todense()
195203

@@ -309,9 +317,9 @@ def dot(a, b):
309317

310318
if a.ndim == 1 and b.ndim == 1:
311319
if isinstance(a, SparseArray):
312-
a = asCOO(a)
320+
a = as_coo(a)
313321
if isinstance(b, SparseArray):
314-
b = asCOO(b)
322+
b = as_coo(b)
315323
return (a * b).sum()
316324

317325
a_axis = -1
@@ -1182,7 +1190,7 @@ def _parse_einsum_input(operands):
11821190
for s in subscripts:
11831191
if s in ".,->":
11841192
continue
1185-
if s not in np.core.einsumfunc.einsum_symbols:
1193+
if not s.isalpha():
11861194
raise ValueError(f"Character {s} is not a valid symbol.")
11871195

11881196
else:
@@ -1206,7 +1214,7 @@ def _parse_einsum_input(operands):
12061214
s = index(s)
12071215
except TypeError as e:
12081216
raise TypeError("For this input type lists must contain either int or Ellipsis") from e
1209-
subscripts += np.core.einsumfunc.einsum_symbols[s]
1217+
subscripts += _EINSUM_SYMBOLS[s]
12101218
if num != last:
12111219
subscripts += ","
12121220

@@ -1220,7 +1228,7 @@ def _parse_einsum_input(operands):
12201228
s = index(s)
12211229
except TypeError as e:
12221230
raise TypeError("For this input type lists must contain either int or Ellipsis") from e
1223-
subscripts += np.core.einsumfunc.einsum_symbols[s]
1231+
subscripts += _EINSUM_SYMBOLS[s]
12241232
# Check for proper "->"
12251233
if ("-" in subscripts) or (">" in subscripts):
12261234
invalid = (subscripts.count("-") > 1) or (subscripts.count(">") > 1)
@@ -1230,7 +1238,7 @@ def _parse_einsum_input(operands):
12301238
# Parse ellipses
12311239
if "." in subscripts:
12321240
used = subscripts.replace(".", "").replace(",", "").replace("->", "")
1233-
unused = list(np.core.einsumfunc.einsum_symbols_set - set(used))
1241+
unused = list(_EINSUM_SYMBOLS_SET - set(used))
12341242
ellipse_inds = "".join(unused)
12351243
longest = 0
12361244

@@ -1275,7 +1283,7 @@ def _parse_einsum_input(operands):
12751283
output_subscript = ""
12761284
tmp_subscripts = subscripts.replace(",", "")
12771285
for s in sorted(set(tmp_subscripts)):
1278-
if s not in (np.core.einsumfunc.einsum_symbols):
1286+
if not s.isalpha():
12791287
raise ValueError(f"Character {s} is not a valid symbol.")
12801288
if tmp_subscripts.count(s) == 1:
12811289
output_subscript += s
@@ -1292,7 +1300,7 @@ def _parse_einsum_input(operands):
12921300
tmp_subscripts = subscripts.replace(",", "")
12931301
output_subscript = ""
12941302
for s in sorted(set(tmp_subscripts)):
1295-
if s not in np.core.einsumfunc.einsum_symbols:
1303+
if not s.isalpha():
12961304
raise ValueError(f"Character {s} is not a valid symbol.")
12971305
if tmp_subscripts.count(s) == 1:
12981306
output_subscript += s
@@ -1340,7 +1348,7 @@ def _einsum_single(lhs, rhs, operand):
13401348

13411349
# else require COO for operations, but check if should convert back
13421350
to_output_format = getattr(operand, "from_coo", lambda x: x)
1343-
operand = asCOO(operand)
1351+
operand = as_coo(operand)
13441352

13451353
# check if repeated / 'trace' indices mean we are only taking a subset
13461354
where = {}

sparse/numba_backend/_coo/core.py

Lines changed: 13 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -207,17 +207,18 @@ def __init__(
207207
fill_value=None,
208208
idx_dtype=None,
209209
):
210+
if isinstance(coords, COO):
211+
self._make_shallow_copy_of(coords)
212+
if data is not None or shape is not None:
213+
raise ValueError("If `coords` is `COO`, then no other arguments should be provided.")
214+
if fill_value is not None:
215+
self.fill_value = self.data.dtype.type(fill_value)
216+
return
217+
210218
self._cache = None
211219
if cache:
212220
self.enable_caching()
213221

214-
if not isinstance(coords, np.ndarray):
215-
warnings.warn(
216-
"coords should be an ndarray. This will raise a ValueError in the future.",
217-
DeprecationWarning,
218-
stacklevel=1,
219-
)
220-
221222
if data is None:
222223
arr = as_coo(coords, shape=shape, fill_value=fill_value, idx_dtype=idx_dtype)
223224
self._make_shallow_copy_of(arr)
@@ -238,15 +239,10 @@ def __init__(
238239
self.data = np.broadcast_to(self.data, self.coords.shape[1])
239240

240241
if self.data.ndim != 1:
241-
raise ValueError("data must be a scalar or 1-dimensional.")
242+
raise ValueError("`data` must be a scalar or 1-dimensional.")
242243

243244
if shape is None:
244-
warnings.warn(
245-
"shape should be provided. This will raise a ValueError in the future.",
246-
DeprecationWarning,
247-
stacklevel=1,
248-
)
249-
shape = tuple(self.coords.max(axis=1) + 1) if self.coords.nbytes else ()
245+
raise ValueError("`shape` was not provided.")
250246

251247
if not isinstance(shape, Iterable):
252248
shape = (shape,)
@@ -256,7 +252,6 @@ def __init__(
256252

257253
if shape and not self.coords.size:
258254
self.coords = np.zeros((len(shape) if isinstance(shape, Iterable) else 1, 0), dtype=np.intp)
259-
260255
super().__init__(shape, fill_value=fill_value)
261256
if idx_dtype:
262257
if not can_store(idx_dtype, max(shape)):
@@ -417,11 +412,12 @@ def todense(self):
417412
coords = tuple([self.coords[i, :] for i in range(self.ndim)])
418413
data = self.data
419414

420-
if coords != ():
415+
if len(coords) != 0:
421416
x[coords] = data
422417
else:
423418
if len(data) != 0:
424-
x[coords] = data
419+
assert data.shape == (1,)
420+
x[...] = data[0]
425421

426422
return x
427423

@@ -1157,52 +1153,6 @@ def squeeze(self, axis=None):
11571153
fill_value=self.fill_value,
11581154
)
11591155

1160-
def resize(self, *args, refcheck=True, coords_dtype=np.intp):
1161-
"""
1162-
This method changes the shape and size of an array in-place.
1163-
Parameters
1164-
----------
1165-
args : tuple, or series of integers
1166-
The desired shape of the output array.
1167-
1168-
See Also
1169-
--------
1170-
[`numpy.ndarray.resize`][] : The equivalent Numpy function.
1171-
1172-
"""
1173-
warnings.warn("resize is deprecated on all SpraseArray objects.", DeprecationWarning, stacklevel=1)
1174-
if len(args) == 1 and isinstance(args[0], tuple):
1175-
shape = args[0]
1176-
elif all(isinstance(arg, int) for arg in args):
1177-
shape = tuple(args)
1178-
else:
1179-
raise ValueError("Invalid input")
1180-
1181-
if any(d < 0 for d in shape):
1182-
raise ValueError("negative dimensions not allowed")
1183-
1184-
new_size = reduce(operator.mul, shape, 1)
1185-
1186-
# TODO: this self.size enforces a 2**64 limit to array size
1187-
linear_loc = self.linear_loc()
1188-
end_idx = np.searchsorted(linear_loc, new_size, side="left")
1189-
linear_loc = linear_loc[:end_idx]
1190-
1191-
idx_dtype = self.coords.dtype
1192-
if shape != () and not can_store(idx_dtype, max(shape)):
1193-
idx_dtype = np.min_scalar_type(max(shape))
1194-
coords = np.empty((len(shape), len(linear_loc)), dtype=idx_dtype)
1195-
strides = 1
1196-
for i, d in enumerate(shape[::-1]):
1197-
coords[-(i + 1), :] = (linear_loc // strides) % d
1198-
strides *= d
1199-
1200-
self.shape = shape
1201-
self.coords = coords
1202-
1203-
if len(self.data) != len(linear_loc):
1204-
self.data = self.data[:end_idx].copy()
1205-
12061156
def to_scipy_sparse(self, /, *, accept_fv=None):
12071157
"""
12081158
Converts this [`sparse.COO`][] object into a [`scipy.sparse.coo_matrix`][].

sparse/numba_backend/_sparse_array.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,9 @@ def density(self):
170170
>>> s.density
171171
0.125
172172
"""
173-
return self.nnz / self.size
173+
with warnings.catch_warnings():
174+
warnings.filterwarnings("ignore", category=RuntimeWarning)
175+
return float(np.float64(self.nnz) / np.float64(self.size))
174176

175177
def _repr_html_(self):
176178
"""

sparse/numba_backend/_utils.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import functools
2-
import operator
32
import warnings
43
from collections.abc import Iterable
5-
from functools import reduce
64
from numbers import Integral
75

86
import numba
@@ -476,14 +474,12 @@ def html_table(arr):
476474
table = ["<table><tbody>"]
477475
headings = ["Format", "Data Type", "Shape", "nnz", "Density", "Read-only"]
478476

479-
density = np.float64(arr.nnz) / np.float64(arr.size)
480-
481477
info = [
482478
type(arr).__name__.lower(),
483479
str(arr.dtype),
484480
str(arr.shape),
485481
str(arr.nnz),
486-
str(density),
482+
str(arr.density),
487483
]
488484

489485
# read-only
@@ -493,9 +489,10 @@ def html_table(arr):
493489
headings.append("Size")
494490
info.append(human_readable_size(arr.nbytes))
495491
headings.append("Storage ratio")
496-
info.append(
497-
f"{np.float64(arr.nbytes) / np.float64(reduce(operator.mul, arr.shape, 1) * arr.dtype.itemsize):.2f}"
498-
)
492+
with warnings.catch_warnings():
493+
warnings.simplefilter("ignore", category=RuntimeWarning)
494+
ratio = float(np.float64(arr.nbytes) / np.float64(arr.size * arr.dtype.itemsize))
495+
info.append(f"{ratio:.2f}")
499496

500497
# compressed_axes
501498
if type(arr).__name__ == "GCXS":

0 commit comments

Comments
 (0)