Coverage for /usr/lib/python3/dist-packages/scipy/_lib/_util.py: 19%

258 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2025-06-14 15:25 +0200

1import re 

2from contextlib import contextmanager 

3import functools 

4import operator 

5import warnings 

6import numbers 

7from collections import namedtuple 

8import inspect 

9import math 

10from typing import ( 

11 Optional, 

12 Union, 

13 TYPE_CHECKING, 

14 TypeVar, 

15) 

16 

17import numpy as np 

18 

19IntNumber = Union[int, np.integer] 

20DecimalNumber = Union[float, np.floating, np.integer] 

21 

22# Since Generator was introduced in numpy 1.17, the following condition is needed for 

23# backward compatibility 

24if TYPE_CHECKING: 

25 SeedType = Optional[Union[IntNumber, np.random.Generator, 

26 np.random.RandomState]] 

27 GeneratorType = TypeVar("GeneratorType", bound=Union[np.random.Generator, 

28 np.random.RandomState]) 

29 

30try: 

31 from numpy.random import Generator as Generator 

32except ImportError: 

33 class Generator(): # type: ignore[no-redef] 

34 pass 

35 

36 

37def _lazywhere(cond, arrays, f, fillvalue=None, f2=None): 

38 """ 

39 np.where(cond, x, fillvalue) always evaluates x even where cond is False. 

40 This one only evaluates f(arr1[cond], arr2[cond], ...). 

41 

42 Examples 

43 -------- 

44 >>> import numpy as np 

45 >>> a, b = np.array([1, 2, 3, 4]), np.array([5, 6, 7, 8]) 

46 >>> def f(a, b): 

47 ... return a*b 

48 >>> _lazywhere(a > 2, (a, b), f, np.nan) 

49 array([ nan, nan, 21., 32.]) 

50 

51 Notice, it assumes that all `arrays` are of the same shape, or can be 

52 broadcasted together. 

53 

54 """ 

55 cond = np.asarray(cond) 

56 if fillvalue is None: 

57 if f2 is None: 

58 raise ValueError("One of (fillvalue, f2) must be given.") 

59 else: 

60 fillvalue = np.nan 

61 else: 

62 if f2 is not None: 

63 raise ValueError("Only one of (fillvalue, f2) can be given.") 

64 

65 args = np.broadcast_arrays(cond, *arrays) 

66 cond, arrays = args[0], args[1:] 

67 temp = tuple(np.extract(cond, arr) for arr in arrays) 

68 tcode = np.mintypecode([a.dtype.char for a in arrays]) 

69 out = np.full(np.shape(arrays[0]), fill_value=fillvalue, dtype=tcode) 

70 np.place(out, cond, f(*temp)) 

71 if f2 is not None: 

72 temp = tuple(np.extract(~cond, arr) for arr in arrays) 

73 np.place(out, ~cond, f2(*temp)) 

74 

75 return out 

76 

77 

78def _lazyselect(condlist, choicelist, arrays, default=0): 

79 """ 

80 Mimic `np.select(condlist, choicelist)`. 

81 

82 Notice, it assumes that all `arrays` are of the same shape or can be 

83 broadcasted together. 

84 

85 All functions in `choicelist` must accept array arguments in the order 

86 given in `arrays` and must return an array of the same shape as broadcasted 

87 `arrays`. 

88 

89 Examples 

90 -------- 

91 >>> import numpy as np 

92 >>> x = np.arange(6) 

93 >>> np.select([x <3, x > 3], [x**2, x**3], default=0) 

94 array([ 0, 1, 4, 0, 64, 125]) 

95 

96 >>> _lazyselect([x < 3, x > 3], [lambda x: x**2, lambda x: x**3], (x,)) 

97 array([ 0., 1., 4., 0., 64., 125.]) 

98 

99 >>> a = -np.ones_like(x) 

100 >>> _lazyselect([x < 3, x > 3], 

101 ... [lambda x, a: x**2, lambda x, a: a * x**3], 

102 ... (x, a), default=np.nan) 

103 array([ 0., 1., 4., nan, -64., -125.]) 

104 

105 """ 

106 arrays = np.broadcast_arrays(*arrays) 

107 tcode = np.mintypecode([a.dtype.char for a in arrays]) 

108 out = np.full(np.shape(arrays[0]), fill_value=default, dtype=tcode) 

109 for func, cond in zip(choicelist, condlist): 

110 if np.all(cond is False): 

111 continue 

112 cond, _ = np.broadcast_arrays(cond, arrays[0]) 

113 temp = tuple(np.extract(cond, arr) for arr in arrays) 

114 np.place(out, cond, func(*temp)) 

115 return out 

116 

117 

118def _aligned_zeros(shape, dtype=float, order="C", align=None): 

119 """Allocate a new ndarray with aligned memory. 

120 

121 Primary use case for this currently is working around a f2py issue 

122 in NumPy 1.9.1, where dtype.alignment is such that np.zeros() does 

123 not necessarily create arrays aligned up to it. 

124 

125 """ 

126 dtype = np.dtype(dtype) 

127 if align is None: 

128 align = dtype.alignment 

129 if not hasattr(shape, '__len__'): 

130 shape = (shape,) 

131 size = functools.reduce(operator.mul, shape) * dtype.itemsize 

132 buf = np.empty(size + align + 1, np.uint8) 

133 offset = buf.__array_interface__['data'][0] % align 

134 if offset != 0: 

135 offset = align - offset 

136 # Note: slices producing 0-size arrays do not necessarily change 

137 # data pointer --- so we use and allocate size+1 

138 buf = buf[offset:offset+size+1][:-1] 

139 data = np.ndarray(shape, dtype, buf, order=order) 

140 data.fill(0) 

141 return data 

142 

143 

144def _prune_array(array): 

145 """Return an array equivalent to the input array. If the input 

146 array is a view of a much larger array, copy its contents to a 

147 newly allocated array. Otherwise, return the input unchanged. 

148 """ 

149 if array.base is not None and array.size < array.base.size // 2: 

150 return array.copy() 

151 return array 

152 

153 

154def float_factorial(n: int) -> float: 

155 """Compute the factorial and return as a float 

156 

157 Returns infinity when result is too large for a double 

158 """ 

159 return float(math.factorial(n)) if n < 171 else np.inf 

160 

161 

162# copy-pasted from scikit-learn utils/validation.py 

163# change this to scipy.stats._qmc.check_random_state once numpy 1.16 is dropped 

164def check_random_state(seed): 

165 """Turn `seed` into a `np.random.RandomState` instance. 

166 

167 Parameters 

168 ---------- 

169 seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional 

170 If `seed` is None (or `np.random`), the `numpy.random.RandomState` 

171 singleton is used. 

172 If `seed` is an int, a new ``RandomState`` instance is used, 

173 seeded with `seed`. 

174 If `seed` is already a ``Generator`` or ``RandomState`` instance then 

175 that instance is used. 

176 

177 Returns 

178 ------- 

179 seed : {`numpy.random.Generator`, `numpy.random.RandomState`} 

180 Random number generator. 

181 

182 """ 

183 if seed is None or seed is np.random: 

184 return np.random.mtrand._rand 

185 if isinstance(seed, (numbers.Integral, np.integer)): 

186 return np.random.RandomState(seed) 

187 if isinstance(seed, (np.random.RandomState, np.random.Generator)): 

188 return seed 

189 

190 raise ValueError('%r cannot be used to seed a numpy.random.RandomState' 

191 ' instance' % seed) 

192 

193 

194def _asarray_validated(a, check_finite=True, 

195 sparse_ok=False, objects_ok=False, mask_ok=False, 

196 as_inexact=False): 

197 """ 

198 Helper function for SciPy argument validation. 

199 

200 Many SciPy linear algebra functions do support arbitrary array-like 

201 input arguments. Examples of commonly unsupported inputs include 

202 matrices containing inf/nan, sparse matrix representations, and 

203 matrices with complicated elements. 

204 

205 Parameters 

206 ---------- 

207 a : array_like 

208 The array-like input. 

209 check_finite : bool, optional 

210 Whether to check that the input matrices contain only finite numbers. 

211 Disabling may give a performance gain, but may result in problems 

212 (crashes, non-termination) if the inputs do contain infinities or NaNs. 

213 Default: True 

214 sparse_ok : bool, optional 

215 True if scipy sparse matrices are allowed. 

216 objects_ok : bool, optional 

217 True if arrays with dype('O') are allowed. 

218 mask_ok : bool, optional 

219 True if masked arrays are allowed. 

220 as_inexact : bool, optional 

221 True to convert the input array to a np.inexact dtype. 

222 

223 Returns 

224 ------- 

225 ret : ndarray 

226 The converted validated array. 

227 

228 """ 

229 if not sparse_ok: 

230 import scipy.sparse 

231 if scipy.sparse.issparse(a): 

232 msg = ('Sparse matrices are not supported by this function. ' 

233 'Perhaps one of the scipy.sparse.linalg functions ' 

234 'would work instead.') 

235 raise ValueError(msg) 

236 if not mask_ok: 

237 if np.ma.isMaskedArray(a): 

238 raise ValueError('masked arrays are not supported') 

239 toarray = np.asarray_chkfinite if check_finite else np.asarray 

240 a = toarray(a) 

241 if not objects_ok: 

242 if a.dtype is np.dtype('O'): 

243 raise ValueError('object arrays are not supported') 

244 if as_inexact: 

245 if not np.issubdtype(a.dtype, np.inexact): 

246 a = toarray(a, dtype=np.float_) 

247 return a 

248 

249 

250def _validate_int(k, name, minimum=None): 

251 """ 

252 Validate a scalar integer. 

253 

254 This functon can be used to validate an argument to a function 

255 that expects the value to be an integer. It uses `operator.index` 

256 to validate the value (so, for example, k=2.0 results in a 

257 TypeError). 

258 

259 Parameters 

260 ---------- 

261 k : int 

262 The value to be validated. 

263 name : str 

264 The name of the parameter. 

265 minimum : int, optional 

266 An optional lower bound. 

267 """ 

268 try: 

269 k = operator.index(k) 

270 except TypeError: 

271 raise TypeError(f'{name} must be an integer.') from None 

272 if minimum is not None and k < minimum: 

273 raise ValueError(f'{name} must be an integer not less ' 

274 f'than {minimum}') from None 

275 return k 

276 

277 

278# Add a replacement for inspect.getfullargspec()/ 

279# The version below is borrowed from Django, 

280# https://github.com/django/django/pull/4846. 

281 

282# Note an inconsistency between inspect.getfullargspec(func) and 

283# inspect.signature(func). If `func` is a bound method, the latter does *not* 

284# list `self` as a first argument, while the former *does*. 

285# Hence, cook up a common ground replacement: `getfullargspec_no_self` which 

286# mimics `inspect.getfullargspec` but does not list `self`. 

287# 

288# This way, the caller code does not need to know whether it uses a legacy 

289# .getfullargspec or a bright and shiny .signature. 

290 

291FullArgSpec = namedtuple('FullArgSpec', 

292 ['args', 'varargs', 'varkw', 'defaults', 

293 'kwonlyargs', 'kwonlydefaults', 'annotations']) 

294 

295 

296def getfullargspec_no_self(func): 

297 """inspect.getfullargspec replacement using inspect.signature. 

298 

299 If func is a bound method, do not list the 'self' parameter. 

300 

301 Parameters 

302 ---------- 

303 func : callable 

304 A callable to inspect 

305 

306 Returns 

307 ------- 

308 fullargspec : FullArgSpec(args, varargs, varkw, defaults, kwonlyargs, 

309 kwonlydefaults, annotations) 

310 

311 NOTE: if the first argument of `func` is self, it is *not*, I repeat 

312 *not*, included in fullargspec.args. 

313 This is done for consistency between inspect.getargspec() under 

314 Python 2.x, and inspect.signature() under Python 3.x. 

315 

316 """ 

317 sig = inspect.signature(func) 

318 args = [ 

319 p.name for p in sig.parameters.values() 

320 if p.kind in [inspect.Parameter.POSITIONAL_OR_KEYWORD, 

321 inspect.Parameter.POSITIONAL_ONLY] 

322 ] 

323 varargs = [ 

324 p.name for p in sig.parameters.values() 

325 if p.kind == inspect.Parameter.VAR_POSITIONAL 

326 ] 

327 varargs = varargs[0] if varargs else None 

328 varkw = [ 

329 p.name for p in sig.parameters.values() 

330 if p.kind == inspect.Parameter.VAR_KEYWORD 

331 ] 

332 varkw = varkw[0] if varkw else None 

333 defaults = tuple( 

334 p.default for p in sig.parameters.values() 

335 if (p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD and 

336 p.default is not p.empty) 

337 ) or None 

338 kwonlyargs = [ 

339 p.name for p in sig.parameters.values() 

340 if p.kind == inspect.Parameter.KEYWORD_ONLY 

341 ] 

342 kwdefaults = {p.name: p.default for p in sig.parameters.values() 

343 if p.kind == inspect.Parameter.KEYWORD_ONLY and 

344 p.default is not p.empty} 

345 annotations = {p.name: p.annotation for p in sig.parameters.values() 

346 if p.annotation is not p.empty} 

347 return FullArgSpec(args, varargs, varkw, defaults, kwonlyargs, 

348 kwdefaults or None, annotations) 

349 

350 

351class _FunctionWrapper: 

352 """ 

353 Object to wrap user's function, allowing picklability 

354 """ 

355 def __init__(self, f, args): 

356 self.f = f 

357 self.args = [] if args is None else args 

358 

359 def __call__(self, x): 

360 return self.f(x, *self.args) 

361 

362 

363class MapWrapper: 

364 """ 

365 Parallelisation wrapper for working with map-like callables, such as 

366 `multiprocessing.Pool.map`. 

367 

368 Parameters 

369 ---------- 

370 pool : int or map-like callable 

371 If `pool` is an integer, then it specifies the number of threads to 

372 use for parallelization. If ``int(pool) == 1``, then no parallel 

373 processing is used and the map builtin is used. 

374 If ``pool == -1``, then the pool will utilize all available CPUs. 

375 If `pool` is a map-like callable that follows the same 

376 calling sequence as the built-in map function, then this callable is 

377 used for parallelization. 

378 """ 

379 def __init__(self, pool=1): 

380 self.pool = None 

381 self._mapfunc = map 

382 self._own_pool = False 

383 

384 if callable(pool): 

385 self.pool = pool 

386 self._mapfunc = self.pool 

387 else: 

388 from multiprocessing import Pool 

389 # user supplies a number 

390 if int(pool) == -1: 

391 # use as many processors as possible 

392 self.pool = Pool() 

393 self._mapfunc = self.pool.map 

394 self._own_pool = True 

395 elif int(pool) == 1: 

396 pass 

397 elif int(pool) > 1: 

398 # use the number of processors requested 

399 self.pool = Pool(processes=int(pool)) 

400 self._mapfunc = self.pool.map 

401 self._own_pool = True 

402 else: 

403 raise RuntimeError("Number of workers specified must be -1," 

404 " an int >= 1, or an object with a 'map' " 

405 "method") 

406 

407 def __enter__(self): 

408 return self 

409 

410 def terminate(self): 

411 if self._own_pool: 

412 self.pool.terminate() 

413 

414 def join(self): 

415 if self._own_pool: 

416 self.pool.join() 

417 

418 def close(self): 

419 if self._own_pool: 

420 self.pool.close() 

421 

422 def __exit__(self, exc_type, exc_value, traceback): 

423 if self._own_pool: 

424 self.pool.close() 

425 self.pool.terminate() 

426 

427 def __call__(self, func, iterable): 

428 # only accept one iterable because that's all Pool.map accepts 

429 try: 

430 return self._mapfunc(func, iterable) 

431 except TypeError as e: 

432 # wrong number of arguments 

433 raise TypeError("The map-like callable must be of the" 

434 " form f(func, iterable)") from e 

435 

436 

437def rng_integers(gen, low, high=None, size=None, dtype='int64', 

438 endpoint=False): 

439 """ 

440 Return random integers from low (inclusive) to high (exclusive), or if 

441 endpoint=True, low (inclusive) to high (inclusive). Replaces 

442 `RandomState.randint` (with endpoint=False) and 

443 `RandomState.random_integers` (with endpoint=True). 

444 

445 Return random integers from the "discrete uniform" distribution of the 

446 specified dtype. If high is None (the default), then results are from 

447 0 to low. 

448 

449 Parameters 

450 ---------- 

451 gen : {None, np.random.RandomState, np.random.Generator} 

452 Random number generator. If None, then the np.random.RandomState 

453 singleton is used. 

454 low : int or array-like of ints 

455 Lowest (signed) integers to be drawn from the distribution (unless 

456 high=None, in which case this parameter is 0 and this value is used 

457 for high). 

458 high : int or array-like of ints 

459 If provided, one above the largest (signed) integer to be drawn from 

460 the distribution (see above for behavior if high=None). If array-like, 

461 must contain integer values. 

462 size : array-like of ints, optional 

463 Output shape. If the given shape is, e.g., (m, n, k), then m * n * k 

464 samples are drawn. Default is None, in which case a single value is 

465 returned. 

466 dtype : {str, dtype}, optional 

467 Desired dtype of the result. All dtypes are determined by their name, 

468 i.e., 'int64', 'int', etc, so byteorder is not available and a specific 

469 precision may have different C types depending on the platform. 

470 The default value is np.int_. 

471 endpoint : bool, optional 

472 If True, sample from the interval [low, high] instead of the default 

473 [low, high) Defaults to False. 

474 

475 Returns 

476 ------- 

477 out: int or ndarray of ints 

478 size-shaped array of random integers from the appropriate distribution, 

479 or a single such random int if size not provided. 

480 """ 

481 if isinstance(gen, Generator): 

482 return gen.integers(low, high=high, size=size, dtype=dtype, 

483 endpoint=endpoint) 

484 else: 

485 if gen is None: 

486 # default is RandomState singleton used by np.random. 

487 gen = np.random.mtrand._rand 

488 if endpoint: 

489 # inclusive of endpoint 

490 # remember that low and high can be arrays, so don't modify in 

491 # place 

492 if high is None: 

493 return gen.randint(low + 1, size=size, dtype=dtype) 

494 if high is not None: 

495 return gen.randint(low, high=high + 1, size=size, dtype=dtype) 

496 

497 # exclusive 

498 return gen.randint(low, high=high, size=size, dtype=dtype) 

499 

500 

501@contextmanager 

502def _fixed_default_rng(seed=1638083107694713882823079058616272161): 

503 """Context with a fixed np.random.default_rng seed.""" 

504 orig_fun = np.random.default_rng 

505 np.random.default_rng = lambda seed=seed: orig_fun(seed) 

506 try: 

507 yield 

508 finally: 

509 np.random.default_rng = orig_fun 

510 

511 

512def _rng_html_rewrite(func): 

513 """Rewrite the HTML rendering of ``np.random.default_rng``. 

514 

515 This is intended to decorate 

516 ``numpydoc.docscrape_sphinx.SphinxDocString._str_examples``. 

517 

518 Examples are only run by Sphinx when there are plot involved. Even so, 

519 it does not change the result values getting printed. 

520 """ 

521 # hexadecimal or number seed, case-insensitive 

522 pattern = re.compile(r'np.random.default_rng\((0x[0-9A-F]+|\d+)\)', re.I) 

523 

524 def _wrapped(*args, **kwargs): 

525 res = func(*args, **kwargs) 

526 lines = [ 

527 re.sub(pattern, 'np.random.default_rng()', line) 

528 for line in res 

529 ] 

530 return lines 

531 

532 return _wrapped 

533 

534 

535def _argmin(a, keepdims=False, axis=None): 

536 """ 

537 argmin with a `keepdims` parameter. 

538 

539 See https://github.com/numpy/numpy/issues/8710 

540 

541 If axis is not None, a.shape[axis] must be greater than 0. 

542 """ 

543 res = np.argmin(a, axis=axis) 

544 if keepdims and axis is not None: 

545 res = np.expand_dims(res, axis=axis) 

546 return res 

547 

548 

549def _first_nonnan(a, axis): 

550 """ 

551 Return the first non-nan value along the given axis. 

552 

553 If a slice is all nan, nan is returned for that slice. 

554 

555 The shape of the return value corresponds to ``keepdims=True``. 

556 

557 Examples 

558 -------- 

559 >>> import numpy as np 

560 >>> nan = np.nan 

561 >>> a = np.array([[ 3., 3., nan, 3.], 

562 [ 1., nan, 2., 4.], 

563 [nan, nan, 9., -1.], 

564 [nan, 5., 4., 3.], 

565 [ 2., 2., 2., 2.], 

566 [nan, nan, nan, nan]]) 

567 >>> _first_nonnan(a, axis=0) 

568 array([[3., 3., 2., 3.]]) 

569 >>> _first_nonnan(a, axis=1) 

570 array([[ 3.], 

571 [ 1.], 

572 [ 9.], 

573 [ 5.], 

574 [ 2.], 

575 [nan]]) 

576 """ 

577 k = _argmin(np.isnan(a), axis=axis, keepdims=True) 

578 return np.take_along_axis(a, k, axis=axis) 

579 

580 

581def _nan_allsame(a, axis, keepdims=False): 

582 """ 

583 Determine if the values along an axis are all the same. 

584 

585 nan values are ignored. 

586 

587 `a` must be a numpy array. 

588 

589 `axis` is assumed to be normalized; that is, 0 <= axis < a.ndim. 

590 

591 For an axis of length 0, the result is True. That is, we adopt the 

592 convention that ``allsame([])`` is True. (There are no values in the 

593 input that are different.) 

594 

595 `True` is returned for slices that are all nan--not because all the 

596 values are the same, but because this is equivalent to ``allsame([])``. 

597 

598 Examples 

599 -------- 

600 >>> import numpy as np 

601 >>> a = np.array([[ 3., 3., nan, 3.], 

602 [ 1., nan, 2., 4.], 

603 [nan, nan, 9., -1.], 

604 [nan, 5., 4., 3.], 

605 [ 2., 2., 2., 2.], 

606 [nan, nan, nan, nan]]) 

607 >>> _nan_allsame(a, axis=1, keepdims=True) 

608 array([[ True], 

609 [False], 

610 [False], 

611 [False], 

612 [ True], 

613 [ True]]) 

614 """ 

615 if axis is None: 

616 if a.size == 0: 

617 return True 

618 a = a.ravel() 

619 axis = 0 

620 else: 

621 shp = a.shape 

622 if shp[axis] == 0: 

623 shp = shp[:axis] + (1,)*keepdims + shp[axis + 1:] 

624 return np.full(shp, fill_value=True, dtype=bool) 

625 a0 = _first_nonnan(a, axis=axis) 

626 return ((a0 == a) | np.isnan(a)).all(axis=axis, keepdims=keepdims) 

627 

628 

629def _contains_nan(a, nan_policy='propagate', use_summation=True, 

630 policies=None): 

631 if not isinstance(a, np.ndarray): 

632 use_summation = False # some array_likes ignore nans (e.g. pandas) 

633 if policies is None: 

634 policies = ['propagate', 'raise', 'omit'] 

635 if nan_policy not in policies: 

636 raise ValueError("nan_policy must be one of {%s}" % 

637 ', '.join("'%s'" % s for s in policies)) 

638 

639 if np.issubdtype(a.dtype, np.inexact): 

640 # The summation method avoids creating a (potentially huge) array. 

641 if use_summation: 

642 with np.errstate(invalid='ignore', over='ignore'): 

643 contains_nan = np.isnan(np.sum(a)) 

644 else: 

645 contains_nan = np.isnan(a).any() 

646 elif np.issubdtype(a.dtype, object): 

647 contains_nan = False 

648 for el in a.ravel(): 

649 # isnan doesn't work on non-numeric elements 

650 if np.issubdtype(type(el), np.number) and np.isnan(el): 

651 contains_nan = True 

652 break 

653 else: 

654 # Only `object` and `inexact` arrays can have NaNs 

655 contains_nan = False 

656 

657 if contains_nan and nan_policy == 'raise': 

658 raise ValueError("The input contains nan values") 

659 

660 return contains_nan, nan_policy 

661 

662 

663def _rename_parameter(old_name, new_name, dep_version=None): 

664 """ 

665 Generate decorator for backward-compatible keyword renaming. 

666 

667 Apply the decorator generated by `_rename_parameter` to functions with a 

668 recently renamed parameter to maintain backward-compatibility. 

669 

670 After decoration, the function behaves as follows: 

671 If only the new parameter is passed into the function, behave as usual. 

672 If only the old parameter is passed into the function (as a keyword), raise 

673 a DeprecationWarning if `dep_version` is provided, and behave as usual 

674 otherwise. 

675 If both old and new parameters are passed into the function, raise a 

676 DeprecationWarning if `dep_version` is provided, and raise the appropriate 

677 TypeError (function got multiple values for argument). 

678 

679 Parameters 

680 ---------- 

681 old_name : str 

682 Old name of parameter 

683 new_name : str 

684 New name of parameter 

685 dep_version : str, optional 

686 Version of SciPy in which old parameter was deprecated in the format 

687 'X.Y.Z'. If supplied, the deprecation message will indicate that 

688 support for the old parameter will be removed in version 'X.Y+2.Z' 

689 

690 Notes 

691 ----- 

692 Untested with functions that accept *args. Probably won't work as written. 

693 

694 """ 

695 def decorator(fun): 

696 @functools.wraps(fun) 

697 def wrapper(*args, **kwargs): 

698 if old_name in kwargs: 

699 if dep_version: 

700 end_version = dep_version.split('.') 

701 end_version[1] = str(int(end_version[1]) + 2) 

702 end_version = '.'.join(end_version) 

703 message = (f"Use of keyword argument `{old_name}` is " 

704 f"deprecated and replaced by `{new_name}`. " 

705 f"Support for `{old_name}` will be removed " 

706 f"in SciPy {end_version}.") 

707 warnings.warn(message, DeprecationWarning, stacklevel=2) 

708 if new_name in kwargs: 

709 message = (f"{fun.__name__}() got multiple values for " 

710 f"argument now known as `{new_name}`") 

711 raise TypeError(message) 

712 kwargs[new_name] = kwargs.pop(old_name) 

713 return fun(*args, **kwargs) 

714 return wrapper 

715 return decorator 

716 

717 

718def _rng_spawn(rng, n_children): 

719 # spawns independent RNGs from a parent RNG 

720 bg = rng._bit_generator 

721 ss = bg._seed_seq 

722 child_rngs = [np.random.Generator(type(bg)(child_ss)) 

723 for child_ss in ss.spawn(n_children)] 

724 return child_rngs 

725 

726 

727def _get_nan(*data): 

728 # Get NaN of appropriate dtype for data 

729 data = [np.asarray(item) for item in data] 

730 dtype = np.result_type(*data, np.half) # must be a float16 at least 

731 return np.array(np.nan, dtype=dtype)[()]