Skip to content

Commit 81385df

Browse files
mdhaberdschult
andauthored
API: sparse: transition random-like functions to rng (SPEC 7) (scipy#21888)
--------- Co-authored-by: Dan Schult <[email protected]>
1 parent c75f588 commit 81385df

File tree

14 files changed

+117
-165
lines changed

14 files changed

+117
-165
lines changed

doc/source/reference/sparse.migration_to_sparray.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ Their signatures are::
103103
def block_array(blocks, format=None, dtype=None):
104104
def diags_array(diagonals, /, *, offsets=0, shape=None, format=None, dtype=None):
105105
def eye_array(m, n=None, *, k=0, dtype=float, format=None):
106-
def random_array(m, n, density=0.01, format='coo', dtype=None, random_state=None, data_random_state=None):
106+
def random_array(m, n, density=0.01, format='coo', dtype=None, rng=None, data_random_state=None):
107107

108108
Existing functions that need careful migration
109109
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

scipy/sparse/_construct.py

Lines changed: 53 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import math
1212
import numpy as np
1313

14-
from scipy._lib._util import check_random_state, rng_integers
14+
from scipy._lib._util import check_random_state, rng_integers, _transition_to_rng
1515
from ._sputils import upcast, get_index_dtype, isscalarlike
1616

1717
from ._sparsetools import csr_hstack
@@ -1093,22 +1093,14 @@ def block_diag(mats, format=None, dtype=None):
10931093
dtype=dtype).asformat(format)
10941094

10951095

1096+
@_transition_to_rng("random_state")
10961097
def random_array(shape, *, density=0.01, format='coo', dtype=None,
1097-
random_state=None, data_sampler=None):
1098+
rng=None, data_sampler=None):
10981099
"""Return a sparse array of uniformly random numbers in [0, 1)
10991100
11001101
Returns a sparse array with the given shape and density
11011102
where values are generated uniformly randomly in the range [0, 1).
11021103
1103-
.. warning::
1104-
1105-
Since numpy 1.17, passing a ``np.random.Generator`` (e.g.
1106-
``np.random.default_rng``) for ``random_state`` will lead to much
1107-
faster execution times.
1108-
1109-
A much slower implementation is used by default for backwards
1110-
compatibility.
1111-
11121104
Parameters
11131105
----------
11141106
shape : int or tuple of ints
@@ -1120,21 +1112,14 @@ def random_array(shape, *, density=0.01, format='coo', dtype=None,
11201112
sparse matrix format.
11211113
dtype : dtype, optional (default: np.float64)
11221114
type of the returned matrix values.
1123-
random_state : {None, int, `Generator`, `RandomState`}, optional
1124-
A random number generator to determine nonzero structure. We recommend using
1125-
a `numpy.random.Generator` manually provided for every call as it is much
1126-
faster than RandomState.
1127-
1128-
- If `None` (or `np.random`), the `numpy.random.RandomState`
1129-
singleton is used.
1130-
- If an int, a new ``Generator`` instance is used,
1131-
seeded with the int.
1132-
- If a ``Generator`` or ``RandomState`` instance then
1133-
that instance is used.
1115+
rng : `numpy.random.Generator`, optional
1116+
Pseudorandom number generator state. When `rng` is None, a new
1117+
`numpy.random.Generator` is created using entropy from the
1118+
operating system. Types other than `numpy.random.Generator` are
1119+
passed to `numpy.random.default_rng` to instantiate a ``Generator``.
11341120
11351121
This random state will be used for sampling `indices` (the sparsity
11361122
structure), and by default for the data values too (see `data_sampler`).
1137-
11381123
data_sampler : callable, optional (default depends on dtype)
11391124
Sampler of random data values with keyword arg `size`.
11401125
This function should take a single keyword argument `size` specifying
@@ -1143,7 +1128,7 @@ def random_array(shape, *, density=0.01, format='coo', dtype=None,
11431128
By default, uniform [0, 1) random values are used unless `dtype` is
11441129
an integer (default uniform integers from that dtype) or
11451130
complex (default uniform over the unit square in the complex plane).
1146-
For these, the `random_state` rng is used e.g. ``rng.uniform(size=size)``.
1131+
For these, the `rng` is used e.g. ``rng.uniform(size=size)``.
11471132
11481133
Returns
11491134
-------
@@ -1160,13 +1145,13 @@ def random_array(shape, *, density=0.01, format='coo', dtype=None,
11601145
11611146
Default sampling uniformly from [0, 1):
11621147
1163-
>>> S = sp.sparse.random_array((3, 4), density=0.25, random_state=rng)
1148+
>>> S = sp.sparse.random_array((3, 4), density=0.25, rng=rng)
11641149
11651150
Providing a sampler for the values:
11661151
11671152
>>> rvs = sp.stats.poisson(25, loc=10).rvs
11681153
>>> S = sp.sparse.random_array((3, 4), density=0.25,
1169-
... random_state=rng, data_sampler=rvs)
1154+
... rng=rng, data_sampler=rvs)
11701155
>>> S.toarray()
11711156
array([[ 36., 0., 33., 0.], # random
11721157
[ 0., 0., 0., 0.],
@@ -1175,38 +1160,38 @@ def random_array(shape, *, density=0.01, format='coo', dtype=None,
11751160
Building a custom distribution.
11761161
This example builds a squared normal from np.random:
11771162
1178-
>>> def np_normal_squared(size=None, random_state=rng):
1179-
... return random_state.standard_normal(size) ** 2
1180-
>>> S = sp.sparse.random_array((3, 4), density=0.25, random_state=rng,
1181-
... data_sampler=np_normal_squared)
1163+
>>> def np_normal_squared(size=None, rng=rng):
1164+
... return rng.standard_normal(size) ** 2
1165+
>>> S = sp.sparse.random_array((3, 4), density=0.25, rng=rng,
1166+
... data_sampler=np_normal_squared)
11821167
11831168
Or we can build it from sp.stats style rvs functions:
11841169
1185-
>>> def sp_stats_normal_squared(size=None, random_state=rng):
1170+
>>> def sp_stats_normal_squared(size=None, rng=rng):
11861171
... std_normal = sp.stats.distributions.norm_gen().rvs
1187-
... return std_normal(size=size, random_state=random_state) ** 2
1188-
>>> S = sp.sparse.random_array((3, 4), density=0.25, random_state=rng,
1189-
... data_sampler=sp_stats_normal_squared)
1172+
... return std_normal(size=size, random_state=rng) ** 2
1173+
>>> S = sp.sparse.random_array((3, 4), density=0.25, rng=rng,
1174+
... data_sampler=sp_stats_normal_squared)
11901175
11911176
Or we can subclass sp.stats rv_continuous or rv_discrete:
11921177
11931178
>>> class NormalSquared(sp.stats.rv_continuous):
11941179
... def _rvs(self, size=None, random_state=rng):
1195-
... return random_state.standard_normal(size) ** 2
1180+
... return rng.standard_normal(size) ** 2
11961181
>>> X = NormalSquared()
11971182
>>> Y = X().rvs
11981183
>>> S = sp.sparse.random_array((3, 4), density=0.25,
1199-
... random_state=rng, data_sampler=Y)
1184+
... rng=rng, data_sampler=Y)
12001185
"""
12011186
# Use the more efficient RNG by default.
1202-
if random_state is None:
1203-
random_state = np.random.default_rng()
1204-
data, ind = _random(shape, density, format, dtype, random_state, data_sampler)
1187+
if rng is None:
1188+
rng = np.random.default_rng()
1189+
data, ind = _random(shape, density, format, dtype, rng, data_sampler)
12051190
return coo_array((data, ind), shape=shape).asformat(format)
12061191

12071192

12081193
def _random(shape, density=0.01, format=None, dtype=None,
1209-
random_state=None, data_sampler=None):
1194+
rng=None, data_sampler=None):
12101195
if density < 0 or density > 1:
12111196
raise ValueError("density expected to be 0 <= density <= 1")
12121197

@@ -1215,7 +1200,7 @@ def _random(shape, density=0.01, format=None, dtype=None,
12151200
# Number of non zero values
12161201
size = int(round(density * tot_prod))
12171202

1218-
rng = check_random_state(random_state)
1203+
rng = check_random_state(rng)
12191204

12201205
if data_sampler is None:
12211206
if np.issubdtype(dtype, np.integer):
@@ -1250,20 +1235,12 @@ def data_sampler(size):
12501235
return vals, ind
12511236

12521237

1238+
@_transition_to_rng("random_state", position_num=5)
12531239
def random(m, n, density=0.01, format='coo', dtype=None,
1254-
random_state=None, data_rvs=None):
1240+
rng=None, data_rvs=None):
12551241
"""Generate a sparse matrix of the given shape and density with randomly
12561242
distributed values.
12571243
1258-
.. warning::
1259-
1260-
Since numpy 1.17, passing a ``np.random.Generator`` (e.g.
1261-
``np.random.default_rng``) for ``random_state`` will lead to much
1262-
faster execution times.
1263-
1264-
A much slower implementation is used by default for backwards
1265-
compatibility.
1266-
12671244
.. warning::
12681245
12691246
This function returns a sparse matrix -- not a sparse array.
@@ -1281,15 +1258,11 @@ def random(m, n, density=0.01, format='coo', dtype=None,
12811258
sparse matrix format.
12821259
dtype : dtype, optional
12831260
type of the returned matrix values.
1284-
random_state : {None, int, `numpy.random.Generator`,
1285-
`numpy.random.RandomState`}, optional
1286-
1287-
- If `seed` is None (or `np.random`), the `numpy.random.RandomState`
1288-
singleton is used.
1289-
- If `seed` is an int, a new ``RandomState`` instance is used,
1290-
seeded with `seed`.
1291-
- If `seed` is already a ``Generator`` or ``RandomState`` instance then
1292-
that instance is used.
1261+
rng : `numpy.random.Generator`, optional
1262+
Pseudorandom number generator state. When `rng` is None, a new
1263+
`numpy.random.Generator` is created using entropy from the
1264+
operating system. Types other than `numpy.random.Generator` are
1265+
passed to `numpy.random.default_rng` to instantiate a ``Generator``.
12931266
12941267
This random state will be used for sampling the sparsity structure, but
12951268
not necessarily for sampling the values of the structurally nonzero
@@ -1319,12 +1292,12 @@ def random(m, n, density=0.01, format='coo', dtype=None,
13191292
>>> import scipy as sp
13201293
>>> import numpy as np
13211294
>>> rng = np.random.default_rng()
1322-
>>> S = sp.sparse.random(3, 4, density=0.25, random_state=rng)
1295+
>>> S = sp.sparse.random(3, 4, density=0.25, rng=rng)
13231296
13241297
Providing a sampler for the values:
13251298
13261299
>>> rvs = sp.stats.poisson(25, loc=10).rvs
1327-
>>> S = sp.sparse.random(3, 4, density=0.25, random_state=rng, data_rvs=rvs)
1300+
>>> S = sp.sparse.random(3, 4, density=0.25, rng=rng, data_rvs=rvs)
13281301
>>> S.toarray()
13291302
array([[ 36., 0., 33., 0.], # random
13301303
[ 0., 0., 0., 0.],
@@ -1333,27 +1306,27 @@ def random(m, n, density=0.01, format='coo', dtype=None,
13331306
Building a custom distribution.
13341307
This example builds a squared normal from np.random:
13351308
1336-
>>> def np_normal_squared(size=None, random_state=rng):
1337-
... return random_state.standard_normal(size) ** 2
1338-
>>> S = sp.sparse.random(3, 4, density=0.25, random_state=rng,
1309+
>>> def np_normal_squared(size=None, rng=rng):
1310+
... return rng.standard_normal(size) ** 2
1311+
>>> S = sp.sparse.random(3, 4, density=0.25, rng=rng,
13391312
... data_rvs=np_normal_squared)
13401313
13411314
Or we can build it from sp.stats style rvs functions:
13421315
1343-
>>> def sp_stats_normal_squared(size=None, random_state=rng):
1316+
>>> def sp_stats_normal_squared(size=None, rng=rng):
13441317
... std_normal = sp.stats.distributions.norm_gen().rvs
1345-
... return std_normal(size=size, random_state=random_state) ** 2
1346-
>>> S = sp.sparse.random(3, 4, density=0.25, random_state=rng,
1318+
... return std_normal(size=size, random_state=rng) ** 2
1319+
>>> S = sp.sparse.random(3, 4, density=0.25, rng=rng,
13471320
... data_rvs=sp_stats_normal_squared)
13481321
13491322
Or we can subclass sp.stats rv_continuous or rv_discrete:
13501323
13511324
>>> class NormalSquared(sp.stats.rv_continuous):
13521325
... def _rvs(self, size=None, random_state=rng):
1353-
... return random_state.standard_normal(size) ** 2
1326+
... return rng.standard_normal(size) ** 2
13541327
>>> X = NormalSquared()
13551328
>>> Y = X() # get a frozen version of the distribution
1356-
>>> S = sp.sparse.random(3, 4, density=0.25, random_state=rng, data_rvs=Y.rvs)
1329+
>>> S = sp.sparse.random(3, 4, density=0.25, rng=rng, data_rvs=Y.rvs)
13571330
"""
13581331
if n is None:
13591332
n = m
@@ -1364,11 +1337,12 @@ def data_rvs_kw(size):
13641337
return data_rvs(size)
13651338
else:
13661339
data_rvs_kw = None
1367-
vals, ind = _random((m, n), density, format, dtype, random_state, data_rvs_kw)
1340+
vals, ind = _random((m, n), density, format, dtype, rng, data_rvs_kw)
13681341
return coo_matrix((vals, ind), shape=(m, n)).asformat(format)
13691342

13701343

1371-
def rand(m, n, density=0.01, format="coo", dtype=None, random_state=None):
1344+
@_transition_to_rng("random_state", position_num=5)
1345+
def rand(m, n, density=0.01, format="coo", dtype=None, rng=None):
13721346
"""Generate a sparse matrix of the given shape and density with uniformly
13731347
distributed values.
13741348
@@ -1389,15 +1363,11 @@ def rand(m, n, density=0.01, format="coo", dtype=None, random_state=None):
13891363
sparse matrix format.
13901364
dtype : dtype, optional
13911365
type of the returned matrix values.
1392-
random_state : {None, int, `numpy.random.Generator`,
1393-
`numpy.random.RandomState`}, optional
1394-
1395-
If `seed` is None (or `np.random`), the `numpy.random.RandomState`
1396-
singleton is used.
1397-
If `seed` is an int, a new ``RandomState`` instance is used,
1398-
seeded with `seed`.
1399-
If `seed` is already a ``Generator`` or ``RandomState`` instance then
1400-
that instance is used.
1366+
rng : `numpy.random.Generator`, optional
1367+
Pseudorandom number generator state. When `rng` is None, a new
1368+
`numpy.random.Generator` is created using entropy from the
1369+
operating system. Types other than `numpy.random.Generator` are
1370+
passed to `numpy.random.default_rng` to instantiate a ``Generator``.
14011371
14021372
Returns
14031373
-------
@@ -1415,7 +1385,7 @@ def rand(m, n, density=0.01, format="coo", dtype=None, random_state=None):
14151385
Examples
14161386
--------
14171387
>>> from scipy.sparse import rand
1418-
>>> matrix = rand(3, 4, density=0.25, format="csr", random_state=42)
1388+
>>> matrix = rand(3, 4, density=0.25, format="csr", rng=42)
14191389
>>> matrix
14201390
<Compressed Sparse Row sparse matrix of dtype 'float64'
14211391
with 3 stored elements and shape (3, 4)>
@@ -1425,4 +1395,4 @@ def rand(m, n, density=0.01, format="coo", dtype=None, random_state=None):
14251395
[0. , 0. , 0. , 0. ]])
14261396
14271397
"""
1428-
return random(m, n, density, format, dtype, random_state)
1398+
return random(m, n, density, format, dtype, rng)

scipy/sparse/csgraph/_laplacian.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ def laplacian(
289289
Fix a random seed ``rng`` and add a random sparse noise to the graph ``G``:
290290
291291
>>> rng = np.random.default_rng()
292-
>>> G += 1e-2 * random_array((N, N), density=0.1, random_state=rng)
292+
>>> G += 1e-2 * random_array((N, N), density=0.1, rng=rng)
293293
294294
Set initial approximations for eigenvectors:
295295

scipy/sparse/csgraph/_matching.pyx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -440,18 +440,18 @@ def min_weight_full_bipartite_matching(biadjacency, maximize=False):
440440
>>> import numpy as np
441441
>>> from scipy.sparse import random_array
442442
>>> from scipy.optimize import linear_sum_assignment
443-
>>> sparse = random_array((10, 10), random_state=42, density=.5, format='coo') * 10
443+
>>> sparse = random_array((10, 10), rng=42, density=.5, format='coo') * 10
444444
>>> sparse.data = np.ceil(sparse.data)
445445
>>> dense = sparse.toarray()
446446
>>> dense = np.full(sparse.shape, np.inf)
447447
>>> dense[sparse.row, sparse.col] = sparse.data
448448
>>> sparse = sparse.tocsr()
449449
>>> row_ind, col_ind = linear_sum_assignment(dense)
450450
>>> print(dense[row_ind, col_ind].sum())
451-
28.0
451+
25.0
452452
>>> row_ind, col_ind = min_weight_full_bipartite_matching(sparse)
453453
>>> print(sparse[row_ind, col_ind].sum())
454-
28.0
454+
25.0
455455
456456
"""
457457
biadjacency = convert_pydata_sparse_to_scipy(biadjacency)

scipy/sparse/csgraph/tests/test_shortest_path.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,13 @@ def test_dijkstra_indices_min_only(directed, SP_ans, indices):
196196

197197
@pytest.mark.parametrize('n', (10, 100, 1000))
198198
def test_dijkstra_min_only_random(n):
199-
np.random.seed(1234)
199+
rng = np.random.default_rng(7345782358920239234)
200200
data = scipy.sparse.random_array((n, n), density=0.5, format='lil',
201-
random_state=42, dtype=np.float64)
201+
rng=rng, dtype=np.float64)
202202
data.setdiag(np.zeros(n, dtype=np.bool_))
203203
# choose some random vertices
204204
v = np.arange(n)
205-
np.random.shuffle(v)
205+
rng.shuffle(v)
206206
indices = v[:int(n*.1)]
207207
ds, pred, sources = dijkstra(data,
208208
directed=True,

scipy/sparse/linalg/_dsolve/tests/test_linsolve.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,7 @@ def test_is_sptriangular_and_spbandwidth(nnz, fmt):
888888

889889
N = nnz // 2
890890
dens = 0.1
891-
A = scipy.sparse.random_array((N, N), density=dens, format="csr", random_state=rng)
891+
A = scipy.sparse.random_array((N, N), density=dens, format="csr", rng=rng)
892892
A[1, 3] = A[3, 1] = 22 # ensure not upper or lower
893893
A = A.asformat(fmt)
894894
AU = scipy.sparse.triu(A, format=fmt)

scipy/sparse/linalg/_eigen/_svds.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ def svds(A, k=6, ncv=None, tol=0, which='LM', v0=None,
297297
ever explicitly constructed.
298298
299299
>>> rng = np.random.default_rng(102524723947864966825913730119128190974)
300-
>>> G = sparse.random_array((8, 9), density=0.5, random_state=rng)
300+
>>> G = sparse.random_array((8, 9), density=0.5, rng=rng)
301301
>>> Glo = aslinearoperator(G)
302302
>>> _, singular_values_svds, _ = svds(Glo, k=5, rng=rng)
303303
>>> _, singular_values_svd, _ = linalg.svd(G.toarray())

scipy/sparse/linalg/_eigen/arpack/tests/test_arpack.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,7 @@ def test_real_eigs_real_k_subset():
682682
rng = np.random.default_rng(2)
683683

684684
n = 10
685-
A = random_array(shape=(n, n), density=0.5, random_state=rng)
685+
A = random_array(shape=(n, n), density=0.5, rng=rng)
686686
A.data *= 2
687687
A.data -= 1
688688
A += A.T # make symmetric to test real eigenvalues

scipy/sparse/linalg/_eigen/tests/test_svds.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -688,9 +688,9 @@ def test_small_sigma_sparse(self, shape, dtype):
688688
rng = np.random.default_rng(0)
689689
k = 5
690690
(m, n) = shape
691-
S = random_array(shape=(m, n), density=0.1, random_state=rng)
691+
S = random_array(shape=(m, n), density=0.1, rng=rng)
692692
if dtype is complex:
693-
S = + 1j * random_array(shape=(m, n), density=0.1, random_state=rng)
693+
S = + 1j * random_array(shape=(m, n), density=0.1, rng=rng)
694694
e = np.ones(m)
695695
e[0:5] *= 1e1 ** np.arange(-5, 0, 1)
696696
S = dia_array((e, 0), shape=(m, m)) @ S

scipy/sparse/linalg/_isolve/tests/test_gcrotmk.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def test_preconditioner(self):
6666
def test_arnoldi(self):
6767
rng = np.random.default_rng(1)
6868

69-
A = eye_array(2000) + random_array((2000, 2000), density=5e-4, random_state=rng)
69+
A = eye_array(2000) + random_array((2000, 2000), density=5e-4, rng=rng)
7070
b = rng.random(2000)
7171

7272
# The inner arnoldi should be equivalent to gmres

0 commit comments

Comments
 (0)