Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/scipy/_lib/_util.py: 20%

272 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-23 06:43 +0000

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 Type, 

15 TypeVar, 

16) 

17 

18import numpy as np 

19from scipy._lib._array_api import array_namespace 

20 

21 

22AxisError: Type[Exception] 

23ComplexWarning: Type[Warning] 

24VisibleDeprecationWarning: Type[Warning] 

25 

26if np.lib.NumpyVersion(np.__version__) >= '1.25.0': 

27 from numpy.exceptions import ( 

28 AxisError, ComplexWarning, VisibleDeprecationWarning # noqa: F401 

29 ) 

30else: 

31 from numpy import ( 

32 AxisError, ComplexWarning, VisibleDeprecationWarning # noqa: F401 

33 ) 

34 

35 

36IntNumber = Union[int, np.integer] 

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

38 

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

40# backward compatibility 

41if TYPE_CHECKING: 

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

43 np.random.RandomState]] 

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

45 np.random.RandomState]) 

46 

47try: 

48 from numpy.random import Generator as Generator 

49except ImportError: 

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

51 pass 

52 

53 

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

55 """Return elements chosen from two possibilities depending on a condition 

56 

57 Equivalent to ``f(*arrays) if cond else fillvalue`` performed elementwise. 

58 

59 Parameters 

60 ---------- 

61 cond : array 

62 The condition (expressed as a boolean array). 

63 arrays : tuple of array 

64 Arguments to `f` (and `f2`). Must be broadcastable with `cond`. 

65 f : callable 

66 Where `cond` is True, output will be ``f(arr1[cond], arr2[cond], ...)`` 

67 fillvalue : object 

68 If provided, value with which to fill output array where `cond` is 

69 not True. 

70 f2 : callable 

71 If provided, output will be ``f2(arr1[cond], arr2[cond], ...)`` where 

72 `cond` is not True. 

73 

74 Returns 

75 ------- 

76 out : array 

77 An array with elements from the ouput of `f` where `cond` is True 

78 and `fillvalue` (or elements from the output of `f2`) elsewhere. The 

79 returned array has data type determined by Type Promotion Rules 

80 with the output of `f` and `fillvalue` (or the output of `f2`). 

81 

82 Notes 

83 ----- 

84 ``xp.where(cond, x, fillvalue)`` requires explicitly forming `x` even where 

85 `cond` is False. This function evaluates ``f(arr1[cond], arr2[cond], ...)`` 

86 onle where `cond` ``is True. 

87 

88 Examples 

89 -------- 

90 >>> import numpy as np 

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

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

93 ... return a*b 

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

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

96 

97 """ 

98 xp = array_namespace(cond, *arrays) 

99 

100 if (f2 is fillvalue is None) or (f2 is not None and fillvalue is not None): 

101 raise ValueError("Exactly one of `fillvalue` or `f2` must be given.") 

102 

103 args = xp.broadcast_arrays(cond, *arrays) 

104 cond, arrays = xp.astype(args[0], bool, copy=False), args[1:] 

105 

106 temp1 = xp.asarray(f(*(arr[cond] for arr in arrays))) 

107 

108 if f2 is None: 

109 fillvalue = xp.asarray(fillvalue) 

110 dtype = xp.result_type(temp1.dtype, fillvalue.dtype) 

111 out = xp.full(cond.shape, fill_value=fillvalue, dtype=dtype) 

112 else: 

113 ncond = ~cond 

114 temp2 = xp.asarray(f2(*(arr[ncond] for arr in arrays))) 

115 dtype = xp.result_type(temp1, temp2) 

116 out = xp.empty(cond.shape, dtype=dtype) 

117 out[ncond] = temp2 

118 

119 out[cond] = temp1 

120 

121 return out 

122 

123 

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

125 """ 

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

127 

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

129 broadcasted together. 

130 

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

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

133 `arrays`. 

134 

135 Examples 

136 -------- 

137 >>> import numpy as np 

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

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

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

141 

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

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

144 

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

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

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

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

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

150 

151 """ 

152 arrays = np.broadcast_arrays(*arrays) 

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

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

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

156 if np.all(cond is False): 

157 continue 

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

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

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

161 return out 

162 

163 

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

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

166 

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

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

169 not necessarily create arrays aligned up to it. 

170 

171 """ 

172 dtype = np.dtype(dtype) 

173 if align is None: 

174 align = dtype.alignment 

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

176 shape = (shape,) 

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

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

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

180 if offset != 0: 

181 offset = align - offset 

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

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

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

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

186 data.fill(0) 

187 return data 

188 

189 

190def _prune_array(array): 

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

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

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

194 """ 

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

196 return array.copy() 

197 return array 

198 

199 

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

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

202 

203 Returns infinity when result is too large for a double 

204 """ 

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

206 

207 

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

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

210def check_random_state(seed): 

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

212 

213 Parameters 

214 ---------- 

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

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

217 singleton is used. 

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

219 seeded with `seed`. 

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

221 that instance is used. 

222 

223 Returns 

224 ------- 

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

226 Random number generator. 

227 

228 """ 

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

230 return np.random.mtrand._rand 

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

232 return np.random.RandomState(seed) 

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

234 return seed 

235 

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

237 ' instance' % seed) 

238 

239 

240def _asarray_validated(a, check_finite=True, 

241 sparse_ok=False, objects_ok=False, mask_ok=False, 

242 as_inexact=False): 

243 """ 

244 Helper function for SciPy argument validation. 

245 

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

247 input arguments. Examples of commonly unsupported inputs include 

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

249 matrices with complicated elements. 

250 

251 Parameters 

252 ---------- 

253 a : array_like 

254 The array-like input. 

255 check_finite : bool, optional 

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

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

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

259 Default: True 

260 sparse_ok : bool, optional 

261 True if scipy sparse matrices are allowed. 

262 objects_ok : bool, optional 

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

264 mask_ok : bool, optional 

265 True if masked arrays are allowed. 

266 as_inexact : bool, optional 

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

268 

269 Returns 

270 ------- 

271 ret : ndarray 

272 The converted validated array. 

273 

274 """ 

275 if not sparse_ok: 

276 import scipy.sparse 

277 if scipy.sparse.issparse(a): 

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

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

280 'would work instead.') 

281 raise ValueError(msg) 

282 if not mask_ok: 

283 if np.ma.isMaskedArray(a): 

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

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

286 a = toarray(a) 

287 if not objects_ok: 

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

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

290 if as_inexact: 

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

292 a = toarray(a, dtype=np.float64) 

293 return a 

294 

295 

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

297 """ 

298 Validate a scalar integer. 

299 

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

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

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

303 TypeError). 

304 

305 Parameters 

306 ---------- 

307 k : int 

308 The value to be validated. 

309 name : str 

310 The name of the parameter. 

311 minimum : int, optional 

312 An optional lower bound. 

313 """ 

314 try: 

315 k = operator.index(k) 

316 except TypeError: 

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

318 if minimum is not None and k < minimum: 

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

320 f'than {minimum}') from None 

321 return k 

322 

323 

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

325# The version below is borrowed from Django, 

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

327 

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

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

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

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

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

333# 

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

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

336 

337FullArgSpec = namedtuple('FullArgSpec', 

338 ['args', 'varargs', 'varkw', 'defaults', 

339 'kwonlyargs', 'kwonlydefaults', 'annotations']) 

340 

341 

342def getfullargspec_no_self(func): 

343 """inspect.getfullargspec replacement using inspect.signature. 

344 

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

346 

347 Parameters 

348 ---------- 

349 func : callable 

350 A callable to inspect 

351 

352 Returns 

353 ------- 

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

355 kwonlydefaults, annotations) 

356 

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

358 *not*, included in fullargspec.args. 

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

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

361 

362 """ 

363 sig = inspect.signature(func) 

364 args = [ 

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

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

367 inspect.Parameter.POSITIONAL_ONLY] 

368 ] 

369 varargs = [ 

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

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

372 ] 

373 varargs = varargs[0] if varargs else None 

374 varkw = [ 

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

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

377 ] 

378 varkw = varkw[0] if varkw else None 

379 defaults = tuple( 

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

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

382 p.default is not p.empty) 

383 ) or None 

384 kwonlyargs = [ 

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

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

387 ] 

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

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

390 p.default is not p.empty} 

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

392 if p.annotation is not p.empty} 

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

394 kwdefaults or None, annotations) 

395 

396 

397class _FunctionWrapper: 

398 """ 

399 Object to wrap user's function, allowing picklability 

400 """ 

401 def __init__(self, f, args): 

402 self.f = f 

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

404 

405 def __call__(self, x): 

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

407 

408 

409class MapWrapper: 

410 """ 

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

412 `multiprocessing.Pool.map`. 

413 

414 Parameters 

415 ---------- 

416 pool : int or map-like callable 

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

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

419 processing is used and the map builtin is used. 

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

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

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

423 used for parallelization. 

424 """ 

425 def __init__(self, pool=1): 

426 self.pool = None 

427 self._mapfunc = map 

428 self._own_pool = False 

429 

430 if callable(pool): 

431 self.pool = pool 

432 self._mapfunc = self.pool 

433 else: 

434 from multiprocessing import Pool 

435 # user supplies a number 

436 if int(pool) == -1: 

437 # use as many processors as possible 

438 self.pool = Pool() 

439 self._mapfunc = self.pool.map 

440 self._own_pool = True 

441 elif int(pool) == 1: 

442 pass 

443 elif int(pool) > 1: 

444 # use the number of processors requested 

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

446 self._mapfunc = self.pool.map 

447 self._own_pool = True 

448 else: 

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

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

451 "method") 

452 

453 def __enter__(self): 

454 return self 

455 

456 def terminate(self): 

457 if self._own_pool: 

458 self.pool.terminate() 

459 

460 def join(self): 

461 if self._own_pool: 

462 self.pool.join() 

463 

464 def close(self): 

465 if self._own_pool: 

466 self.pool.close() 

467 

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

469 if self._own_pool: 

470 self.pool.close() 

471 self.pool.terminate() 

472 

473 def __call__(self, func, iterable): 

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

475 try: 

476 return self._mapfunc(func, iterable) 

477 except TypeError as e: 

478 # wrong number of arguments 

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

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

481 

482 

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

484 endpoint=False): 

485 """ 

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

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

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

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

490 

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

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

493 0 to low. 

494 

495 Parameters 

496 ---------- 

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

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

499 singleton is used. 

500 low : int or array-like of ints 

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

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

503 for high). 

504 high : int or array-like of ints 

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

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

507 must contain integer values. 

508 size : array-like of ints, optional 

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

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

511 returned. 

512 dtype : {str, dtype}, optional 

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

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

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

516 The default value is np.int_. 

517 endpoint : bool, optional 

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

519 [low, high) Defaults to False. 

520 

521 Returns 

522 ------- 

523 out: int or ndarray of ints 

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

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

526 """ 

527 if isinstance(gen, Generator): 

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

529 endpoint=endpoint) 

530 else: 

531 if gen is None: 

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

533 gen = np.random.mtrand._rand 

534 if endpoint: 

535 # inclusive of endpoint 

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

537 # place 

538 if high is None: 

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

540 if high is not None: 

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

542 

543 # exclusive 

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

545 

546 

547@contextmanager 

548def _fixed_default_rng(seed=1638083107694713882823079058616272161): 

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

550 orig_fun = np.random.default_rng 

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

552 try: 

553 yield 

554 finally: 

555 np.random.default_rng = orig_fun 

556 

557 

558def _rng_html_rewrite(func): 

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

560 

561 This is intended to decorate 

562 ``numpydoc.docscrape_sphinx.SphinxDocString._str_examples``. 

563 

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

565 it does not change the result values getting printed. 

566 """ 

567 # hexadecimal or number seed, case-insensitive 

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

569 

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

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

572 lines = [ 

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

574 for line in res 

575 ] 

576 return lines 

577 

578 return _wrapped 

579 

580 

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

582 """ 

583 argmin with a `keepdims` parameter. 

584 

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

586 

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

588 """ 

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

590 if keepdims and axis is not None: 

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

592 return res 

593 

594 

595def _first_nonnan(a, axis): 

596 """ 

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

598 

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

600 

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

602 

603 Examples 

604 -------- 

605 >>> import numpy as np 

606 >>> nan = np.nan 

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

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

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

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

611 [ 2., 2., 2., 2.], 

612 [nan, nan, nan, nan]]) 

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

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

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

616 array([[ 3.], 

617 [ 1.], 

618 [ 9.], 

619 [ 5.], 

620 [ 2.], 

621 [nan]]) 

622 """ 

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

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

625 

626 

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

628 """ 

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

630 

631 nan values are ignored. 

632 

633 `a` must be a numpy array. 

634 

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

636 

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

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

639 input that are different.) 

640 

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

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

643 

644 Examples 

645 -------- 

646 >>> from numpy import nan, array 

647 >>> a = array([[ 3., 3., nan, 3.], 

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

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

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

651 ... [ 2., 2., 2., 2.], 

652 ... [nan, nan, nan, nan]]) 

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

654 array([[ True], 

655 [False], 

656 [False], 

657 [False], 

658 [ True], 

659 [ True]]) 

660 """ 

661 if axis is None: 

662 if a.size == 0: 

663 return True 

664 a = a.ravel() 

665 axis = 0 

666 else: 

667 shp = a.shape 

668 if shp[axis] == 0: 

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

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

671 a0 = _first_nonnan(a, axis=axis) 

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

673 

674 

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

676 policies=None): 

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

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

679 if policies is None: 

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

681 if nan_policy not in policies: 

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

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

684 

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

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

687 if use_summation: 

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

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

690 else: 

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

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

693 contains_nan = False 

694 for el in a.ravel(): 

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

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

697 contains_nan = True 

698 break 

699 else: 

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

701 contains_nan = False 

702 

703 if contains_nan and nan_policy == 'raise': 

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

705 

706 return contains_nan, nan_policy 

707 

708 

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

710 """ 

711 Generate decorator for backward-compatible keyword renaming. 

712 

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

714 recently renamed parameter to maintain backward-compatibility. 

715 

716 After decoration, the function behaves as follows: 

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

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

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

720 otherwise. 

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

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

723 TypeError (function got multiple values for argument). 

724 

725 Parameters 

726 ---------- 

727 old_name : str 

728 Old name of parameter 

729 new_name : str 

730 New name of parameter 

731 dep_version : str, optional 

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

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

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

735 

736 Notes 

737 ----- 

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

739 

740 """ 

741 def decorator(fun): 

742 @functools.wraps(fun) 

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

744 if old_name in kwargs: 

745 if dep_version: 

746 end_version = dep_version.split('.') 

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

748 end_version = '.'.join(end_version) 

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

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

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

752 f"in SciPy {end_version}.") 

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

754 if new_name in kwargs: 

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

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

757 raise TypeError(message) 

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

759 return fun(*args, **kwargs) 

760 return wrapper 

761 return decorator 

762 

763 

764def _rng_spawn(rng, n_children): 

765 # spawns independent RNGs from a parent RNG 

766 bg = rng._bit_generator 

767 ss = bg._seed_seq 

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

769 for child_ss in ss.spawn(n_children)] 

770 return child_rngs 

771 

772 

773def _get_nan(*data): 

774 # Get NaN of appropriate dtype for data 

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

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

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

778 

779 

780def normalize_axis_index(axis, ndim): 

781 # Check if `axis` is in the correct range and normalize it 

782 if axis < -ndim or axis >= ndim: 

783 msg = f"axis {axis} is out of bounds for array of dimension {ndim}" 

784 raise AxisError(msg) 

785 

786 if axis < 0: 

787 axis = axis + ndim 

788 return axis