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
« 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)
18import numpy as np
19from scipy._lib._array_api import array_namespace
22AxisError: Type[Exception]
23ComplexWarning: Type[Warning]
24VisibleDeprecationWarning: Type[Warning]
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 )
36IntNumber = Union[int, np.integer]
37DecimalNumber = Union[float, np.floating, np.integer]
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])
47try:
48 from numpy.random import Generator as Generator
49except ImportError:
50 class Generator(): # type: ignore[no-redef]
51 pass
54def _lazywhere(cond, arrays, f, fillvalue=None, f2=None):
55 """Return elements chosen from two possibilities depending on a condition
57 Equivalent to ``f(*arrays) if cond else fillvalue`` performed elementwise.
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.
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`).
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.
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.])
97 """
98 xp = array_namespace(cond, *arrays)
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.")
103 args = xp.broadcast_arrays(cond, *arrays)
104 cond, arrays = xp.astype(args[0], bool, copy=False), args[1:]
106 temp1 = xp.asarray(f(*(arr[cond] for arr in arrays)))
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
119 out[cond] = temp1
121 return out
124def _lazyselect(condlist, choicelist, arrays, default=0):
125 """
126 Mimic `np.select(condlist, choicelist)`.
128 Notice, it assumes that all `arrays` are of the same shape or can be
129 broadcasted together.
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`.
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])
142 >>> _lazyselect([x < 3, x > 3], [lambda x: x**2, lambda x: x**3], (x,))
143 array([ 0., 1., 4., 0., 64., 125.])
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.])
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
164def _aligned_zeros(shape, dtype=float, order="C", align=None):
165 """Allocate a new ndarray with aligned memory.
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.
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
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
200def float_factorial(n: int) -> float:
201 """Compute the factorial and return as a float
203 Returns infinity when result is too large for a double
204 """
205 return float(math.factorial(n)) if n < 171 else np.inf
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.
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.
223 Returns
224 -------
225 seed : {`numpy.random.Generator`, `numpy.random.RandomState`}
226 Random number generator.
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
236 raise ValueError('%r cannot be used to seed a numpy.random.RandomState'
237 ' instance' % seed)
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.
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.
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.
269 Returns
270 -------
271 ret : ndarray
272 The converted validated array.
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
296def _validate_int(k, name, minimum=None):
297 """
298 Validate a scalar integer.
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).
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
324# Add a replacement for inspect.getfullargspec()/
325# The version below is borrowed from Django,
326# https://github.com/django/django/pull/4846.
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.
337FullArgSpec = namedtuple('FullArgSpec',
338 ['args', 'varargs', 'varkw', 'defaults',
339 'kwonlyargs', 'kwonlydefaults', 'annotations'])
342def getfullargspec_no_self(func):
343 """inspect.getfullargspec replacement using inspect.signature.
345 If func is a bound method, do not list the 'self' parameter.
347 Parameters
348 ----------
349 func : callable
350 A callable to inspect
352 Returns
353 -------
354 fullargspec : FullArgSpec(args, varargs, varkw, defaults, kwonlyargs,
355 kwonlydefaults, annotations)
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.
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)
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
405 def __call__(self, x):
406 return self.f(x, *self.args)
409class MapWrapper:
410 """
411 Parallelisation wrapper for working with map-like callables, such as
412 `multiprocessing.Pool.map`.
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
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")
453 def __enter__(self):
454 return self
456 def terminate(self):
457 if self._own_pool:
458 self.pool.terminate()
460 def join(self):
461 if self._own_pool:
462 self.pool.join()
464 def close(self):
465 if self._own_pool:
466 self.pool.close()
468 def __exit__(self, exc_type, exc_value, traceback):
469 if self._own_pool:
470 self.pool.close()
471 self.pool.terminate()
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
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).
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.
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.
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)
543 # exclusive
544 return gen.randint(low, high=high, size=size, dtype=dtype)
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
558def _rng_html_rewrite(func):
559 """Rewrite the HTML rendering of ``np.random.default_rng``.
561 This is intended to decorate
562 ``numpydoc.docscrape_sphinx.SphinxDocString._str_examples``.
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)
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
578 return _wrapped
581def _argmin(a, keepdims=False, axis=None):
582 """
583 argmin with a `keepdims` parameter.
585 See https://github.com/numpy/numpy/issues/8710
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
595def _first_nonnan(a, axis):
596 """
597 Return the first non-nan value along the given axis.
599 If a slice is all nan, nan is returned for that slice.
601 The shape of the return value corresponds to ``keepdims=True``.
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)
627def _nan_allsame(a, axis, keepdims=False):
628 """
629 Determine if the values along an axis are all the same.
631 nan values are ignored.
633 `a` must be a numpy array.
635 `axis` is assumed to be normalized; that is, 0 <= axis < a.ndim.
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.)
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([])``.
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)
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))
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
703 if contains_nan and nan_policy == 'raise':
704 raise ValueError("The input contains nan values")
706 return contains_nan, nan_policy
709def _rename_parameter(old_name, new_name, dep_version=None):
710 """
711 Generate decorator for backward-compatible keyword renaming.
713 Apply the decorator generated by `_rename_parameter` to functions with a
714 recently renamed parameter to maintain backward-compatibility.
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).
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'
736 Notes
737 -----
738 Untested with functions that accept *args. Probably won't work as written.
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
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
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)[()]
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)
786 if axis < 0:
787 axis = axis + ndim
788 return axis