diff options
Diffstat (limited to 'numpy/random')
-rw-r--r-- | numpy/random/_bounded_integers.pyx.in | 88 | ||||
-rw-r--r-- | numpy/random/_common.pxd | 40 | ||||
-rw-r--r-- | numpy/random/_common.pyx | 2 | ||||
-rw-r--r-- | numpy/random/_generator.pyi | 99 | ||||
-rw-r--r-- | numpy/random/_generator.pyx | 94 | ||||
-rw-r--r-- | numpy/random/_mt19937.pyx | 8 | ||||
-rw-r--r-- | numpy/random/_pcg64.pyx | 17 | ||||
-rw-r--r-- | numpy/random/_philox.pyx | 10 | ||||
-rw-r--r-- | numpy/random/_sfc64.pyx | 6 | ||||
-rw-r--r-- | numpy/random/bit_generator.pyi | 3 | ||||
-rw-r--r-- | numpy/random/bit_generator.pyx | 67 | ||||
-rw-r--r-- | numpy/random/c_distributions.pxd | 12 | ||||
-rw-r--r-- | numpy/random/meson.build | 2 | ||||
-rw-r--r-- | numpy/random/mtrand.pyx | 18 | ||||
-rw-r--r-- | numpy/random/src/distributions/distributions.c | 10 | ||||
-rw-r--r-- | numpy/random/tests/test_direct.py | 40 | ||||
-rw-r--r-- | numpy/random/tests/test_generator_mt19937.py | 2 | ||||
-rw-r--r-- | numpy/random/tests/test_generator_mt19937_regressions.py | 50 | ||||
-rw-r--r-- | numpy/random/tests/test_randomstate.py | 4 |
19 files changed, 436 insertions, 136 deletions
diff --git a/numpy/random/_bounded_integers.pyx.in b/numpy/random/_bounded_integers.pyx.in index 7eb6aff1e..6743001d6 100644 --- a/numpy/random/_bounded_integers.pyx.in +++ b/numpy/random/_bounded_integers.pyx.in @@ -99,8 +99,12 @@ cdef object _rand_{{nptype}}_broadcast(np.ndarray low, np.ndarray high, object s is_open = not closed low_arr = <np.ndarray>low high_arr = <np.ndarray>high - if np.any(np.less(low_arr, {{lb}})): + + if np.can_cast(low_arr, np.{{otype}}): + pass # cannot be out-of-bounds + elif np.any(np.less(low_arr, np.{{otype}}({{lb}}))): raise ValueError('low is out of bounds for {{nptype}}') + if closed: high_comp = np.greater_equal low_high_comp = np.greater @@ -108,8 +112,11 @@ cdef object _rand_{{nptype}}_broadcast(np.ndarray low, np.ndarray high, object s high_comp = np.greater low_high_comp = np.greater_equal - if np.any(high_comp(high_arr, {{ub}})): + if np.can_cast(high_arr, np.{{otype}}): + pass # cannot be out-of-bounds + elif np.any(high_comp(high_arr, np.{{nptype_up}}({{ub}}))): raise ValueError('high is out of bounds for {{nptype}}') + if np.any(low_high_comp(low_arr, high_arr)): raise ValueError(format_bounds_error(closed, low_arr)) @@ -165,50 +172,69 @@ cdef object _rand_{{nptype}}_broadcast(object low, object high, object size, 64 bit integer type. """ - cdef np.ndarray low_arr, high_arr, out_arr, highm1_arr + cdef np.ndarray low_arr, low_arr_orig, high_arr, high_arr_orig, out_arr cdef np.npy_intp i, cnt, n cdef np.broadcast it cdef object closed_upper cdef uint64_t *out_data cdef {{nptype}}_t *highm1_data cdef {{nptype}}_t low_v, high_v - cdef uint64_t rng, last_rng, val, mask, off, out_val + cdef uint64_t rng, last_rng, val, mask, off, out_val, is_open - low_arr = <np.ndarray>low - high_arr = <np.ndarray>high + low_arr_orig = <np.ndarray>low + high_arr_orig = <np.ndarray>high - if np.any(np.less(low_arr, {{lb}})): - raise ValueError('low is out of bounds for {{nptype}}') - dt = high_arr.dtype - if closed or np.issubdtype(dt, np.integer): - # Avoid object dtype path if already an integer - high_lower_comp = np.less if closed else np.less_equal - if np.any(high_lower_comp(high_arr, {{lb}})): - raise ValueError(format_bounds_error(closed, low_arr)) - high_m1 = high_arr if closed else high_arr - dt.type(1) - if np.any(np.greater(high_m1, {{ub}})): - raise ValueError('high is out of bounds for {{nptype}}') - highm1_arr = <np.ndarray>np.PyArray_FROM_OTF(high_m1, np.{{npctype}}, np.NPY_ALIGNED | np.NPY_FORCECAST) + is_open = not closed + + # The following code tries to cast safely, but failing that goes via + # Python `int()` because it is very difficult to cast integers in a + # truly safe way (i.e. so it raises on out-of-bound). + # We correct if the interval is not closed in this step if we go the long + # route. (Not otherwise, since the -1 could overflow in theory.) + if np.can_cast(low_arr_orig, np.{{otype}}): + low_arr = <np.ndarray>np.PyArray_FROM_OTF(low_arr_orig, np.{{npctype}}, np.NPY_ALIGNED) + else: + low_arr = <np.ndarray>np.empty_like(low_arr_orig, dtype=np.{{otype}}) + flat = low_arr_orig.flat + low_data = <{{nptype}}_t *>np.PyArray_DATA(low_arr) + cnt = np.PyArray_SIZE(low_arr) + for i in range(cnt): + lower = int(flat[i]) + if lower < {{lb}} or lower > {{ub}}: + raise ValueError('low is out of bounds for {{nptype}}') + low_data[i] = lower + + del low_arr_orig + + if np.can_cast(high_arr_orig, np.{{otype}}): + high_arr = <np.ndarray>np.PyArray_FROM_OTF(high_arr_orig, np.{{npctype}}, np.NPY_ALIGNED) else: - # If input is object or a floating type - highm1_arr = <np.ndarray>np.empty_like(high_arr, dtype=np.{{otype}}) - highm1_data = <{{nptype}}_t *>np.PyArray_DATA(highm1_arr) + high_arr = np.empty_like(high_arr_orig, dtype=np.{{otype}}) + flat = high_arr_orig.flat + high_data = <{{nptype}}_t *>np.PyArray_DATA(high_arr) cnt = np.PyArray_SIZE(high_arr) - flat = high_arr.flat for i in range(cnt): - # Subtract 1 since generator produces values on the closed int [off, off+rng] - closed_upper = int(flat[i]) - 1 + closed_upper = int(flat[i]) - is_open if closed_upper > {{ub}}: raise ValueError('high is out of bounds for {{nptype}}') if closed_upper < {{lb}}: raise ValueError(format_bounds_error(closed, low_arr)) - highm1_data[i] = <{{nptype}}_t>closed_upper + high_data[i] = closed_upper - if np.any(np.greater(low_arr, highm1_arr)): - raise ValueError(format_bounds_error(closed, low_arr)) + is_open = 0 # we applied is_open in this path already - high_arr = highm1_arr - low_arr = <np.ndarray>np.PyArray_FROM_OTF(low, np.{{npctype}}, np.NPY_ALIGNED | np.NPY_FORCECAST) + del high_arr_orig + + # Since we have the largest supported integer dtypes, they must be within + # range at this point; otherwise conversion would have failed. Check that + # it is never true that `high <= low`` if closed and `high < low` if not + if not is_open: + low_high_comp = np.greater + else: + low_high_comp = np.greater_equal + + if np.any(low_high_comp(low_arr, high_arr)): + raise ValueError(format_bounds_error(closed, low_arr)) if size is not None: out_arr = <np.ndarray>np.empty(size, np.{{otype}}) @@ -224,8 +250,8 @@ cdef object _rand_{{nptype}}_broadcast(object low, object high, object size, for i in range(n): low_v = (<{{nptype}}_t*>np.PyArray_MultiIter_DATA(it, 0))[0] high_v = (<{{nptype}}_t*>np.PyArray_MultiIter_DATA(it, 1))[0] - # Generator produces values on the closed int [off, off+rng], -1 subtracted above - rng = <{{utype}}_t>(high_v - low_v) + # Generator produces values on the closed int [off, off+rng] + rng = <{{utype}}_t>((high_v - is_open) - low_v) off = <{{utype}}_t>(<{{nptype}}_t>low_v) if rng != last_rng: diff --git a/numpy/random/_common.pxd b/numpy/random/_common.pxd index 3eaf39ddf..659da0d2d 100644 --- a/numpy/random/_common.pxd +++ b/numpy/random/_common.pxd @@ -39,32 +39,32 @@ cdef extern from "include/aligned_malloc.h": cdef void *PyArray_calloc_aligned(size_t n, size_t s) cdef void PyArray_free_aligned(void *p) -ctypedef void (*random_double_fill)(bitgen_t *state, np.npy_intp count, double* out) nogil -ctypedef double (*random_double_0)(void *state) nogil -ctypedef double (*random_double_1)(void *state, double a) nogil -ctypedef double (*random_double_2)(void *state, double a, double b) nogil -ctypedef double (*random_double_3)(void *state, double a, double b, double c) nogil +ctypedef void (*random_double_fill)(bitgen_t *state, np.npy_intp count, double* out) noexcept nogil +ctypedef double (*random_double_0)(void *state) noexcept nogil +ctypedef double (*random_double_1)(void *state, double a) noexcept nogil +ctypedef double (*random_double_2)(void *state, double a, double b) noexcept nogil +ctypedef double (*random_double_3)(void *state, double a, double b, double c) noexcept nogil -ctypedef void (*random_float_fill)(bitgen_t *state, np.npy_intp count, float* out) nogil -ctypedef float (*random_float_0)(bitgen_t *state) nogil -ctypedef float (*random_float_1)(bitgen_t *state, float a) nogil +ctypedef void (*random_float_fill)(bitgen_t *state, np.npy_intp count, float* out) noexcept nogil +ctypedef float (*random_float_0)(bitgen_t *state) noexcept nogil +ctypedef float (*random_float_1)(bitgen_t *state, float a) noexcept nogil -ctypedef int64_t (*random_uint_0)(void *state) nogil -ctypedef int64_t (*random_uint_d)(void *state, double a) nogil -ctypedef int64_t (*random_uint_dd)(void *state, double a, double b) nogil -ctypedef int64_t (*random_uint_di)(void *state, double a, uint64_t b) nogil -ctypedef int64_t (*random_uint_i)(void *state, int64_t a) nogil -ctypedef int64_t (*random_uint_iii)(void *state, int64_t a, int64_t b, int64_t c) nogil +ctypedef int64_t (*random_uint_0)(void *state) noexcept nogil +ctypedef int64_t (*random_uint_d)(void *state, double a) noexcept nogil +ctypedef int64_t (*random_uint_dd)(void *state, double a, double b) noexcept nogil +ctypedef int64_t (*random_uint_di)(void *state, double a, uint64_t b) noexcept nogil +ctypedef int64_t (*random_uint_i)(void *state, int64_t a) noexcept nogil +ctypedef int64_t (*random_uint_iii)(void *state, int64_t a, int64_t b, int64_t c) noexcept nogil -ctypedef uint32_t (*random_uint_0_32)(bitgen_t *state) nogil -ctypedef uint32_t (*random_uint_1_i_32)(bitgen_t *state, uint32_t a) nogil +ctypedef uint32_t (*random_uint_0_32)(bitgen_t *state) noexcept nogil +ctypedef uint32_t (*random_uint_1_i_32)(bitgen_t *state, uint32_t a) noexcept nogil -ctypedef int32_t (*random_int_2_i_32)(bitgen_t *state, int32_t a, int32_t b) nogil -ctypedef int64_t (*random_int_2_i)(bitgen_t *state, int64_t a, int64_t b) nogil +ctypedef int32_t (*random_int_2_i_32)(bitgen_t *state, int32_t a, int32_t b) noexcept nogil +ctypedef int64_t (*random_int_2_i)(bitgen_t *state, int64_t a, int64_t b) noexcept nogil -cdef double kahan_sum(double *darr, np.npy_intp n) +cdef double kahan_sum(double *darr, np.npy_intp n) noexcept -cdef inline double uint64_to_double(uint64_t rnd) nogil: +cdef inline double uint64_to_double(uint64_t rnd) noexcept nogil: return (rnd >> 11) * (1.0 / 9007199254740992.0) cdef object double_fill(void *func, bitgen_t *state, object size, object lock, object out) diff --git a/numpy/random/_common.pyx b/numpy/random/_common.pyx index 7b6f69303..c5e4e3297 100644 --- a/numpy/random/_common.pyx +++ b/numpy/random/_common.pyx @@ -171,7 +171,7 @@ cdef object prepare_ctypes(bitgen_t *bitgen): ctypes.c_void_p(<uintptr_t>bitgen)) return _ctypes -cdef double kahan_sum(double *darr, np.npy_intp n): +cdef double kahan_sum(double *darr, np.npy_intp n) noexcept: """ Parameters ---------- diff --git a/numpy/random/_generator.pyi b/numpy/random/_generator.pyi index f0d814fef..e1cdefb15 100644 --- a/numpy/random/_generator.pyi +++ b/numpy/random/_generator.pyi @@ -29,6 +29,7 @@ from numpy._typing import ( _DTypeLikeUInt, _Float32Codes, _Float64Codes, + _FloatLike_co, _Int8Codes, _Int16Codes, _Int32Codes, @@ -72,6 +73,7 @@ class Generator: def __reduce__(self) -> tuple[Callable[[str], Generator], tuple[str], dict[str, Any]]: ... @property def bit_generator(self) -> BitGenerator: ... + def spawn(self, n_children: int) -> list[Generator]: ... def bytes(self, length: int) -> bytes: ... @overload def standard_normal( # type: ignore[misc] @@ -187,13 +189,18 @@ class Generator: out: None | ndarray[Any, dtype[float64]] = ..., ) -> ndarray[Any, dtype[float64]]: ... @overload - def beta(self, a: float, b: float, size: None = ...) -> float: ... # type: ignore[misc] + def beta( + self, + a: _FloatLike_co, + b: _FloatLike_co, + size: None = ..., + ) -> float: ... # type: ignore[misc] @overload def beta( self, a: _ArrayLikeFloat_co, b: _ArrayLikeFloat_co, size: None | _ShapeLike = ... ) -> ndarray[Any, dtype[float64]]: ... @overload - def exponential(self, scale: float = ..., size: None = ...) -> float: ... # type: ignore[misc] + def exponential(self, scale: _FloatLike_co = ..., size: None = ...) -> float: ... # type: ignore[misc] @overload def exponential( self, scale: _ArrayLikeFloat_co = ..., size: None | _ShapeLike = ... @@ -370,7 +377,12 @@ class Generator: shuffle: bool = ..., ) -> ndarray[Any, Any]: ... @overload - def uniform(self, low: float = ..., high: float = ..., size: None = ...) -> float: ... # type: ignore[misc] + def uniform( + self, + low: _FloatLike_co = ..., + high: _FloatLike_co = ..., + size: None = ..., + ) -> float: ... # type: ignore[misc] @overload def uniform( self, @@ -379,7 +391,12 @@ class Generator: size: None | _ShapeLike = ..., ) -> ndarray[Any, dtype[float64]]: ... @overload - def normal(self, loc: float = ..., scale: float = ..., size: None = ...) -> float: ... # type: ignore[misc] + def normal( + self, + loc: _FloatLike_co = ..., + scale: _FloatLike_co = ..., + size: None = ..., + ) -> float: ... # type: ignore[misc] @overload def normal( self, @@ -390,7 +407,7 @@ class Generator: @overload def standard_gamma( # type: ignore[misc] self, - shape: float, + shape: _FloatLike_co, size: None = ..., dtype: _DTypeLikeFloat32 | _DTypeLikeFloat64 = ..., out: None = ..., @@ -425,7 +442,7 @@ class Generator: out: None | ndarray[Any, dtype[float64]] = ..., ) -> ndarray[Any, dtype[float64]]: ... @overload - def gamma(self, shape: float, scale: float = ..., size: None = ...) -> float: ... # type: ignore[misc] + def gamma(self, shape: _FloatLike_co, scale: _FloatLike_co = ..., size: None = ...) -> float: ... # type: ignore[misc] @overload def gamma( self, @@ -434,13 +451,13 @@ class Generator: size: None | _ShapeLike = ..., ) -> ndarray[Any, dtype[float64]]: ... @overload - def f(self, dfnum: float, dfden: float, size: None = ...) -> float: ... # type: ignore[misc] + def f(self, dfnum: _FloatLike_co, dfden: _FloatLike_co, size: None = ...) -> float: ... # type: ignore[misc] @overload def f( self, dfnum: _ArrayLikeFloat_co, dfden: _ArrayLikeFloat_co, size: None | _ShapeLike = ... ) -> ndarray[Any, dtype[float64]]: ... @overload - def noncentral_f(self, dfnum: float, dfden: float, nonc: float, size: None = ...) -> float: ... # type: ignore[misc] + def noncentral_f(self, dfnum: _FloatLike_co, dfden: _FloatLike_co, nonc: _FloatLike_co, size: None = ...) -> float: ... # type: ignore[misc] @overload def noncentral_f( self, @@ -450,19 +467,19 @@ class Generator: size: None | _ShapeLike = ..., ) -> ndarray[Any, dtype[float64]]: ... @overload - def chisquare(self, df: float, size: None = ...) -> float: ... # type: ignore[misc] + def chisquare(self, df: _FloatLike_co, size: None = ...) -> float: ... # type: ignore[misc] @overload def chisquare( self, df: _ArrayLikeFloat_co, size: None | _ShapeLike = ... ) -> ndarray[Any, dtype[float64]]: ... @overload - def noncentral_chisquare(self, df: float, nonc: float, size: None = ...) -> float: ... # type: ignore[misc] + def noncentral_chisquare(self, df: _FloatLike_co, nonc: _FloatLike_co, size: None = ...) -> float: ... # type: ignore[misc] @overload def noncentral_chisquare( self, df: _ArrayLikeFloat_co, nonc: _ArrayLikeFloat_co, size: None | _ShapeLike = ... ) -> ndarray[Any, dtype[float64]]: ... @overload - def standard_t(self, df: float, size: None = ...) -> float: ... # type: ignore[misc] + def standard_t(self, df: _FloatLike_co, size: None = ...) -> float: ... # type: ignore[misc] @overload def standard_t( self, df: _ArrayLikeFloat_co, size: None = ... @@ -472,25 +489,25 @@ class Generator: self, df: _ArrayLikeFloat_co, size: _ShapeLike = ... ) -> ndarray[Any, dtype[float64]]: ... @overload - def vonmises(self, mu: float, kappa: float, size: None = ...) -> float: ... # type: ignore[misc] + def vonmises(self, mu: _FloatLike_co, kappa: _FloatLike_co, size: None = ...) -> float: ... # type: ignore[misc] @overload def vonmises( self, mu: _ArrayLikeFloat_co, kappa: _ArrayLikeFloat_co, size: None | _ShapeLike = ... ) -> ndarray[Any, dtype[float64]]: ... @overload - def pareto(self, a: float, size: None = ...) -> float: ... # type: ignore[misc] + def pareto(self, a: _FloatLike_co, size: None = ...) -> float: ... # type: ignore[misc] @overload def pareto( self, a: _ArrayLikeFloat_co, size: None | _ShapeLike = ... ) -> ndarray[Any, dtype[float64]]: ... @overload - def weibull(self, a: float, size: None = ...) -> float: ... # type: ignore[misc] + def weibull(self, a: _FloatLike_co, size: None = ...) -> float: ... # type: ignore[misc] @overload def weibull( self, a: _ArrayLikeFloat_co, size: None | _ShapeLike = ... ) -> ndarray[Any, dtype[float64]]: ... @overload - def power(self, a: float, size: None = ...) -> float: ... # type: ignore[misc] + def power(self, a: _FloatLike_co, size: None = ...) -> float: ... # type: ignore[misc] @overload def power( self, a: _ArrayLikeFloat_co, size: None | _ShapeLike = ... @@ -500,7 +517,12 @@ class Generator: @overload def standard_cauchy(self, size: _ShapeLike = ...) -> ndarray[Any, dtype[float64]]: ... @overload - def laplace(self, loc: float = ..., scale: float = ..., size: None = ...) -> float: ... # type: ignore[misc] + def laplace( + self, + loc: _FloatLike_co = ..., + scale: _FloatLike_co = ..., + size: None = ..., + ) -> float: ... # type: ignore[misc] @overload def laplace( self, @@ -509,7 +531,12 @@ class Generator: size: None | _ShapeLike = ..., ) -> ndarray[Any, dtype[float64]]: ... @overload - def gumbel(self, loc: float = ..., scale: float = ..., size: None = ...) -> float: ... # type: ignore[misc] + def gumbel( + self, + loc: _FloatLike_co = ..., + scale: _FloatLike_co = ..., + size: None = ..., + ) -> float: ... # type: ignore[misc] @overload def gumbel( self, @@ -518,7 +545,12 @@ class Generator: size: None | _ShapeLike = ..., ) -> ndarray[Any, dtype[float64]]: ... @overload - def logistic(self, loc: float = ..., scale: float = ..., size: None = ...) -> float: ... # type: ignore[misc] + def logistic( + self, + loc: _FloatLike_co = ..., + scale: _FloatLike_co = ..., + size: None = ..., + ) -> float: ... # type: ignore[misc] @overload def logistic( self, @@ -527,7 +559,12 @@ class Generator: size: None | _ShapeLike = ..., ) -> ndarray[Any, dtype[float64]]: ... @overload - def lognormal(self, mean: float = ..., sigma: float = ..., size: None = ...) -> float: ... # type: ignore[misc] + def lognormal( + self, + mean: _FloatLike_co = ..., + sigma: _FloatLike_co = ..., + size: None = ..., + ) -> float: ... # type: ignore[misc] @overload def lognormal( self, @@ -536,19 +573,25 @@ class Generator: size: None | _ShapeLike = ..., ) -> ndarray[Any, dtype[float64]]: ... @overload - def rayleigh(self, scale: float = ..., size: None = ...) -> float: ... # type: ignore[misc] + def rayleigh(self, scale: _FloatLike_co = ..., size: None = ...) -> float: ... # type: ignore[misc] @overload def rayleigh( self, scale: _ArrayLikeFloat_co = ..., size: None | _ShapeLike = ... ) -> ndarray[Any, dtype[float64]]: ... @overload - def wald(self, mean: float, scale: float, size: None = ...) -> float: ... # type: ignore[misc] + def wald(self, mean: _FloatLike_co, scale: _FloatLike_co, size: None = ...) -> float: ... # type: ignore[misc] @overload def wald( self, mean: _ArrayLikeFloat_co, scale: _ArrayLikeFloat_co, size: None | _ShapeLike = ... ) -> ndarray[Any, dtype[float64]]: ... @overload - def triangular(self, left: float, mode: float, right: float, size: None = ...) -> float: ... # type: ignore[misc] + def triangular( + self, + left: _FloatLike_co, + mode: _FloatLike_co, + right: _FloatLike_co, + size: None = ..., + ) -> float: ... # type: ignore[misc] @overload def triangular( self, @@ -558,31 +601,31 @@ class Generator: size: None | _ShapeLike = ..., ) -> ndarray[Any, dtype[float64]]: ... @overload - def binomial(self, n: int, p: float, size: None = ...) -> int: ... # type: ignore[misc] + def binomial(self, n: int, p: _FloatLike_co, size: None = ...) -> int: ... # type: ignore[misc] @overload def binomial( self, n: _ArrayLikeInt_co, p: _ArrayLikeFloat_co, size: None | _ShapeLike = ... ) -> ndarray[Any, dtype[int64]]: ... @overload - def negative_binomial(self, n: float, p: float, size: None = ...) -> int: ... # type: ignore[misc] + def negative_binomial(self, n: _FloatLike_co, p: _FloatLike_co, size: None = ...) -> int: ... # type: ignore[misc] @overload def negative_binomial( self, n: _ArrayLikeFloat_co, p: _ArrayLikeFloat_co, size: None | _ShapeLike = ... ) -> ndarray[Any, dtype[int64]]: ... @overload - def poisson(self, lam: float = ..., size: None = ...) -> int: ... # type: ignore[misc] + def poisson(self, lam: _FloatLike_co = ..., size: None = ...) -> int: ... # type: ignore[misc] @overload def poisson( self, lam: _ArrayLikeFloat_co = ..., size: None | _ShapeLike = ... ) -> ndarray[Any, dtype[int64]]: ... @overload - def zipf(self, a: float, size: None = ...) -> int: ... # type: ignore[misc] + def zipf(self, a: _FloatLike_co, size: None = ...) -> int: ... # type: ignore[misc] @overload def zipf( self, a: _ArrayLikeFloat_co, size: None | _ShapeLike = ... ) -> ndarray[Any, dtype[int64]]: ... @overload - def geometric(self, p: float, size: None = ...) -> int: ... # type: ignore[misc] + def geometric(self, p: _FloatLike_co, size: None = ...) -> int: ... # type: ignore[misc] @overload def geometric( self, p: _ArrayLikeFloat_co, size: None | _ShapeLike = ... @@ -598,7 +641,7 @@ class Generator: size: None | _ShapeLike = ..., ) -> ndarray[Any, dtype[int64]]: ... @overload - def logseries(self, p: float, size: None = ...) -> int: ... # type: ignore[misc] + def logseries(self, p: _FloatLike_co, size: None = ...) -> int: ... # type: ignore[misc] @overload def logseries( self, p: _ArrayLikeFloat_co, size: None | _ShapeLike = ... diff --git a/numpy/random/_generator.pyx b/numpy/random/_generator.pyx index da66c1cac..a30d116c2 100644 --- a/numpy/random/_generator.pyx +++ b/numpy/random/_generator.pyx @@ -238,6 +238,64 @@ cdef class Generator: """ return self._bit_generator + def spawn(self, int n_children): + """ + spawn(n_children) + + Create new independent child generators. + + See :ref:`seedsequence-spawn` for additional notes on spawning + children. + + .. versionadded:: 1.25.0 + + Parameters + ---------- + n_children : int + + Returns + ------- + child_generators : list of Generators + + Raises + ------ + TypeError + When the underlying SeedSequence does not implement spawning. + + See Also + -------- + random.BitGenerator.spawn, random.SeedSequence.spawn : + Equivalent method on the bit generator and seed sequence. + bit_generator : + The bit generator instance used by the generator. + + Examples + -------- + Starting from a seeded default generator: + + >>> # High quality entropy created with: f"0x{secrets.randbits(128):x}" + >>> entropy = 0x3034c61a9ae04ff8cb62ab8ec2c4b501 + >>> rng = np.random.default_rng(entropy) + + Create two new generators for example for parallel executation: + + >>> child_rng1, child_rng2 = rng.spawn(2) + + Drawn numbers from each are independent but derived from the initial + seeding entropy: + + >>> rng.uniform(), child_rng1.uniform(), child_rng2.uniform() + (0.19029263503854454, 0.9475673279178444, 0.4702687338396767) + + It is safe to spawn additional children from the original ``rng`` or + the children: + + >>> more_child_rngs = rng.spawn(20) + >>> nested_spawn = child_rng1.spawn(20) + + """ + return [type(self)(g) for g in self._bit_generator.spawn(n_children)] + def random(self, size=None, dtype=np.float64, out=None): """ random(size=None, dtype=np.float64, out=None) @@ -380,6 +438,22 @@ cdef class Generator: out : ndarray or scalar Drawn samples from the parameterized exponential distribution. + Examples + -------- + A real world example: Assume a company has 10000 customer support + agents and the average time between customer calls is 4 minutes. + + >>> n = 10000 + >>> time_between_calls = np.random.default_rng().exponential(scale=4, size=n) + + What is the probability that a customer will call in the next + 4 to 5 minutes? + + >>> x = ((time_between_calls < 5).sum())/n + >>> y = ((time_between_calls < 4).sum())/n + >>> x-y + 0.08 # may vary + References ---------- .. [1] Peyton Z. Peebles Jr., "Probability, Random Variables and @@ -2560,7 +2634,7 @@ cdef class Generator: >>> b = [] >>> for i in range(1000): ... a = 10. + rng.standard_normal(100) - ... b.append(np.product(a)) + ... b.append(np.prod(a)) >>> b = np.array(b) / np.min(b) # scale values to be positive >>> count, bins, ignored = plt.hist(b, 100, density=True, align='mid') @@ -3533,8 +3607,8 @@ cdef class Generator: generalization of the one-dimensional normal distribution to higher dimensions. Such a distribution is specified by its mean and covariance matrix. These parameters are analogous to the mean - (average or "center") and variance (standard deviation, or "width," - squared) of the one-dimensional normal distribution. + (average or "center") and variance (the squared standard deviation, + or "width") of the one-dimensional normal distribution. Parameters ---------- @@ -3611,6 +3685,12 @@ cdef class Generator: nonnegative-definite). Otherwise, the behavior of this method is undefined and backwards compatibility is not guaranteed. + This function internally uses linear algebra routines, and thus results + may not be identical (even up to precision) across architectures, OSes, + or even builds. For example, this is likely if ``cov`` has multiple equal + singular values and ``method`` is ``'svd'`` (default). In this case, + ``method='cholesky'`` may be more robust. + References ---------- .. [1] Papoulis, A., "Probability, Random Variables, and Stochastic @@ -4247,7 +4327,7 @@ cdef class Generator: Raises ------ ValueError - If any value in ``alpha`` is less than or equal to zero + If any value in ``alpha`` is less than zero Notes ----- @@ -4326,8 +4406,8 @@ cdef class Generator: alpha_arr = <np.ndarray>np.PyArray_FROMANY( alpha, np.NPY_DOUBLE, 1, 1, np.NPY_ARRAY_ALIGNED | np.NPY_ARRAY_C_CONTIGUOUS) - if np.any(np.less_equal(alpha_arr, 0)): - raise ValueError('alpha <= 0') + if np.any(np.less(alpha_arr, 0)): + raise ValueError('alpha < 0') alpha_data = <double*>np.PyArray_DATA(alpha_arr) if size is None: @@ -4803,6 +4883,8 @@ def default_rng(seed=None): ----- If ``seed`` is not a `BitGenerator` or a `Generator`, a new `BitGenerator` is instantiated. This function does not manage a default global instance. + + See :ref:`seeding_and_entropy` for more information about seeding. Examples -------- diff --git a/numpy/random/_mt19937.pyx b/numpy/random/_mt19937.pyx index 5a8d52e6b..8b991254a 100644 --- a/numpy/random/_mt19937.pyx +++ b/numpy/random/_mt19937.pyx @@ -28,16 +28,16 @@ cdef extern from "src/mt19937/mt19937.h": enum: RK_STATE_LEN -cdef uint64_t mt19937_uint64(void *st) nogil: +cdef uint64_t mt19937_uint64(void *st) noexcept nogil: return mt19937_next64(<mt19937_state *> st) -cdef uint32_t mt19937_uint32(void *st) nogil: +cdef uint32_t mt19937_uint32(void *st) noexcept nogil: return mt19937_next32(<mt19937_state *> st) -cdef double mt19937_double(void *st) nogil: +cdef double mt19937_double(void *st) noexcept nogil: return mt19937_next_double(<mt19937_state *> st) -cdef uint64_t mt19937_raw(void *st) nogil: +cdef uint64_t mt19937_raw(void *st) noexcept nogil: return <uint64_t>mt19937_next32(<mt19937_state *> st) cdef class MT19937(BitGenerator): diff --git a/numpy/random/_pcg64.pyx b/numpy/random/_pcg64.pyx index c0a10a812..f7891aa85 100644 --- a/numpy/random/_pcg64.pyx +++ b/numpy/random/_pcg64.pyx @@ -26,26 +26,26 @@ cdef extern from "src/pcg64/pcg64.h": void pcg64_get_state(pcg64_state *state, uint64_t *state_arr, int *has_uint32, uint32_t *uinteger) void pcg64_set_state(pcg64_state *state, uint64_t *state_arr, int has_uint32, uint32_t uinteger) - uint64_t pcg64_cm_next64(pcg64_state *state) nogil - uint32_t pcg64_cm_next32(pcg64_state *state) nogil + uint64_t pcg64_cm_next64(pcg64_state *state) noexcept nogil + uint32_t pcg64_cm_next32(pcg64_state *state) noexcept nogil void pcg64_cm_advance(pcg64_state *state, uint64_t *step) -cdef uint64_t pcg64_uint64(void* st) nogil: +cdef uint64_t pcg64_uint64(void* st) noexcept nogil: return pcg64_next64(<pcg64_state *>st) -cdef uint32_t pcg64_uint32(void *st) nogil: +cdef uint32_t pcg64_uint32(void *st) noexcept nogil: return pcg64_next32(<pcg64_state *> st) -cdef double pcg64_double(void* st) nogil: +cdef double pcg64_double(void* st) noexcept nogil: return uint64_to_double(pcg64_next64(<pcg64_state *>st)) -cdef uint64_t pcg64_cm_uint64(void* st) nogil: +cdef uint64_t pcg64_cm_uint64(void* st) noexcept nogil: return pcg64_cm_next64(<pcg64_state *>st) -cdef uint32_t pcg64_cm_uint32(void *st) nogil: +cdef uint32_t pcg64_cm_uint32(void *st) noexcept nogil: return pcg64_cm_next32(<pcg64_state *> st) -cdef double pcg64_cm_double(void* st) nogil: +cdef double pcg64_cm_double(void* st) noexcept nogil: return uint64_to_double(pcg64_cm_next64(<pcg64_state *>st)) cdef class PCG64(BitGenerator): @@ -515,4 +515,3 @@ cdef class PCG64DXSM(BitGenerator): pcg64_cm_advance(&self.rng_state, <uint64_t *>np.PyArray_DATA(d)) self._reset_state_variables() return self - diff --git a/numpy/random/_philox.pyx b/numpy/random/_philox.pyx index d9a366e86..e5353460c 100644 --- a/numpy/random/_philox.pyx +++ b/numpy/random/_philox.pyx @@ -36,19 +36,19 @@ cdef extern from 'src/philox/philox.h': ctypedef s_philox_state philox_state - uint64_t philox_next64(philox_state *state) nogil - uint32_t philox_next32(philox_state *state) nogil + uint64_t philox_next64(philox_state *state) noexcept nogil + uint32_t philox_next32(philox_state *state) noexcept nogil void philox_jump(philox_state *state) void philox_advance(uint64_t *step, philox_state *state) -cdef uint64_t philox_uint64(void*st) nogil: +cdef uint64_t philox_uint64(void*st) noexcept nogil: return philox_next64(<philox_state *> st) -cdef uint32_t philox_uint32(void *st) nogil: +cdef uint32_t philox_uint32(void *st) noexcept nogil: return philox_next32(<philox_state *> st) -cdef double philox_double(void*st) nogil: +cdef double philox_double(void*st) noexcept nogil: return uint64_to_double(philox_next64(<philox_state *> st)) cdef class Philox(BitGenerator): diff --git a/numpy/random/_sfc64.pyx b/numpy/random/_sfc64.pyx index 1daee34f8..419045c1d 100644 --- a/numpy/random/_sfc64.pyx +++ b/numpy/random/_sfc64.pyx @@ -21,13 +21,13 @@ cdef extern from "src/sfc64/sfc64.h": void sfc64_set_state(sfc64_state *state, uint64_t *state_arr, int has_uint32, uint32_t uinteger) -cdef uint64_t sfc64_uint64(void* st) nogil: +cdef uint64_t sfc64_uint64(void* st) noexcept nogil: return sfc64_next64(<sfc64_state *>st) -cdef uint32_t sfc64_uint32(void *st) nogil: +cdef uint32_t sfc64_uint32(void *st) noexcept nogil: return sfc64_next32(<sfc64_state *> st) -cdef double sfc64_double(void* st) nogil: +cdef double sfc64_double(void* st) noexcept nogil: return uint64_to_double(sfc64_next64(<sfc64_state *>st)) diff --git a/numpy/random/bit_generator.pyi b/numpy/random/bit_generator.pyi index e6e3b10cd..8b9779cad 100644 --- a/numpy/random/bit_generator.pyi +++ b/numpy/random/bit_generator.pyi @@ -96,6 +96,9 @@ class BitGenerator(abc.ABC): def state(self) -> Mapping[str, Any]: ... @state.setter def state(self, value: Mapping[str, Any]) -> None: ... + @property + def seed_seq(self) -> ISeedSequence: ... + def spawn(self, n_children: int) -> list[BitGenerator]: ... @overload def random_raw(self, size: None = ..., output: Literal[True] = ...) -> int: ... # type: ignore[misc] @overload diff --git a/numpy/random/bit_generator.pyx b/numpy/random/bit_generator.pyx index 3da4fabce..83441747a 100644 --- a/numpy/random/bit_generator.pyx +++ b/numpy/random/bit_generator.pyx @@ -212,6 +212,9 @@ class ISpawnableSeedSequence(ISeedSequence): Spawn a number of child `SeedSequence` s by extending the `spawn_key`. + See :ref:`seedsequence-spawn` for additional notes on spawning + children. + Parameters ---------- n_children : int @@ -260,6 +263,7 @@ cdef class SeedSequence(): ---------- entropy : {None, int, sequence[int]}, optional The entropy for creating a `SeedSequence`. + All integer values must be non-negative. spawn_key : {(), sequence[int]}, optional An additional source of entropy based on the position of this `SeedSequence` in the tree of such objects created with the @@ -450,6 +454,9 @@ cdef class SeedSequence(): Spawn a number of child `SeedSequence` s by extending the `spawn_key`. + See :ref:`seedsequence-spawn` for additional notes on spawning + children. + Parameters ---------- n_children : int @@ -457,6 +464,12 @@ cdef class SeedSequence(): Returns ------- seqs : list of `SeedSequence` s + + See Also + -------- + random.Generator.spawn, random.BitGenerator.spawn : + Equivalent method on the generator and bit generator. + """ cdef uint32_t i @@ -490,6 +503,7 @@ cdef class BitGenerator(): ``array_like[ints]`` is passed, then it will be passed to `~numpy.random.SeedSequence` to derive the initial `BitGenerator` state. One may also pass in a `SeedSequence` instance. + All integer values must be non-negative. Attributes ---------- @@ -549,6 +563,59 @@ cdef class BitGenerator(): def state(self, value): raise NotImplementedError('Not implemented in base BitGenerator') + @property + def seed_seq(self): + """ + Get the seed sequence used to initialize the bit generator. + + .. versionadded:: 1.25.0 + + Returns + ------- + seed_seq : ISeedSequence + The SeedSequence object used to initialize the BitGenerator. + This is normally a `np.random.SeedSequence` instance. + + """ + return self._seed_seq + + def spawn(self, int n_children): + """ + spawn(n_children) + + Create new independent child bit generators. + + See :ref:`seedsequence-spawn` for additional notes on spawning + children. Some bit generators also implement ``jumped`` + as a different approach for creating independent streams. + + .. versionadded:: 1.25.0 + + Parameters + ---------- + n_children : int + + Returns + ------- + child_bit_generators : list of BitGenerators + + Raises + ------ + TypeError + When the underlying SeedSequence does not implement spawning. + + See Also + -------- + random.Generator.spawn, random.SeedSequence.spawn : + Equivalent method on the generator and seed sequence. + + """ + if not isinstance(self._seed_seq, ISpawnableSeedSequence): + raise TypeError( + "The underlying SeedSequence does not implement spawning.") + + return [type(self)(seed=s) for s in self._seed_seq.spawn(n_children)] + def random_raw(self, size=None, output=True): """ random_raw(self, size=None) diff --git a/numpy/random/c_distributions.pxd b/numpy/random/c_distributions.pxd index 6f905edc1..b978d1350 100644 --- a/numpy/random/c_distributions.pxd +++ b/numpy/random/c_distributions.pxd @@ -28,18 +28,24 @@ cdef extern from "numpy/random/distributions.h": ctypedef s_binomial_t binomial_t + float random_standard_uniform_f(bitgen_t *bitgen_state) nogil double random_standard_uniform(bitgen_t *bitgen_state) nogil void random_standard_uniform_fill(bitgen_t* bitgen_state, npy_intp cnt, double *out) nogil + void random_standard_uniform_fill_f(bitgen_t *bitgen_state, npy_intp cnt, float *out) nogil + double random_standard_exponential(bitgen_t *bitgen_state) nogil - double random_standard_exponential_f(bitgen_t *bitgen_state) nogil + float random_standard_exponential_f(bitgen_t *bitgen_state) nogil void random_standard_exponential_fill(bitgen_t *bitgen_state, npy_intp cnt, double *out) nogil - void random_standard_exponential_fill_f(bitgen_t *bitgen_state, npy_intp cnt, double *out) nogil + void random_standard_exponential_fill_f(bitgen_t *bitgen_state, npy_intp cnt, float *out) nogil void random_standard_exponential_inv_fill(bitgen_t *bitgen_state, npy_intp cnt, double *out) nogil - void random_standard_exponential_inv_fill_f(bitgen_t *bitgen_state, npy_intp cnt, double *out) nogil + void random_standard_exponential_inv_fill_f(bitgen_t *bitgen_state, npy_intp cnt, float *out) nogil + double random_standard_normal(bitgen_t* bitgen_state) nogil + float random_standard_normal_f(bitgen_t *bitgen_state) nogil void random_standard_normal_fill(bitgen_t *bitgen_state, npy_intp count, double *out) nogil void random_standard_normal_fill_f(bitgen_t *bitgen_state, npy_intp count, float *out) nogil double random_standard_gamma(bitgen_t *bitgen_state, double shape) nogil + float random_standard_gamma_f(bitgen_t *bitgen_state, float shape) nogil float random_standard_uniform_f(bitgen_t *bitgen_state) nogil void random_standard_uniform_fill_f(bitgen_t* bitgen_state, npy_intp cnt, float *out) nogil diff --git a/numpy/random/meson.build b/numpy/random/meson.build index cc61c66dd..036cd81b9 100644 --- a/numpy/random/meson.build +++ b/numpy/random/meson.build @@ -66,7 +66,7 @@ random_pyx_sources = [ fs.copyfile('mtrand.pyx'), 'src/distributions/distributions.c', 'src/legacy/legacy-distributions.c' - ], ['-DNPY_RANDOM_LEGACY=1'], npymath_lib, + ], ['-DNP_RANDOM_LEGACY=1'], npymath_lib, ], ] foreach gen: random_pyx_sources diff --git a/numpy/random/mtrand.pyx b/numpy/random/mtrand.pyx index edf812a4d..dfa553ee4 100644 --- a/numpy/random/mtrand.pyx +++ b/numpy/random/mtrand.pyx @@ -537,6 +537,22 @@ cdef class RandomState: out : ndarray or scalar Drawn samples from the parameterized exponential distribution. + Examples + -------- + A real world example: Assume a company has 10000 customer support + agents and the average time between customer calls is 4 minutes. + + >>> n = 10000 + >>> time_between_calls = np.random.default_rng().exponential(scale=4, size=n) + + What is the probability that a customer will call in the next + 4 to 5 minutes? + + >>> x = ((time_between_calls < 5).sum())/n + >>> y = ((time_between_calls < 4).sum())/n + >>> x-y + 0.08 # may vary + See Also -------- random.Generator.exponential: which should be used for new code. @@ -3050,7 +3066,7 @@ cdef class RandomState: >>> b = [] >>> for i in range(1000): ... a = 10. + np.random.standard_normal(100) - ... b.append(np.product(a)) + ... b.append(np.prod(a)) >>> b = np.array(b) / np.min(b) # scale values to be positive >>> count, bins, ignored = plt.hist(b, 100, density=True, align='mid') diff --git a/numpy/random/src/distributions/distributions.c b/numpy/random/src/distributions/distributions.c index 9ab3f94a0..cebeb07cf 100644 --- a/numpy/random/src/distributions/distributions.c +++ b/numpy/random/src/distributions/distributions.c @@ -960,7 +960,15 @@ RAND_INT_TYPE random_geometric_search(bitgen_t *bitgen_state, double p) { } int64_t random_geometric_inversion(bitgen_t *bitgen_state, double p) { - return (int64_t)ceil(-random_standard_exponential(bitgen_state) / npy_log1p(-p)); + double z = ceil(-random_standard_exponential(bitgen_state) / npy_log1p(-p)); + /* + * The constant 9.223372036854776e+18 is the smallest double that is + * larger than INT64_MAX. + */ + if (z >= 9.223372036854776e+18) { + return INT64_MAX; + } + return (int64_t) z; } int64_t random_geometric(bitgen_t *bitgen_state, double p) { diff --git a/numpy/random/tests/test_direct.py b/numpy/random/tests/test_direct.py index 58d966adf..fa2ae866b 100644 --- a/numpy/random/tests/test_direct.py +++ b/numpy/random/tests/test_direct.py @@ -148,6 +148,46 @@ def test_seedsequence(): assert len(dummy.spawn(10)) == 10 +def test_generator_spawning(): + """ Test spawning new generators and bit_generators directly. + """ + rng = np.random.default_rng() + seq = rng.bit_generator.seed_seq + new_ss = seq.spawn(5) + expected_keys = [seq.spawn_key + (i,) for i in range(5)] + assert [c.spawn_key for c in new_ss] == expected_keys + + new_bgs = rng.bit_generator.spawn(5) + expected_keys = [seq.spawn_key + (i,) for i in range(5, 10)] + assert [bg.seed_seq.spawn_key for bg in new_bgs] == expected_keys + + new_rngs = rng.spawn(5) + expected_keys = [seq.spawn_key + (i,) for i in range(10, 15)] + found_keys = [rng.bit_generator.seed_seq.spawn_key for rng in new_rngs] + assert found_keys == expected_keys + + # Sanity check that streams are actually different: + assert new_rngs[0].uniform() != new_rngs[1].uniform() + + +def test_non_spawnable(): + from numpy.random.bit_generator import ISeedSequence + + class FakeSeedSequence: + def generate_state(self, n_words, dtype=np.uint32): + return np.zeros(n_words, dtype=dtype) + + ISeedSequence.register(FakeSeedSequence) + + rng = np.random.default_rng(FakeSeedSequence()) + + with pytest.raises(TypeError, match="The underlying SeedSequence"): + rng.spawn(5) + + with pytest.raises(TypeError, match="The underlying SeedSequence"): + rng.bit_generator.spawn(5) + + class Base: dtype = np.uint64 data2 = data1 = {} diff --git a/numpy/random/tests/test_generator_mt19937.py b/numpy/random/tests/test_generator_mt19937.py index 54a5b73a3..5c4c2cbf9 100644 --- a/numpy/random/tests/test_generator_mt19937.py +++ b/numpy/random/tests/test_generator_mt19937.py @@ -345,6 +345,8 @@ class TestIntegers: endpoint=endpoint, dtype=dt) assert_raises(ValueError, self.rfunc, 1, [0], endpoint=endpoint, dtype=dt) + assert_raises(ValueError, self.rfunc, [ubnd+1], [ubnd], + endpoint=endpoint, dtype=dt) def test_bounds_checking_array(self, endpoint): for dt in self.itype: diff --git a/numpy/random/tests/test_generator_mt19937_regressions.py b/numpy/random/tests/test_generator_mt19937_regressions.py index 0227d6502..7c2b6867c 100644 --- a/numpy/random/tests/test_generator_mt19937_regressions.py +++ b/numpy/random/tests/test_generator_mt19937_regressions.py @@ -3,32 +3,32 @@ import numpy as np import pytest from numpy.random import Generator, MT19937 -mt19937 = Generator(MT19937()) - class TestRegression: + def setup_method(self): + self.mt19937 = Generator(MT19937(121263137472525314065)) + def test_vonmises_range(self): # Make sure generated random variables are in [-pi, pi]. # Regression test for ticket #986. for mu in np.linspace(-7., 7., 5): - r = mt19937.vonmises(mu, 1, 50) + r = self.mt19937.vonmises(mu, 1, 50) assert_(np.all(r > -np.pi) and np.all(r <= np.pi)) def test_hypergeometric_range(self): # Test for ticket #921 - assert_(np.all(mt19937.hypergeometric(3, 18, 11, size=10) < 4)) - assert_(np.all(mt19937.hypergeometric(18, 3, 11, size=10) > 0)) + assert_(np.all(self.mt19937.hypergeometric(3, 18, 11, size=10) < 4)) + assert_(np.all(self.mt19937.hypergeometric(18, 3, 11, size=10) > 0)) # Test for ticket #5623 args = (2**20 - 2, 2**20 - 2, 2**20 - 2) # Check for 32-bit systems - assert_(mt19937.hypergeometric(*args) > 0) + assert_(self.mt19937.hypergeometric(*args) > 0) def test_logseries_convergence(self): # Test for ticket #923 N = 1000 - mt19937 = Generator(MT19937(0)) - rvsn = mt19937.logseries(0.8, size=N) + rvsn = self.mt19937.logseries(0.8, size=N) # these two frequency counts should be close to theoretical # numbers with this large sample # theoretical large N result is 0.49706795 @@ -65,41 +65,38 @@ class TestRegression: # Test for multivariate_normal issue with 'size' argument. # Check that the multivariate_normal size argument can be a # numpy integer. - mt19937.multivariate_normal([0], [[0]], size=1) - mt19937.multivariate_normal([0], [[0]], size=np.int_(1)) - mt19937.multivariate_normal([0], [[0]], size=np.int64(1)) + self.mt19937.multivariate_normal([0], [[0]], size=1) + self.mt19937.multivariate_normal([0], [[0]], size=np.int_(1)) + self.mt19937.multivariate_normal([0], [[0]], size=np.int64(1)) def test_beta_small_parameters(self): # Test that beta with small a and b parameters does not produce # NaNs due to roundoff errors causing 0 / 0, gh-5851 - mt19937 = Generator(MT19937(1234567890)) - x = mt19937.beta(0.0001, 0.0001, size=100) + x = self.mt19937.beta(0.0001, 0.0001, size=100) assert_(not np.any(np.isnan(x)), 'Nans in mt19937.beta') def test_choice_sum_of_probs_tolerance(self): # The sum of probs should be 1.0 with some tolerance. # For low precision dtypes the tolerance was too tight. # See numpy github issue 6123. - mt19937 = Generator(MT19937(1234)) a = [1, 2, 3] counts = [4, 4, 2] for dt in np.float16, np.float32, np.float64: probs = np.array(counts, dtype=dt) / sum(counts) - c = mt19937.choice(a, p=probs) + c = self.mt19937.choice(a, p=probs) assert_(c in a) with pytest.raises(ValueError): - mt19937.choice(a, p=probs*0.9) + self.mt19937.choice(a, p=probs*0.9) def test_shuffle_of_array_of_different_length_strings(self): # Test that permuting an array of different length strings # will not cause a segfault on garbage collection # Tests gh-7710 - mt19937 = Generator(MT19937(1234)) a = np.array(['a', 'a' * 1000]) for _ in range(100): - mt19937.shuffle(a) + self.mt19937.shuffle(a) # Force Garbage Collection - should not segfault. import gc @@ -109,17 +106,17 @@ class TestRegression: # Test that permuting an array of objects will not cause # a segfault on garbage collection. # See gh-7719 - mt19937 = Generator(MT19937(1234)) a = np.array([np.arange(1), np.arange(4)], dtype=object) for _ in range(1000): - mt19937.shuffle(a) + self.mt19937.shuffle(a) # Force Garbage Collection - should not segfault. import gc gc.collect() def test_permutation_subclass(self): + class N(np.ndarray): pass @@ -142,9 +139,16 @@ class TestRegression: assert_array_equal(m.__array__(), np.arange(5)) def test_gamma_0(self): - assert mt19937.standard_gamma(0.0) == 0.0 - assert_array_equal(mt19937.standard_gamma([0.0]), 0.0) + assert self.mt19937.standard_gamma(0.0) == 0.0 + assert_array_equal(self.mt19937.standard_gamma([0.0]), 0.0) - actual = mt19937.standard_gamma([0.0], dtype='float') + actual = self.mt19937.standard_gamma([0.0], dtype='float') expected = np.array([0.], dtype=np.float32) assert_array_equal(actual, expected) + + def test_geometric_tiny_prob(self): + # Regression test for gh-17007. + # When p = 1e-30, the probability that a sample will exceed 2**63-1 + # is 0.9999999999907766, so we expect the result to be all 2**63-1. + assert_array_equal(self.mt19937.geometric(p=1e-30, size=3), + np.iinfo(np.int64).max) diff --git a/numpy/random/tests/test_randomstate.py b/numpy/random/tests/test_randomstate.py index 8b911cb3a..3a2961098 100644 --- a/numpy/random/tests/test_randomstate.py +++ b/numpy/random/tests/test_randomstate.py @@ -812,6 +812,10 @@ class TestRandomDist: alpha = np.array([5.4e-01, -1.0e-16]) assert_raises(ValueError, random.dirichlet, alpha) + def test_dirichlet_zero_alpha(self): + y = random.default_rng().dirichlet([5, 9, 0, 8]) + assert_equal(y[2], 0) + def test_dirichlet_alpha_non_contiguous(self): a = np.array([51.72840233779265162, -1.0, 39.74494232180943953]) alpha = a[::2] |