Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/scipy/_lib/_util.py: 26%
250 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-12 06:31 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-12 06:31 +0000
1from contextlib import contextmanager
2import functools
3import operator
4import warnings
5import numbers
6from collections import namedtuple
7import inspect
8import math
9from typing import (
10 Optional,
11 Union,
12 TYPE_CHECKING,
13 TypeVar,
14)
16import numpy as np
18IntNumber = Union[int, np.integer]
19DecimalNumber = Union[float, np.floating, np.integer]
21# Since Generator was introduced in numpy 1.17, the following condition is needed for
22# backward compatibility
23if TYPE_CHECKING:
24 SeedType = Optional[Union[IntNumber, np.random.Generator,
25 np.random.RandomState]]
26 GeneratorType = TypeVar("GeneratorType", bound=Union[np.random.Generator,
27 np.random.RandomState])
29try:
30 from numpy.random import Generator as Generator
31except ImportError:
32 class Generator(): # type: ignore[no-redef]
33 pass
36def _lazywhere(cond, arrays, f, fillvalue=None, f2=None):
37 """
38 np.where(cond, x, fillvalue) always evaluates x even where cond is False.
39 This one only evaluates f(arr1[cond], arr2[cond], ...).
41 Examples
42 --------
43 >>> import numpy as np
44 >>> a, b = np.array([1, 2, 3, 4]), np.array([5, 6, 7, 8])
45 >>> def f(a, b):
46 ... return a*b
47 >>> _lazywhere(a > 2, (a, b), f, np.nan)
48 array([ nan, nan, 21., 32.])
50 Notice, it assumes that all `arrays` are of the same shape, or can be
51 broadcasted together.
53 """
54 cond = np.asarray(cond)
55 if fillvalue is None:
56 if f2 is None:
57 raise ValueError("One of (fillvalue, f2) must be given.")
58 else:
59 fillvalue = np.nan
60 else:
61 if f2 is not None:
62 raise ValueError("Only one of (fillvalue, f2) can be given.")
64 args = np.broadcast_arrays(cond, *arrays)
65 cond, arrays = args[0], args[1:]
66 temp = tuple(np.extract(cond, arr) for arr in arrays)
67 tcode = np.mintypecode([a.dtype.char for a in arrays])
68 out = np.full(np.shape(arrays[0]), fill_value=fillvalue, dtype=tcode)
69 np.place(out, cond, f(*temp))
70 if f2 is not None:
71 temp = tuple(np.extract(~cond, arr) for arr in arrays)
72 np.place(out, ~cond, f2(*temp))
74 return out
77def _lazyselect(condlist, choicelist, arrays, default=0):
78 """
79 Mimic `np.select(condlist, choicelist)`.
81 Notice, it assumes that all `arrays` are of the same shape or can be
82 broadcasted together.
84 All functions in `choicelist` must accept array arguments in the order
85 given in `arrays` and must return an array of the same shape as broadcasted
86 `arrays`.
88 Examples
89 --------
90 >>> import numpy as np
91 >>> x = np.arange(6)
92 >>> np.select([x <3, x > 3], [x**2, x**3], default=0)
93 array([ 0, 1, 4, 0, 64, 125])
95 >>> _lazyselect([x < 3, x > 3], [lambda x: x**2, lambda x: x**3], (x,))
96 array([ 0., 1., 4., 0., 64., 125.])
98 >>> a = -np.ones_like(x)
99 >>> _lazyselect([x < 3, x > 3],
100 ... [lambda x, a: x**2, lambda x, a: a * x**3],
101 ... (x, a), default=np.nan)
102 array([ 0., 1., 4., nan, -64., -125.])
104 """
105 arrays = np.broadcast_arrays(*arrays)
106 tcode = np.mintypecode([a.dtype.char for a in arrays])
107 out = np.full(np.shape(arrays[0]), fill_value=default, dtype=tcode)
108 for func, cond in zip(choicelist, condlist):
109 if np.all(cond is False):
110 continue
111 cond, _ = np.broadcast_arrays(cond, arrays[0])
112 temp = tuple(np.extract(cond, arr) for arr in arrays)
113 np.place(out, cond, func(*temp))
114 return out
117def _aligned_zeros(shape, dtype=float, order="C", align=None):
118 """Allocate a new ndarray with aligned memory.
120 Primary use case for this currently is working around a f2py issue
121 in NumPy 1.9.1, where dtype.alignment is such that np.zeros() does
122 not necessarily create arrays aligned up to it.
124 """
125 dtype = np.dtype(dtype)
126 if align is None:
127 align = dtype.alignment
128 if not hasattr(shape, '__len__'):
129 shape = (shape,)
130 size = functools.reduce(operator.mul, shape) * dtype.itemsize
131 buf = np.empty(size + align + 1, np.uint8)
132 offset = buf.__array_interface__['data'][0] % align
133 if offset != 0:
134 offset = align - offset
135 # Note: slices producing 0-size arrays do not necessarily change
136 # data pointer --- so we use and allocate size+1
137 buf = buf[offset:offset+size+1][:-1]
138 data = np.ndarray(shape, dtype, buf, order=order)
139 data.fill(0)
140 return data
143def _prune_array(array):
144 """Return an array equivalent to the input array. If the input
145 array is a view of a much larger array, copy its contents to a
146 newly allocated array. Otherwise, return the input unchanged.
147 """
148 if array.base is not None and array.size < array.base.size // 2:
149 return array.copy()
150 return array
153def prod(iterable):
154 """
155 Product of a sequence of numbers.
157 Faster than np.prod for short lists like array shapes, and does
158 not overflow if using Python integers.
159 """
160 product = 1
161 for x in iterable:
162 product *= x
163 return product
166def float_factorial(n: int) -> float:
167 """Compute the factorial and return as a float
169 Returns infinity when result is too large for a double
170 """
171 return float(math.factorial(n)) if n < 171 else np.inf
174# copy-pasted from scikit-learn utils/validation.py
175# change this to scipy.stats._qmc.check_random_state once numpy 1.16 is dropped
176def check_random_state(seed):
177 """Turn `seed` into a `np.random.RandomState` instance.
179 Parameters
180 ----------
181 seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional
182 If `seed` is None (or `np.random`), the `numpy.random.RandomState`
183 singleton is used.
184 If `seed` is an int, a new ``RandomState`` instance is used,
185 seeded with `seed`.
186 If `seed` is already a ``Generator`` or ``RandomState`` instance then
187 that instance is used.
189 Returns
190 -------
191 seed : {`numpy.random.Generator`, `numpy.random.RandomState`}
192 Random number generator.
194 """
195 if seed is None or seed is np.random:
196 return np.random.mtrand._rand
197 if isinstance(seed, (numbers.Integral, np.integer)):
198 return np.random.RandomState(seed)
199 if isinstance(seed, (np.random.RandomState, np.random.Generator)):
200 return seed
202 raise ValueError('%r cannot be used to seed a numpy.random.RandomState'
203 ' instance' % seed)
206def _asarray_validated(a, check_finite=True,
207 sparse_ok=False, objects_ok=False, mask_ok=False,
208 as_inexact=False):
209 """
210 Helper function for SciPy argument validation.
212 Many SciPy linear algebra functions do support arbitrary array-like
213 input arguments. Examples of commonly unsupported inputs include
214 matrices containing inf/nan, sparse matrix representations, and
215 matrices with complicated elements.
217 Parameters
218 ----------
219 a : array_like
220 The array-like input.
221 check_finite : bool, optional
222 Whether to check that the input matrices contain only finite numbers.
223 Disabling may give a performance gain, but may result in problems
224 (crashes, non-termination) if the inputs do contain infinities or NaNs.
225 Default: True
226 sparse_ok : bool, optional
227 True if scipy sparse matrices are allowed.
228 objects_ok : bool, optional
229 True if arrays with dype('O') are allowed.
230 mask_ok : bool, optional
231 True if masked arrays are allowed.
232 as_inexact : bool, optional
233 True to convert the input array to a np.inexact dtype.
235 Returns
236 -------
237 ret : ndarray
238 The converted validated array.
240 """
241 if not sparse_ok:
242 import scipy.sparse
243 if scipy.sparse.issparse(a):
244 msg = ('Sparse matrices are not supported by this function. '
245 'Perhaps one of the scipy.sparse.linalg functions '
246 'would work instead.')
247 raise ValueError(msg)
248 if not mask_ok:
249 if np.ma.isMaskedArray(a):
250 raise ValueError('masked arrays are not supported')
251 toarray = np.asarray_chkfinite if check_finite else np.asarray
252 a = toarray(a)
253 if not objects_ok:
254 if a.dtype is np.dtype('O'):
255 raise ValueError('object arrays are not supported')
256 if as_inexact:
257 if not np.issubdtype(a.dtype, np.inexact):
258 a = toarray(a, dtype=np.float_)
259 return a
262def _validate_int(k, name, minimum=None):
263 """
264 Validate a scalar integer.
266 This functon can be used to validate an argument to a function
267 that expects the value to be an integer. It uses `operator.index`
268 to validate the value (so, for example, k=2.0 results in a
269 TypeError).
271 Parameters
272 ----------
273 k : int
274 The value to be validated.
275 name : str
276 The name of the parameter.
277 minimum : int, optional
278 An optional lower bound.
279 """
280 try:
281 k = operator.index(k)
282 except TypeError:
283 raise TypeError(f'{name} must be an integer.') from None
284 if minimum is not None and k < minimum:
285 raise ValueError(f'{name} must be an integer not less '
286 f'than {minimum}') from None
287 return k
290# Add a replacement for inspect.getfullargspec()/
291# The version below is borrowed from Django,
292# https://github.com/django/django/pull/4846.
294# Note an inconsistency between inspect.getfullargspec(func) and
295# inspect.signature(func). If `func` is a bound method, the latter does *not*
296# list `self` as a first argument, while the former *does*.
297# Hence, cook up a common ground replacement: `getfullargspec_no_self` which
298# mimics `inspect.getfullargspec` but does not list `self`.
299#
300# This way, the caller code does not need to know whether it uses a legacy
301# .getfullargspec or a bright and shiny .signature.
303FullArgSpec = namedtuple('FullArgSpec',
304 ['args', 'varargs', 'varkw', 'defaults',
305 'kwonlyargs', 'kwonlydefaults', 'annotations'])
308def getfullargspec_no_self(func):
309 """inspect.getfullargspec replacement using inspect.signature.
311 If func is a bound method, do not list the 'self' parameter.
313 Parameters
314 ----------
315 func : callable
316 A callable to inspect
318 Returns
319 -------
320 fullargspec : FullArgSpec(args, varargs, varkw, defaults, kwonlyargs,
321 kwonlydefaults, annotations)
323 NOTE: if the first argument of `func` is self, it is *not*, I repeat
324 *not*, included in fullargspec.args.
325 This is done for consistency between inspect.getargspec() under
326 Python 2.x, and inspect.signature() under Python 3.x.
328 """
329 sig = inspect.signature(func)
330 args = [
331 p.name for p in sig.parameters.values()
332 if p.kind in [inspect.Parameter.POSITIONAL_OR_KEYWORD,
333 inspect.Parameter.POSITIONAL_ONLY]
334 ]
335 varargs = [
336 p.name for p in sig.parameters.values()
337 if p.kind == inspect.Parameter.VAR_POSITIONAL
338 ]
339 varargs = varargs[0] if varargs else None
340 varkw = [
341 p.name for p in sig.parameters.values()
342 if p.kind == inspect.Parameter.VAR_KEYWORD
343 ]
344 varkw = varkw[0] if varkw else None
345 defaults = tuple(
346 p.default for p in sig.parameters.values()
347 if (p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD and
348 p.default is not p.empty)
349 ) or None
350 kwonlyargs = [
351 p.name for p in sig.parameters.values()
352 if p.kind == inspect.Parameter.KEYWORD_ONLY
353 ]
354 kwdefaults = {p.name: p.default for p in sig.parameters.values()
355 if p.kind == inspect.Parameter.KEYWORD_ONLY and
356 p.default is not p.empty}
357 annotations = {p.name: p.annotation for p in sig.parameters.values()
358 if p.annotation is not p.empty}
359 return FullArgSpec(args, varargs, varkw, defaults, kwonlyargs,
360 kwdefaults or None, annotations)
363class _FunctionWrapper:
364 """
365 Object to wrap user's function, allowing picklability
366 """
367 def __init__(self, f, args):
368 self.f = f
369 self.args = [] if args is None else args
371 def __call__(self, x):
372 return self.f(x, *self.args)
375class MapWrapper:
376 """
377 Parallelisation wrapper for working with map-like callables, such as
378 `multiprocessing.Pool.map`.
380 Parameters
381 ----------
382 pool : int or map-like callable
383 If `pool` is an integer, then it specifies the number of threads to
384 use for parallelization. If ``int(pool) == 1``, then no parallel
385 processing is used and the map builtin is used.
386 If ``pool == -1``, then the pool will utilize all available CPUs.
387 If `pool` is a map-like callable that follows the same
388 calling sequence as the built-in map function, then this callable is
389 used for parallelization.
390 """
391 def __init__(self, pool=1):
392 self.pool = None
393 self._mapfunc = map
394 self._own_pool = False
396 if callable(pool):
397 self.pool = pool
398 self._mapfunc = self.pool
399 else:
400 from multiprocessing import Pool
401 # user supplies a number
402 if int(pool) == -1:
403 # use as many processors as possible
404 self.pool = Pool()
405 self._mapfunc = self.pool.map
406 self._own_pool = True
407 elif int(pool) == 1:
408 pass
409 elif int(pool) > 1:
410 # use the number of processors requested
411 self.pool = Pool(processes=int(pool))
412 self._mapfunc = self.pool.map
413 self._own_pool = True
414 else:
415 raise RuntimeError("Number of workers specified must be -1,"
416 " an int >= 1, or an object with a 'map' "
417 "method")
419 def __enter__(self):
420 return self
422 def terminate(self):
423 if self._own_pool:
424 self.pool.terminate()
426 def join(self):
427 if self._own_pool:
428 self.pool.join()
430 def close(self):
431 if self._own_pool:
432 self.pool.close()
434 def __exit__(self, exc_type, exc_value, traceback):
435 if self._own_pool:
436 self.pool.close()
437 self.pool.terminate()
439 def __call__(self, func, iterable):
440 # only accept one iterable because that's all Pool.map accepts
441 try:
442 return self._mapfunc(func, iterable)
443 except TypeError as e:
444 # wrong number of arguments
445 raise TypeError("The map-like callable must be of the"
446 " form f(func, iterable)") from e
449def rng_integers(gen, low, high=None, size=None, dtype='int64',
450 endpoint=False):
451 """
452 Return random integers from low (inclusive) to high (exclusive), or if
453 endpoint=True, low (inclusive) to high (inclusive). Replaces
454 `RandomState.randint` (with endpoint=False) and
455 `RandomState.random_integers` (with endpoint=True).
457 Return random integers from the "discrete uniform" distribution of the
458 specified dtype. If high is None (the default), then results are from
459 0 to low.
461 Parameters
462 ----------
463 gen : {None, np.random.RandomState, np.random.Generator}
464 Random number generator. If None, then the np.random.RandomState
465 singleton is used.
466 low : int or array-like of ints
467 Lowest (signed) integers to be drawn from the distribution (unless
468 high=None, in which case this parameter is 0 and this value is used
469 for high).
470 high : int or array-like of ints
471 If provided, one above the largest (signed) integer to be drawn from
472 the distribution (see above for behavior if high=None). If array-like,
473 must contain integer values.
474 size : array-like of ints, optional
475 Output shape. If the given shape is, e.g., (m, n, k), then m * n * k
476 samples are drawn. Default is None, in which case a single value is
477 returned.
478 dtype : {str, dtype}, optional
479 Desired dtype of the result. All dtypes are determined by their name,
480 i.e., 'int64', 'int', etc, so byteorder is not available and a specific
481 precision may have different C types depending on the platform.
482 The default value is np.int_.
483 endpoint : bool, optional
484 If True, sample from the interval [low, high] instead of the default
485 [low, high) Defaults to False.
487 Returns
488 -------
489 out: int or ndarray of ints
490 size-shaped array of random integers from the appropriate distribution,
491 or a single such random int if size not provided.
492 """
493 if isinstance(gen, Generator):
494 return gen.integers(low, high=high, size=size, dtype=dtype,
495 endpoint=endpoint)
496 else:
497 if gen is None:
498 # default is RandomState singleton used by np.random.
499 gen = np.random.mtrand._rand
500 if endpoint:
501 # inclusive of endpoint
502 # remember that low and high can be arrays, so don't modify in
503 # place
504 if high is None:
505 return gen.randint(low + 1, size=size, dtype=dtype)
506 if high is not None:
507 return gen.randint(low, high=high + 1, size=size, dtype=dtype)
509 # exclusive
510 return gen.randint(low, high=high, size=size, dtype=dtype)
513@contextmanager
514def _fixed_default_rng(seed=1638083107694713882823079058616272161):
515 """Context with a fixed np.random.default_rng seed."""
516 orig_fun = np.random.default_rng
517 np.random.default_rng = lambda seed=seed: orig_fun(seed)
518 try:
519 yield
520 finally:
521 np.random.default_rng = orig_fun
524def _argmin(a, keepdims=False, axis=None):
525 """
526 argmin with a `keepdims` parameter.
528 See https://github.com/numpy/numpy/issues/8710
530 If axis is not None, a.shape[axis] must be greater than 0.
531 """
532 res = np.argmin(a, axis=axis)
533 if keepdims and axis is not None:
534 res = np.expand_dims(res, axis=axis)
535 return res
538def _first_nonnan(a, axis):
539 """
540 Return the first non-nan value along the given axis.
542 If a slice is all nan, nan is returned for that slice.
544 The shape of the return value corresponds to ``keepdims=True``.
546 Examples
547 --------
548 >>> import numpy as np
549 >>> nan = np.nan
550 >>> a = np.array([[ 3., 3., nan, 3.],
551 [ 1., nan, 2., 4.],
552 [nan, nan, 9., -1.],
553 [nan, 5., 4., 3.],
554 [ 2., 2., 2., 2.],
555 [nan, nan, nan, nan]])
556 >>> _first_nonnan(a, axis=0)
557 array([[3., 3., 2., 3.]])
558 >>> _first_nonnan(a, axis=1)
559 array([[ 3.],
560 [ 1.],
561 [ 9.],
562 [ 5.],
563 [ 2.],
564 [nan]])
565 """
566 k = _argmin(np.isnan(a), axis=axis, keepdims=True)
567 return np.take_along_axis(a, k, axis=axis)
570def _nan_allsame(a, axis, keepdims=False):
571 """
572 Determine if the values along an axis are all the same.
574 nan values are ignored.
576 `a` must be a numpy array.
578 `axis` is assumed to be normalized; that is, 0 <= axis < a.ndim.
580 For an axis of length 0, the result is True. That is, we adopt the
581 convention that ``allsame([])`` is True. (There are no values in the
582 input that are different.)
584 `True` is returned for slices that are all nan--not because all the
585 values are the same, but because this is equivalent to ``allsame([])``.
587 Examples
588 --------
589 >>> import numpy as np
590 >>> a = np.array([[ 3., 3., nan, 3.],
591 [ 1., nan, 2., 4.],
592 [nan, nan, 9., -1.],
593 [nan, 5., 4., 3.],
594 [ 2., 2., 2., 2.],
595 [nan, nan, nan, nan]])
596 >>> _nan_allsame(a, axis=1, keepdims=True)
597 array([[ True],
598 [False],
599 [False],
600 [False],
601 [ True],
602 [ True]])
603 """
604 if axis is None:
605 if a.size == 0:
606 return True
607 a = a.ravel()
608 axis = 0
609 else:
610 shp = a.shape
611 if shp[axis] == 0:
612 shp = shp[:axis] + (1,)*keepdims + shp[axis + 1:]
613 return np.full(shp, fill_value=True, dtype=bool)
614 a0 = _first_nonnan(a, axis=axis)
615 return ((a0 == a) | np.isnan(a)).all(axis=axis, keepdims=keepdims)
618def _contains_nan(a, nan_policy='propagate', use_summation=True):
619 if not isinstance(a, np.ndarray):
620 use_summation = False # some array_likes ignore nans (e.g. pandas)
621 policies = ['propagate', 'raise', 'omit']
622 if nan_policy not in policies:
623 raise ValueError("nan_policy must be one of {%s}" %
624 ', '.join("'%s'" % s for s in policies))
626 if np.issubdtype(a.dtype, np.inexact):
627 # The summation method avoids creating a (potentially huge) array.
628 if use_summation:
629 with np.errstate(invalid='ignore', over='ignore'):
630 contains_nan = np.isnan(np.sum(a))
631 else:
632 contains_nan = np.isnan(a).any()
633 elif np.issubdtype(a.dtype, object):
634 contains_nan = False
635 for el in a.ravel():
636 # isnan doesn't work on non-numeric elements
637 if np.issubdtype(type(el), np.number) and np.isnan(el):
638 contains_nan = True
639 break
640 else:
641 # Only `object` and `inexact` arrays can have NaNs
642 contains_nan = False
644 if contains_nan and nan_policy == 'raise':
645 raise ValueError("The input contains nan values")
647 return contains_nan, nan_policy
650def _rename_parameter(old_name, new_name, dep_version=None):
651 """
652 Generate decorator for backward-compatible keyword renaming.
654 Apply the decorator generated by `_rename_parameter` to functions with a
655 recently renamed parameter to maintain backward-compatibility.
657 After decoration, the function behaves as follows:
658 If only the new parameter is passed into the function, behave as usual.
659 If only the old parameter is passed into the function (as a keyword), raise
660 a DeprecationWarning if `dep_version` is provided, and behave as usual
661 otherwise.
662 If both old and new parameters are passed into the function, raise a
663 DeprecationWarning if `dep_version` is provided, and raise the appropriate
664 TypeError (function got multiple values for argument).
666 Parameters
667 ----------
668 old_name : str
669 Old name of parameter
670 new_name : str
671 New name of parameter
672 dep_version : str, optional
673 Version of SciPy in which old parameter was deprecated in the format
674 'X.Y.Z'. If supplied, the deprecation message will indicate that
675 support for the old parameter will be removed in version 'X.Y+2.Z'
677 Notes
678 -----
679 Untested with functions that accept *args. Probably won't work as written.
681 """
682 def decorator(fun):
683 @functools.wraps(fun)
684 def wrapper(*args, **kwargs):
685 if old_name in kwargs:
686 if dep_version:
687 end_version = dep_version.split('.')
688 end_version[1] = str(int(end_version[1]) + 2)
689 end_version = '.'.join(end_version)
690 message = (f"Use of keyword argument `{old_name}` is "
691 f"deprecated and replaced by `{new_name}`. "
692 f"Support for `{old_name}` will be removed "
693 f"in SciPy {end_version}.")
694 warnings.warn(message, DeprecationWarning, stacklevel=2)
695 if new_name in kwargs:
696 message = (f"{fun.__name__}() got multiple values for "
697 f"argument now known as `{new_name}`")
698 raise TypeError(message)
699 kwargs[new_name] = kwargs.pop(old_name)
700 return fun(*args, **kwargs)
701 return wrapper
702 return decorator
705def _rng_spawn(rng, n_children):
706 # spawns independent RNGs from a parent RNG
707 bg = rng._bit_generator
708 ss = bg._seed_seq
709 child_rngs = [np.random.Generator(type(bg)(child_ss))
710 for child_ss in ss.spawn(n_children)]
711 return child_rngs